List Box Column Widths

SirDrums

Registered User.
Local time
Today, 01:06
Joined
Jul 15, 2009
Messages
83
I am using a list box to return data from a query that will allow users to select multiple records to edit on another form.

The problem I am running into is that there are about 20 columns of data being returned and the length of the data returned in some columns can vary. I was wondering if there was a way to set the list box column width to adjust to the length of the longest string returned? I don't want to have to manually set the widths for 20 columns in the list boxes properties.

Thanks!
 
Stephan Lebans created a couple of custom classes for this a few years back. One that will auto resize the list box columns and another that will actually let you manually resize the columns with a mouse drag in form view. See this link for a downloadable sample application.

http://www.lebans.com/listboxcolumnresize.htm
 
Thanks for the link. I am trying to wade through his sample now... more complex than I am used to seeing (at least for me) and I am having a hard time following what is going on so I hope you guys are ok with me asking a few questions about this class module..

I have copied the class module clsAutoSizeColumns over to my DB and added the follwing to my forms load function:

Private Sub Form_Load()
' Create a new instance of our class
Set ColReSize1 = New clsAutoSizeColumns
Set ColReSize2 = New clsAutoSizeColumns
' We must tell the Class which control
' we want to work with.
ColReSize1.SetControl Me.List4
ColReSize2.SetControl Me.Combo25

' Set our Form size
DoCmd.MoveSize 0, 0, 9000, 5700
End Sub

I take it that the two variables ColReSize1 and 2 are set by the results of the class module? Then the variable sare used to set the properties of the list/combo boxes?

I replaced Me.List4 with the name of my listbox.

When I run the form, nothin happens.. the column widths are unchanged..

I looked into the class module ad found the code with a note:

Public Sub SetControl(ctl As Access.Control)
' You must set this property from the calling Form
' in order for this Class to work properly!!!
Dim lngTemp As Long
Dim strTemp As String
Dim intTemp As Integer

I dont understand where I set these properties?
 
ok, i think I understand a little more

ColReSize1.SetControl Me.List4
ColReSize2.SetControl Me.Combo25


These are actually telling the class module what objects to work with... I also add the follwing code to the General area of the form:

Private ColReSize1 As clsAutoSizeColumns
Private ColReSize2 As clsAutoSizeColumns


I still dont understand why its not working though.. I think I am giving the class module what it is asking for...
 
OK,

I just realized that on his example that the autosizing is turned on via a button on the form. I added a button with the following Click_code to my form

ColReSize1.AutoSize
ColReSize2.AutoSize

When I click this button when the form loads I get the following error:

Run-time error '9':
Subscript out of range

Any idea what that means?

Sorry for all the posts, just trying to work through this :)
 
Ok I have figured it out.

There has to be a column width set for each column of data returned.

For example if your query returns 10 columns of data, in the controls columns widths property you have to have a size set for each column (IE 1",1",1",1",1",1",1",1",1",1")

You can have to many column widths (you have 10 columns but you specify 20 in the column widths propoerty) but you cannot have to few.

Once I corrected this the form behaved properly.
 
I need to correct the statement above...

Actually you only need one 1" in the column width and it will handle the rest.
 
Ok new problem:

It seems that the routine has a limit of 36 columns before it starts to error out... can someone take a look at the code and tell me why this is?
Code:
'Copyright: Stephen Lebans - Lebans Holdings 1999 Ltd.
' Please feel free to use this code within your own
' projects whether they are private or commercial applications
' without obligation.
' This code may not be resold by itself or as part of a collection.
'
'Name: clsAutoSizeColumns
'
'Version: 1.0
'
'Purpose: Method to AUTOSIZE column widths.
'
'Author: Stephen Lebans
'Email: [EMAIL="Stephen@lebans.com"]Stephen@lebans.com[/EMAIL]
'
'Web Site: [URL="http://www.lebans.com"]www.lebans.com[/URL]
'
'Date: Nov 04, 2001, 11:11:11 PM
'
'Credits: Terry Kreft for the SPLIT function.
'
'BUGS: Please report any bugs to my email address.
'
'What's Missing:
' Proper error handling.
'
'
'How it Works:
' It's late...you read the code and figure it out!<GRIN>
'
'
' Remember, in life you get what you pay for.
' Please remember what you paid for this code!<grin>
'
' Enjoy!
' SL
Option Compare Database
Option Explicit

