A question that seems to come up about once a week in the newsgroup goes something like "I checked the horizontal scroll style in my ListBox, and I don't see any scrollbar. What can I do?"
Horizontal scrolling is poorly understood and even more poorly documented. In order for horizontal scrolling to work, you have to call the SetHorizontalExtent
method and set the total width of the horizontal space being used by the entries in the ListBox. If the horizontal extent is larger than the client area of the listbox, the horizontal scrollbar will appear, providing you have selected the horizontal scroll style. You need both to be set to get the effect.
Unfortunately, this is hard to do in the parent window. It involves having to do the computation at every site where you add a string. This is not well object-oriented. So what I've done is create a new class derived from CListBox
that incorporates this functionality automatically.
The way I do this is maintain a value which is the maximum width set thus far. Whenever a new string is added, I update the width. When strings are deleted I update the width. I do this by overriding the ResetContent
, InsertItem
, AddItem
, and DeleteString
methods.
Note that this works only for non-owner-drawn list boxes. For owner-drawn, it is somewhat easier because you can maintain it in the DrawItem
handler.
You can download the sample code, but here's some excerpts. The sample code includes a complete project which demonstrates the scrolling.
Note that to include this class in your project, you need to include the source file in your project, then delete the .clw file and re-invoke the ClassWizard to get it to see the new class (Microsoft used to allow the importation of classes directly, but this feature seems to have been deleted in the latest versions of Visual Studio). You can then create control variables using the CHListBox
class directly in ClassWizard. If you don't do the rebuild of the .clw file, you will have to hand-edit your header file. If you don't know how to do this, check out my essay on Avoiding GetDlgItem.
Constructor
We need to initialize the width variable in the constructor:
CHListBox::CHListBox()
{
width = 0;
}
AddString and InsertString
In the AddString
and InsertString
handlers, we call a common function to update the width:
int CHListBox::AddString(LPCTSTR s)
{
int result = CListBox::AddString(s);
if(result < 0)
return result;
updateWidth(s);
return result;
}
int CHListBox::InsertString(int i, LPCTSTR s)
{
int result = CListBox::InsertString(i, s);
if(result < 0)
return result;
updateWidth(s);
return result;
}
The updateWidth
function is defined as
void CHListBox::updateWidth(LPCTSTR s)
{
CClientDC dc(this);
CFont * f = CListBox::GetFont();
dc.SelectObject(f);
CSize sz = dc.GetTextExtent(s, _tcslen(s));
sz.cx += 3 * ::GetSystemMetrics(SM_CXBORDER);
if(sz.cx > width)
{
width = sz.cx;
CListBox::SetHorizontalExtent(width);
}
}
The reason we add the 3*SM_CXBORDER
factor is because we need to allow a bit of additional space to account for the (undocumented and inaccessible) margin that is used to draw the characters. This fudge factor appears to give the best result. To get the correct computation, we have to select the font that is set in the control into the DC.
ResetContent
The ResetContent
method is trivial:
void CHListBox::ResetContent()
{
CListBox::ResetContent();
width = 0;
}
DeleteString
The DeleteString
operation is expensive because we don't know if we have deleted the widest string. Consequently, we have to evaluate all the strings all over again. Since this can be a bit expensive if we keep calling updateWidth
, so the functionality of DC creation and font selection have been moved back into the DeleteString
. This could probably be expedited with some inline
functions but the code is not very complex, so there seems to be little reason to not duplicated it.
int CHListBox::DeleteString(int n)
{
int result = CListBox::DeleteString(n);
if(result < 0)
return result;
CClientDC dc(this);
CFont * f = CListBox::GetFont();
dc.SelectObject(f);
width = 0;
for(int i = 0; i < CListBox::GetCount(); i++)
{
CString s;
CListBox::GetText(i, s);
CSize sz = dc.GetTextExtent(s);
sz.cx += 3 * ::GetSystemMetrics(SM_CXBORDER);
if(sz.cx > width)
width = sz.cx;
}
CListBox::SetHorizontalExtent(width);
return result;
}
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.
Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright � 1999 CompanyLongName All Rights Reserved.
www.flounder.com/mvp_tips.htm