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:
115712
: 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:
- wParam% is an integer that specifies the number of tab stops.
- lParam& is a long pointer to the first member of an array of integers
containing the tab-stop position in dialog box units.
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
- Start a new project in Visual Basic. This creates Form1 by
default.
- 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.
- 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)
- 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
- 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.