Private Const TWIPSPERINCH = 1440
Private Const MouseNormal = 0    '(Default) The shape is determined by Microsoft Access
Private Const MouseArrow = 1
Private Const MouseIBeam = 3
Private Const MouseVerticalResize = 7    ' (Size N, S)
Private Const MouseHorizontalResize = 9    ' Horizontal Resize (Size E, W)
Private Const MouseBusy = 111    ' Busy (Hourglass)

Private Type Size
    cx         As Long
    cy         As Long
End Type

Private Const LF_FACESIZE = 32

Private Type LOGFONT
    lfHeight   As Long
    lfWidth    As Long
    lfEscapement As Long
    lfOrientation As Long
    lfWeight   As Long
    lfItalic   As Byte
    lfUnderline As Byte
    lfStrikeOut As Byte
    lfCharSet  As Byte
    lfOutPrecision As Byte
    lfClipPrecision As Byte
    lfQuality  As Byte
    lfPitchAndFamily As Byte
    lfFaceName As String * LF_FACESIZE
End Type

Private Declare Function apiCreateFontIndirect Lib "gdi32" Alias _
                                               "CreateFontIndirectA" (lpLogFont As LOGFONT) As Long
Private Declare Function apiSelectObject Lib "gdi32" _
                                         Alias "SelectObject" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" _
                                            (ByVal hDC As Long) As Long
Private Declare Function apiGetDC Lib "user32" _
                                  Alias "GetDC" (ByVal hwnd As Long) As Long
Private Declare Function apiReleaseDC Lib "user32" _
                                      Alias "ReleaseDC" (ByVal hwnd As Long, _
                                                         ByVal hDC As Long) As Long
Private Declare Function apiDeleteObject Lib "gdi32" _
                                         Alias "DeleteObject" (ByVal hObject As Long) As Long
Private Declare Function apiGetTextExtentPoint32 Lib "gdi32" _
                                                 Alias "GetTextExtentPoint32A" _
                                                 (ByVal hDC As Long, ByVal lpsz As String, ByVal cbString As Long, _
                                                  lpSize As Size) As Long
' Create an Information Context
Private Declare Function apiCreateIC Lib "gdi32" Alias "CreateICA" _
                                     (ByVal lpDriverName As String, ByVal lpDeviceName As String, _
                                      ByVal lpOutput As String, lpInitData As Any) As Long
' Close an existing Device Context (or information context)
Private Declare Function apiDeleteDC Lib "gdi32" Alias "DeleteDC" _
                                     (ByVal hDC As Long) As Long
Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" _
                                       (ByVal hDC As Long, ByVal nIndex As Long) As Long
 
' Constants
Private Const SM_CXVSCROLL = 2
Private Const LOGPIXELSX = 88

' Array of strings used to build the ColumnWidth property
Private strWidthArray() As String

' Array of Column Widths.
' The entries are cumulative in order to
' aid matching of the start of each column
Private sngWidthArray() As Single

' Amount of extra space to add to edge of each column
Private m_ColumnMargin As Long

' ListBox/Combo we are resizing
Private m_Control As Access.Control
'
 
Public Sub SetControl(ctl As Access.Control)
' You must set this property from the calling Form
' in order for this Class to work properly!!!
    Dim lngTemp As Long
    Dim strTemp As String
    Dim intTemp As Integer
    ' Junk Var for loops
    Dim ctr    As Long

    ' Save a local reference
    Set m_Control = ctl

    ' If we access the ListIndex property
    ' then the entire Index for the RowSource
    ' behind each ListBox is loaded.
    ' Allows for smoother initial scrolling.
    lngTemp = m_Control.ListCount

    ' Check and see if there is only one entry
    ' for the ColumnWidth property. This would
    ' signify the value is to be repeated for all Columns.
    ' The delimineter is the ";" character
    strTemp = m_Control.ColumnWidths
    intTemp = Split(strWidthArray(), strTemp, ";")

    ' If only one entry then we must redim the array
    ' to hold values for all columns and copy this
    ' value into each element of the array.
    If intTemp = 0 Then
        ReDim Preserve strWidthArray(m_Control.ColumnCount - 1)
        For lngTemp = 1 To UBound(strWidthArray)
            strWidthArray(lngTemp) = strWidthArray(0)
        Next
    End If

    ' Build cumulative ColumnWidth positions
    ' Size sngWidthArray to match strWidthArray
    ReDim sngWidthArray(UBound(strWidthArray))

    For lngTemp = 0 To UBound(strWidthArray)
        For ctr = 0 To lngTemp
            sngWidthArray(lngTemp) = sngWidthArray(lngTemp) + CSng(strWidthArray(ctr))
        Next ctr

    Next lngTemp

End Sub

Public Sub AutoSize()
' Junk vars
    Dim lngRet As Long
    Dim ctr    As Long
    Dim strTemp As String
    Dim lngWidth As Long
    ' Temp array to hold calculated Column Width
    Dim lngArray() As Long
    ' Temp array to hold calculated Column Widths
    Dim strArray() As String

    ReDim lngArray(UBound(sngWidthArray))
    ReDim strArray(UBound(sngWidthArray))

    For ctr = 0 To m_Control.ColumnCount - 1
        lngArray(ctr) = GetColumnMaxWidth(m_Control, ctr) + m_ColumnMargin
    Next ctr

    ' Build the ColumnWidths property
    For ctr = 0 To UBound(lngArray)

        ' Init var
        lngWidth = lngArray(ctr)
 
        If ctr <> UBound(strArray) Then
            strArray(ctr) = lngWidth & ";"
        Else
            strArray(ctr) = lngWidth
        End If

    Next ctr

    ' Build ColumnWidths property
    strTemp = ""

    For ctr = 0 To UBound(strArray)
        strTemp = strTemp & strArray(ctr)
    Next

    ' Update the property
    m_Control.ColumnWidths = strTemp

End Sub
 
Private Function UpdateColumnWidthProp()
' Build a new ColumnWidth property from our
' array of singles.
    Dim strTemp As String
    Dim lngTemp As Long
    Dim sngTemp As Single
    Dim ctr    As Long
    Dim blBusy As Boolean

    On Error Resume Next

    If blBusy = True Then Exit Function

    blBusy = True

    ' Build the ColumnWidths property
    For lngTemp = UBound(sngWidthArray) To 0 Step -1

        ' Init var
        sngTemp = sngWidthArray(lngTemp)

        If lngTemp > 0 Then sngTemp = sngTemp - sngWidthArray(lngTemp - 1)
 
        If lngTemp <> UBound(strWidthArray) Then
            strWidthArray(lngTemp) = sngTemp & ";"
        Else
            strWidthArray(lngTemp) = sngTemp
        End If

    Next lngTemp

    ' Build ColumnWidths property
    strTemp = ""

    For lngTemp = 0 To UBound(strWidthArray)
        strTemp = strTemp & strWidthArray(lngTemp)
    Next

    lngTemp = StrComp(strTemp, m_Control.ColumnWidths, 0)

    ' Only update if there is a change from the current settings
    If lngTemp <> 0 Then m_Control.ColumnWidths = strTemp

    ' Clear our Busy Flag
    blBusy = False

End Function

Private Function Split(ArrayReturn() As String, ByVal StringToSplit As String, _
                       SplitAt As String) As Integer
' Copyright Terry Kreft
    Dim intInstr As Integer
    Dim intCount As Integer
    Dim strTemp As String

    intCount = -1

    intInstr = InStr(StringToSplit, SplitAt)

    Do While intInstr > 0
        intCount = intCount + 1
        ReDim Preserve ArrayReturn(0 To intCount)
        ArrayReturn(intCount) = Left(StringToSplit, intInstr - 1)
        StringToSplit = Mid(StringToSplit, intInstr + 1)
        intInstr = InStr(StringToSplit, SplitAt)
    Loop

    If Len(StringToSplit) > 0 Then
        intCount = intCount + 1
        ReDim Preserve ArrayReturn(0 To intCount)
        ArrayReturn(intCount) = StringToSplit
    End If

    Split = intCount

End Function
 
Private Function StringToTwips(ctl As Control, strText As String) As Long
    Dim myfont As LOGFONT
    Dim stfSize As Size
    Dim lngLength As Long
    Dim lngRet As Long
    Dim hDC    As Long
    Dim lngscreenXdpi As Long
    Dim fontsize As Long
    Dim hfont As Long, prevhfont As Long
 
    ' Get Desktop's Device Context
    hDC = apiGetDC(0&)
 
    'Get Current Screen Twips per Pixel
    lngscreenXdpi = GetDPI()
 
    ' Build our LogFont structure.
    ' This is required to create a font matching
    ' the font selected into the Control we are passed
    ' to the main function.

    'Copy font stuff from Text Control's property sheet
    With myfont
        .lfFaceName = ctl.FontName & Chr$(0)    'Terminate with Null
        fontsize = ctl.fontsize
        .lfWeight = ctl.FontWeight
        .lfItalic = ctl.FontItalic
        .lfUnderline = ctl.FontUnderline
        ' Must be a negative figure for height or system will return
        ' closest match on character cell not glyph
        .lfHeight = (fontsize / 72) * -lngscreenXdpi
    End With
 
    ' Create our Font
    hfont = apiCreateFontIndirect(myfont)

    ' Select our Font into the Device Context
    prevhfont = apiSelectObject(hDC, hfont)
 
    ' Let's get length and height of output string
    lngLength = Len(strText)
    lngRet = apiGetTextExtentPoint32(hDC, strText, lngLength, stfSize)
 
    ' Select original Font back into DC
    hfont = apiSelectObject(hDC, prevhfont)
 
    ' Delete Font we created
    lngRet = apiDeleteObject(hfont)
 
    ' Release the DC
    lngRet = apiReleaseDC(0&, hDC)
 
    ' Return the Height of the String in Twips
    StringToTwips = stfSize.cy * (1440 / GetDPI())
End Function
 
Private Function GetDPI() As Integer
' Determine how many Twips make up 1 Pixel
' based on current screen resolution
    Dim lngIC  As Long

    lngIC = apiCreateIC("DISPLAY", vbNullString, _
                        vbNullString, vbNullString)
 
    ' If the call to CreateIC didn't fail, then get the info.
    If lngIC <> 0 Then
        GetDPI = GetDeviceCaps(lngIC, LOGPIXELSX)
        ' Release the information context.
        apiDeleteDC lngIC
    Else
        ' Something has gone wrong. Assume a standard value.
        GetDPI = 96
    End If
End Function
 
