Click here to Skip to main content
Licence 
First Posted 27 Mar 2001
Views 114,528
Bookmarked 36 times

A CListBox with automatic HSCROLL maintenance

By | 27 Jun 2001 | Article
Taking the pain out of adding a horizontal scrollbar to a listbox.

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)
	 { /* extend */
	  width = sz.cx;
	  CListBox::SetHorizontalExtent(width);
	 } /* extend */
    }

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++)
	 { /* scan strings */
	  CString s;
	  CListBox::GetText(i, s);
	  CSize sz = dc.GetTextExtent(s);
          sz.cx += 3 * ::GetSystemMetrics(SM_CXBORDER);
	  if(sz.cx > width)
	      width = sz.cx;
	 } /* scan strings */
     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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Joseph M. Newcomer



United States United States

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralA little problem with owner drawn Pinmember_kane_1:47 16 Nov '06  
GeneralRe: A little problem with owner drawn Pinmember_Bids2:00 13 May '07  
GeneralRe: A little problem with owner drawn Pinmember_kane_3:24 13 May '07  
GeneralRe: A little problem with owner drawn Pinmember_Bids6:57 15 May '07  
GeneralRe: A little problem with owner drawn Pinmember_Bids10:48 16 May '07  
GeneralSource with Windows-API functions [modified] PinmemberPeter Wucherer2:26 26 Jul '06  
GeneralInteresting PinmemberK(arl)4:18 7 Mar '06  
GeneralRe: Interesting PinmemberJoseph M. Newcomer5:54 7 Mar '06  
GeneralFlame wars PinmemberK(arl)6:24 7 Mar '06  
GeneralRe: Flame wars PinmemberJoseph M. Newcomer8:16 7 Mar '06  
I find the m_ notation to be about as silly. You're right, it is a matter of taste. If you can't see the local variables nearby, the function is probably too long anyway. The use of m_ leads to even sillier conventions like insisting that global variables be notated as g_ and local variables as l_. You would probably not believe the number of times I see people writing
 
void SomeFunction(int m_thing, LPCTSTR g_other)
 
and the excuse they give is that "Well, I had written the code to use m_thing and g_other, and then I decided to just pass them as parameters". At least by avoiding the conventions entirely, I force the programmer to ***actually look at the code*** instead of making possibly invalid assumptions.
 
Efficiency is hardly an issue for SaveDC/RestoreDC. You are about to execute a couple hundred thousand instructions, or have just finished, and the cost of SaveDC/RestoreDC is minimal by comparison. Also, for those who try to play the "efficiency" card, remember that the biggest overhead is the kernel call itself; one SaveDC/RestoreDC pair compared with a large number of SelectObject(old_whatever) becomes a debatable point. I get very, very suspicious of people who play the efficiency card at the sacrifice of code clarity (there are over 30 parameters to a DC that must be saved; I've seen even simple graphics code use dozens of variables to hold intermediate state, and the inevitable confusion ensues. So I avoid it all)
 
I have not hit a limit on the SaveDC/RestoreDC stack size; no stack size limit is documented, and I've been nested fairly deeply. Perhaps I'll write a little test program to see when the DC stack overflows...but unless you have a documented llimit, claiming this is a limited resource is difficult to justify.
 
When there are thousands of calls in a tight loop, and the call represents a significant cost of the loop, it is worth worrying about. The rest of the time, write simple code. The older I get, the more I appreciate the value of really simple code. Having spent 15 years doing performance analysis (I wrote the performance analysis tool and spent a lot of time with a lot of people trying to optimize their code), I've discovered that program architecture matters, and lines of code rarely matter. In the absence of evidence of performance problems, "optimizing" them away just wastes time and energy. Once you've profiled your program, and know EXACTLY where the time is going, is the time to start worrying about how to make the inner loops faster.
 
I've seen inner loops that gained four orders of magnitude performance improvement by simply avoiding using the storage allocator, and those which lost six orders of magnitude performance by ignoring page faults as a concept. Both were found by getting actual data. Interestingly enough, in the first case, the programmer was "optimizing" the wrong part of the loop; it was small, tight, unintelligible, and irrelevant. Once the allocator bottleneck was removed, the "optimizations" accounted for only a 10% improvement in the loop, which was an unmeasuable percentage of the program execution. In the second case, the programmer had worked for weeks to get the code as small, fast, and bizarrely convoluted as possible, and then walked over a couple megabytes of data on a 512K machine supporting multiple users. We threw the "optimized" code out, used the "unoptimized" computation but worked on the data a couple pages at a time, and dropped the computation from hours to under a minute. Also, check out my essay "Optimization--your worst enemy". By focusing on lines of code, major architectural issues are often ignored. Only a profiler can tell you the real truth.
GeneralRe: Flame wars PinmemberK(arl)21:42 8 Mar '06  
GeneralRe: Flame wars PinmemberJoseph M. Newcomer22:15 8 Mar '06  
GeneralRe: Flame wars PinmemberK(arl)0:18 9 Mar '06  
GeneralThanks from me too. PinmemberRancidCrabtree6:50 24 Nov '05  
GeneralGreat ! PinmemberRodrigo Pinho Pereira de Souza10:00 14 Oct '04  
Generalwhy using "width" param PinmemberAnonymous1:02 10 Aug '01  
GeneralMSDN info on this subject PinmemberRick York14:03 1 Jul '01  
QuestionLinker Errors? PinmemberHenry Seeto8:06 14 Jun '01  
AnswerRe: Linker Errors? PinmemberHenry Seeto8:54 14 Jun '01  
GeneralYes but.... PinmemberAnonymous20:45 2 May '01  
GeneralRe: Yes but.... PinmemberDnicholson12:16 3 Mar '04  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120529.1 | Last Updated 28 Jun 2001
Article Copyright 2001 by Joseph M. Newcomer
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid