HOWTO: Set Tab Stops in a Visual Basic (16-bit) List Box

ID: Q71067


The information in this article applies to:


SUMMARY

Visual Basic version 3.0 has intrinsic multiple-column list boxes. You create the multiple columns by dividing the linear list of items into columns, based on the size of the list box. However, this can lead to overlapped columns if the length of the items in the list exceed the area allocated automatically by the list box.

NOTE: This is different than setting the Columns property of the list box, which merely determines whether a list box scrolls vertically or horizontally.

This article contains information on using the Windows API to create a multiple-column list box by setting the tab stops of the list box, thus creating the multiple-column effect. The related topics of dialog box units, dialog box base units, and providing a horizontal scroll bar on a list box are also covered.


MORE INFORMATION

For a complete example of this method used in a more generic manner, please see the following article in the Microsoft Knowledge Base:

Q115712 : How to Fill a List Box from a Snapshot Generically

To create the multiple-column effect in list boxes, you must use the Windows API SendMessage function. If you use the argument LB_SETTABSTOPS as the second parameter to SendMessage, it will set the tab stops you want for the multicolumn effect based on the other arguments to the function. The SendMessage function requires the following parameters to set tab stops:

   SendMessage (hWnd%, LB_SETTABSTOPS, wParam%, lparam&) 

Where:
For this to work, the values in the tab-stop array must be cumulative. For example, if you want to set three consecutive tabs every 50 units, you need to load the tab-stop array with 50, 100, and 150. The tabs work the same as typewriter tabs: once a tab stop is overrun, a tab character moves the cursor to the next tab stop. If the tab-stop list is overrun (that is, if the current position is greater than the last tab-stop value), the default tab value of 8 characters is used.

Dialog Box Units and Dialog Box Base Units

Tab stops in a list box are specified in dialog box units, not pixels or character position. Essentially, a dialog box unit is used by Windows to size a control based on the average character width of the current system font. This average character width is called the dialog box base unit, and 1 dialog box base unit equals 4 dialog box units (1:4 ratio).

When setting tab stops in the list box, however, dialog box units are based on the average character width, in pixels, of the currently selected font for the list box. Thus, you have to calculate the average character width of the current font in the list box before you can set its tab stops. The average character width is based on the average width of the upper- and lower-case characters of the alphabet; therefore, it can be calculated in the sample code as follows:

   ' First set the form's font properties to match the list box.
   Me.FontName = list1.FontName
   Me.FontSize = list1.FontSize
   Me.FontBold = list1.FontBold
   Me.FontItalic = list1.FontItalic
   Me.FontStrikethru = list1.FontStrikethru
   Me.FontUnderline = list1.FontUnderline

   ' Make use of the form's TextWidth function to calculate the average
   ' character width of the alphabet. Visual Basic uses Twips and the
   ' SendMessage API needs pixels, so use "screen.TwipsPerPixelX" to
   ' convert.
   ' The following code is used to return the dialog box unit
   ' equivalent to one character:
   alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
   AvgCharWidth = (Me.TextWidth(alphabet) / 52) / screen.TwipsPerPixelX

   ' Calculate the dialog box unit for the list box.
   ListBoxDialogBoxUnit =  AvgCharWidth \ 4 

You now have a way to relate dialog box units to pixel position. So to set a tab stop at a specific pixel position, take the number of pixels, divide by the average character width (gives us the number of dialog box base units) and multiply by 4 to give us the dialog box units. In code, this looks like the following:

   TabStop1 = (Me.TextWidth("Hello")/screen.TwipsPerPixelX \ 
   AvgCharWidth)*4 

NOTE: The example code assumes that the form's scale mode is the default Twips. If the scale mode is set to pixels, then the division by screen.TwipsPerPixelX is extraneous and should be removed. Also note that you could use the TextWidth method of a picture control if you didn't want to change the font properties of the form.

Providing a Horizontal Scroll Bar

When you use SendMessage to set tab stops, the Visual Basic list box does not automatically provide the horizontal scroll bar. To set the horizontal extent of the list box, use the API SendMessage with the LB_SETHORIZONTALEXTENT message. First, calculate the size, in pixels, of the scrolling region (based on the largest row entered into the list box). If you specify the horizontal extent to be larger than the width of the list box, a horizontal scroll bar is displayed.

The message LB_SETHORIZONTALEXTENT expects the extent to be specified in pixels. Because the tab-stop array holds dialog box units, which represent 4 times the number of average character spaces, to get that value converted back into pixels, perform the inverse operation.

  ' For simplicity, assume container scale mode of pixels.
  num_chars = Me.TextWidth("Total Character String") \ AvgCharWidth
  ListBoxDialogBoxUnits = num_chars * 4 

So the inverse operation is:

  num_pixels = (ListBoxDialogBoxUnits \ 4) * AvgCharWidth 

Step-by-Step Instructions for Creating the Program

  1. Start a new project in Visual Basic. This creates Form1 by default.


  2. On the form, create the following controls, and set the design-time properties shown:
    
          Control          Name        Property
          -------------------------------------------------------
          Command Button   Command1    Caption="Fill the ListBox"
          ListBox          List1       Fontname="MS Sans Serif" *
    
       * Any TrueType font will test the code.
     


  3. Add the following code to the form's general declarations level (NOTE: All declare statements should be complete on one line):
    
          Option Explicit
          Const WM_USER = &H400
          '--------------------------------------------------------------------
          ' Win16 API constants and API declarations.
          '--------------------------------------------------------------------
          Const LB_SETTABSTOPS = WM_USER + 19
          Const LB_SETHORIZONTALEXTENT = WM_USER + 21
    
          Declare Function SendMessage Lib "user" (ByVal hWnd As Integer, _
                      ByVal wMsg As Integer, ByVal wParam As Integer, _
                      lParam As Any) As Long
          Declare Function SetParent Lib "user" (ByVal hWndChild As Integer, _
                      ByVal hWndNew As Integer) As Integer
          Declare Sub ShellAbout Lib "shell.dll" (ByVal hWndOwner As Integer, _
                      ByVal lpszAppName As String, _
                      ByVal lpszMoreInfo As String, ByVal hIcon As Integer)
     


  4. Add the following code to Command1_Click event:
    
          Sub Command1_Click ()
             Const numchars = 20             ' White space between columns.
             Dim AvgCharWidth As Single
                                      ' Variables to store the font properties.
             Dim hold_fontname As String, hold_fontsize As Integer
             Dim hold_fontbold As Integer, hold_fontitalic As Integer
             Dim hold_fontstrikethru As Integer, hold_fontunderline As Integer
    
             Dim retL As Long               ' Return value of SendMessage
             Dim WhiteSpace As Integer      ' Blank pixels between columns
             Dim i As Integer               ' Counter
             Dim t$                         ' Temp variable
             Dim alphabet As String ' Holds uppercase and lowercase chars.
    
             ReDim tabstops(1 To 3) As Integer  ' Tab-stop array for API call.
             ReDim s(1 To 3) As String          ' String array to hold test
                                                ' data.
    
             ' Save the form's original properties.
             hold_fontname = Me.FontName
             hold_fontsize = Me.FontSize
             hold_fontbold = Me.FontBold
             hold_fontitalic = Me.FontItalic
             hold_fontstrikethru = Me.FontStrikethru
             hold_fontunderline = Me.FontUnderline
    
             ' Set the list box's container's properties
             ' so that the TextWidth method of the form
             ' gives accurate results.
             Me.FontName = list1.FontName
             Me.FontSize = list1.FontSize
             Me.FontBold = list1.FontBold
             Me.FontItalic = list1.FontItalic
             Me.FontStrikethru = list1.FontStrikethru
             Me.FontUnderline = list1.FontUnderline
    
             ' Clear the list box and set up column headers.
             list1.Clear
             list1.AddItem "Column1" & Chr(9) & "Column2" & Chr(9) & "Column3"
    
             ' Add test data to the list box.
             s(1) = "This is a test of the"
             s(2) = "Emergency Broadcast System."
             s(3) = "This is only a test!"
    
             ' Add the test data with tabs into list box.
             For i = LBound(s) To UBound(s)
                   t$ = t$ & s(i) & Chr(9)
             Next i
             list1.AddItem t$
    
             ' Get the average character width of the current list-box font
             ' now reflected in the container form's properties;
             ' this needs to be on one line of code.
             alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
             AvgCharWidth = (Me.TextWidth(alphabet) / screen.TwipsPerPixelX) _
             / 52
    
             ' Set a variable for the white space you want between columns.
             WhiteSpace = AvgCharWidth * numchars
    
             ' Accumulate tab stops in list box's dialog box units;
             ' NOTE: these tabstop statements should each be on one line.
             tabstops(1) = ((Me.TextWidth(s(1)) / screen.TwipsPerPixelX + _
             WhiteSpace) \ AvgCharWidth) * 4 tabstops(2) = tabstops(1) + _
             ((Me.TextWidth(s(2)) / screen.TwipsPerPixelX + WhiteSpace) \ _
             AvgCharWidth) * 4
    
             ' This third value is not necessary for the tab stops, but it
             ' is used to set the extent of the horizontal scrolling area.
             tabstops(3) = tabstops(2) + ((Me.TextWidth(s(3)) / _
             screen.TwipsPerPixelX + WhiteSpace) \ AvgCharWidth) * 4
    
             ' Set the tab stops for the list box in dialog box units.
             retL = SendMessage(list1.hWnd, LB_SETTABSTOPS, 3, tabstops(1))
    
             ' Set the horizontal extent to the last tab stop.
             ' Because the tab-stop array holds dialog box units (which
             ' represent 4 times the number of characters), to get that value
             ' converted into pixels, perform the inverse operation.
             retL = SendMessage(list1.hWnd, LB_SETHORIZONTALEXTENT, _
             (tabstops(3) \ 4) * AvgCharWidth, 0&)
    
             ' Tell the list box to refresh its display.
             list1.Refresh
    
             ' Restore form's property values.
             Me.FontName = hold_fontname
             Me.FontSize = hold_fontsize
             Me.FontBold = hold_fontbold
             Me.FontItalic = hold_fontitalic
             Me.FontStrikethru = hold_fontstrikethru
             Me.FontUnderline = hold_fontunderline
          End Sub 


  5. Run the program (press F5) and note that the list box contains neat columns displaying the contents of the sample data from the array and the scroll bar allows a view of the entire line. The constant numchars can be altered to allow more white space between columns.



Keywords          : kbcode kbVBp300 kbvbp200 
Version           : 
Platform          : 
Issue type        : kbhowto 

Last Reviewed: May 24, 1999