Private Function GetColumnMaxWidth(ctl As Control, col As Long) As Long
' Loop through passed Column and calculate the
' width of the largest string for all rows of this column.
' Junk var
    Dim lngRet As Long
    ' Create our Font
    Dim myfont As LOGFONT
    Dim lngscreenXdpi As Long
    Dim fontsize As Long
    Dim hfont As Long, prevhfont As Long
    Dim hDC    As Long
    Dim hDC2   As Long
    ' Calc size of the string
    Dim strText As String
    Dim lngLength As Long
    Dim stfSize As Size
    ' Loop through the rows of the ctl
    Dim ctr    As Long
    Dim MaxWidth As Long
 
    ' Get Desktop's Device Context
    hDC2 = apiGetDC(0&)

    ' Create a compatible DC
    hDC = CreateCompatibleDC(hDC2)
 
    ' Release the handle to the Desktop DC
    lngRet = apiReleaseDC(0&, hDC2)
 
    'Get Current Screen Twips per Pixel
    lngscreenXdpi = GetDPI()
 
 
    ' Build our LogFont structure.
    ' This is required to create a font matching
    ' the font selected into the Control we are passed
    ' to the main function.

    'Copy font stuff from Control's property sheet
    With myfont
        .lfFaceName = ctl.FontName & Chr$(0)    'Terminate with Null
        fontsize = ctl.fontsize
        .lfWeight = ctl.FontWeight
        .lfItalic = ctl.FontItalic
        .lfUnderline = ctl.FontUnderline
        ' Must be a negative figure for height or system will return
        ' closest match on character cell not glyph
        .lfHeight = (fontsize / 72) * -lngscreenXdpi
    End With
 
    ' Create our Font
    hfont = apiCreateFontIndirect(myfont)

    ' Select our Font into the Device Context
    prevhfont = apiSelectObject(hDC, hfont)
 
    ' Loop through all of the rows in the ListBox
    ' for the given Column(col) and row(ctr)

    ' Reset our max width var
    MaxWidth = 0
 
    For ctr = 0 To ctl.ListCount - 1

        strText = ctl.Column(col, ctr)
 
        ' Let's get the width of output string
        lngLength = Len(strText)
        lngRet = apiGetTextExtentPoint32(hDC, strText, lngLength, stfSize)
 
        ' Now compare with last result and save larger value
        If stfSize.cx > MaxWidth Then MaxWidth = stfSize.cx
    Next ctr
 
    ' Select original Font back into DC
    hfont = apiSelectObject(hDC, prevhfont)
 
    ' Delete Font we created
    lngRet = apiDeleteObject(hfont)
 
    ' Release the DC
    lngRet = apiDeleteDC(hDC)
 
    ' Return the Height of the String in Twips
    GetColumnMaxWidth = MaxWidth * (1440 / GetDPI())
End Function
 
Public Property Let ColumnMargin(m As Long)
' This is TWIPS
    m_ColumnMargin = m
End Property

Public Property Get ColumnMargin() As Long
    ColumnMargin = m_ColumnMargin
End Property
 
Private Sub Class_Terminate()
' Release our reference
    Set m_Control = Nothing
End Sub

Private Sub Class_Initialize()
' Add a couple of pixels to allow
' for a margin at column edges
    m_ColumnMargin = (TWIPSPERINCH / GetDPI()) * 6
End Sub
 
Last edited by a moderator:
Please use code tags and indenting when posting code (ESPECIALLY long code like this). This is just simply a pain in the backside to try to read like this. I won't even try.

codetag001.png



As for the error - Listboxes do not have unlimited columns. You have reached the limit, I would say.
 
sorry about the formating. I have fixed it.


I think I read somewhere that the limit is 64 columns with 63 being able to display.

My largest query returns 60 columns....
 
Update:

I have discovered that the number column count varaible effects the performance of this module.

To get an acurate column count I have added code to simply count the number of columns in a selected query and populate that columncount property.

This is working good except now it seems I am running into problems with queries that return more than 52 columns....

Edit:

Oh I am getting the error:

Run-time error 2176:
The setting for the property is too long.
 
Last edited:
Ok I think I have found the issue.

Take a look at this code (If anyone is still reading this LOL)

Code:
Public Sub AutoSize()
' Junk vars
Dim lngRet As Long
Dim ctr As Long
Dim strTemp As String
Dim lngWidth As Long
' Temp array to hold calculated Column Width
Dim lngArray() As Long
' Temp array to hold calculated Column Widths
Dim strArray() As String
ReDim lngArray(UBound(sngWidthArray))
ReDim strArray(UBound(sngWidthArray))
For ctr = 0 To m_Control.ColumnCount - 1
    lngArray(ctr) = GetColumnMaxWidth(m_Control, ctr) + m_ColumnMargin
Next ctr
' Build the ColumnWidths property
For ctr = 0 To UBound(lngArray)
    ' Init var
    lngWidth = lngArray(ctr)
        
    If ctr <> UBound(strArray) Then
        strArray(ctr) = lngWidth & ";"
    Else
        strArray(ctr) = lngWidth
    End If
Next ctr
' Build ColumnWidths property
strTemp = ""
For ctr = 0 To UBound(strArray)
strTemp = strTemp & strArray(ctr)
Next
' Update the property
m_Control.ColumnWidths = strTemp
End Sub

The variable strTemp is being populated with the individual widths for the columns. I think when I try a query with over 52 columns this strings gets to long.

One of the strings it stores looks like this:

1140;1455;2910;2355;645;345;1065;2310;1410;795;1875............etc etc

My question is, why is the column widths getting set using these large numbers? Is there a way to accomplish the same thing and not use so many characters?
 
My question is, why is the column widths getting set using these large numbers?

Because the column widths are built in TWIPS in the code (there are 1440 TWIPS per inch).

Is there a way to accomplish the same thing and not use so many characters?

Don't know, but my guess would be that if Lebans coded it that way it's because it had to be coded that way. When he wrote the code, I doubt he took into consideration combo or list boxes with 50 or 60 columns (which is highly unusual).
 
Because the column widths are built in TWIPS in the code (there are 1440 TWIPS per inch).



Don't know, but my guess would be that if Lebans coded it that way it's because it had to be coded that way. When he wrote the code, I doubt he took into consideration combo or list boxes with 50 or 60 columns (which is highly unusual).


I see, believe me if the queries didnt have to return 50+ columns they wouldn't. I am just have to show alot of data from different tables in one query.

Most of the queries are at most 30 columns, just this in particular is a monster.

perhaps if I divide the twip numbers by 1440 so they will return as inces.. maybe that would work?
 
I see, believe me if the queries didnt have to return 50+ columns they wouldn't. I am just have to show alot of data from different tables in one query.
How is the query going to be used? There may be alternatives, depending on what the actual end point is intended.

As for TWIPS, I will frequently set up a constant of

Const TW As Integer = 1440

and then I just use this in the places I need -

Something.Width = 4.3 * TW
 
How is the query going to be used? There may be alternatives, depending on what the actual end point is intended.

As for TWIPS, I will frequently set up a constant of

Const TW As Integer = 1440

and then I just use this in the places I need -

Something.Width = 4.3 * TW


The query is returning engineering design data. This data is stored in different tables ( about 8 or 9) and is used by other applications.

These queries are a quick way for the design team (about 300+ people) to access an 'overview' of the design data.

I have created about 10 differnt queries for this task, most of them are only about 20 - 30 columns max which isnt a problem for this module.

This one I am having trouble with is sitting at 57 columns. it is just a big query.

The problem really isnt the module but that the string its feeding the listbox.columnwidths control is to long. It hits the limit at 52 columns.

I have tried dividing the number by 1440 and adding a inch symbol on it

Round(lngWidth / 1440, 2) & Chr$(34) & ";"

but it is still to long.
 
You know, if you focused less on creating a huge query and instead focused on creating a REPORT. You could use a series of subreports to pull together this information with a lot less trouble than you're going through now.

As for them getting an overview, I question how that many columns can provide a meaningful overview as someone would have to scroll and scroll to find stuff. And so you may just have to come to the realization that you've bit off more than Access can chew. You will need to use a different method in order to provide smaller, yet more meaningful, chunks. And if they don't like that, then you'll have to look at something else anyway because I believe you have taxed the limits of what Access can do.
 
Ok, I found a way around it.

I have split up the large query, added a new table and pasted the new SQL strings in the table. I named the sql string the name of the original quesry and added a 1 or a 2 at the end.

What the form does now is check to see if the a query is over 50 columns.

If it is (like the one I have been struggling with) it then goes to another function that goes to the table I created and pulls the first SQL string and applies it to my list box.

It then adds a "Next" button on the form.

When the button is pressed it swaps out the first SQL string with the second Allowing the viewing of the extra fields. The button also hides itself and makes a Previous button visible (you can figure out what that one does).

Also If a another query gets beyond 50 columns and isn't split up the form displays a message stating that the query is to large and they need to contact a admin.

The neat thing is, as Long as the split query retains the same name as the original, the form will find it automatically.
 

Users who are viewing this thread

Back
Top Bottom