Virtual Developer Workshop: Containerized Development with Docker

Environment: Visual Studio, VC++ 6.0, MFC, W9x/Me, W2k, XP


CListBox: This is a wrapper class for the ListBox control, and it's used in almost every application. There is a little "gotcha" to this class: The horizontal scroll bar doesn't work. Okay, I created it with the WS_HSCROLL flag set, the scroll bar is visible, and strings that I'm adding to the box are obviously longer then the box's horizontal extent, but I'm unable to scroll. What's wrong?

My theory is that the guy who originally implemented that control had way too much Greek mythology in his childhood. Villainous Procrustes and his bed where he had to cut off travelers' legs if they didn't fit in had a huge impact on the poor guy. If something is too long, chop it off. Well, whatever the reason is, the control simply ignores the fact that the strings are longer then it can show. How can we fix it?

Well, first, we have to catch all messages sent to the control that affect the box's content, hence the strings that it has (behind such functions as AddString() or ResetContent(), is SendMessage() with LB_ADDSTRING and LB_RESETCONTENT respectively). There are five messages of that type (at least that's the number I've come with. There may be more; I'm not sure.). Once they are identified, they can be caught and modified the same way as the others:

  • LB_DIR

After the control is subclassed, we can catch any message that is sent to it through the control's message map:

  // NOTE - the ClassWizard will add and remove mapping macros here.

When we catch the message (for example, LB_ADDSTRING), we are going to call default processing first, and, if the returned code is okay (most of these messages return some kind of a completion indicator), call our functions, either SetNewHExtent() or ResetHExtent(), that will bring the horizontal extent member of ListBox (this is the parameter that affects the horizontal scroll bar, if the bar is present) in sync with the longest string that it contains:

// OnAddString: wParam - none, lParam - string, returns - int
LRESULT CHScrollListBox::OnAddString(WPARAM wParam, LPARAM lParam)
  LRESULT lResult = Default();
  if (!((lResult == LB_ERR) || (lResult == LB_ERRSPACE)))
    SetNewHExtent((LPCTSTR) lParam);
  return lResult;

SetNewHExtent() in turn will calculate in pixels the length of the supplied string in the given device context (GetTextLen()) and set a new horizontal extent for the control. The extent can be set through CListBox::SetHorizontalExtent(extent) and we do it only if it's bigger then the current extent:

void CHScrollListBox::SetNewHExtent(LPCTSTR lpszNewString)
  int iExt = GetTextLen(lpszNewString);
  if (iExt > GetHorizontalExtent())

Let's take a look at what GetTextLen() does:

int CHScrollListBox::GetTextLen(LPCTSTR lpszText)

  CDC *pDC = GetDC();

  CSize size;
  CFont* pOldFont = pDC->SelectObject(GetFont());
  if ((GetStyle() & LBS_USETABSTOPS) == 0)
    size = pDC->GetTextExtent(lpszText, (int) _tcslen(lpszText));
    size.cx += 3;
    // Expand tabs as well
    size = pDC->GetTabbedTextExtent(lpszText, (int)
          _tcslen(lpszText), 0, NULL);
    size.cx += 2;

  return size.cx;

The first call is a sanity check with AfxIsValidString(). Then we get the control's DC through GetDC() and select the control's font in the context. After that, we look at the styles. If LBS_USETABSTOPS is set, we call GetTabbedTextExtent(); otherwise, it's GetTextExtent(), which returns the number of pixels that the specified string will occupy on the screen in a horizontal direction when drawn in that device context. I add a few more pixels here, just to make sure the string looks symmetrical when displayed, because the box has a margin on the left. That's it.

In calls OnDeleteString() and OnDir(), I use the function named ResetHExtent(). Because I do not know what string has the next longest extent from the one I'm deleting and since OnDir() will reset the content and fill it in with directory or file names, I'm simply going through the whole list, looking for the longest string the list has and setting its extent.

CHScrollListBox should only be used with a single column, non-ownerdrawn list box. There are a couple of ASSERTs in PreSubclassWindow() to alert you in case you are setting the flags, which the box does not know how to handle. A typical propsheet for the box will look something like the picture below:

Click here for a larger image.

If you don't need the box to display tabs, uncheck Use Tabstops. The main flags for CHScrollListBox to function properly are Horizontal Scroll, Selection->Single, Owner Draw->No.


Okay, how one can utilize CHScrollListBox class? Here are some steps to take, sort of manual labor, that will get you there:

  1. In the resource editor, create a dialog template that will host a listbox control.
  2. Set properties of the list box according to the picture above (at least, Horizontal Scroll, Selection, and Owner Draw should match).
  3. In the header file of your dialog's class, associated with the template, add:
    #include "HScrollListBox.h"
  4. Define a member variable of CHScrollListBox, like so:
    CHScrollListBox m_listBox;
  5. In the implementation file, in DoDataExchange() function, add a line:
    DDX_Control(pDX, IDC_YOUR_LISTBOX_CTRL_ID, m_listBox);
    This call in turn calls SubclassDlgItem(), which does the trick of routing all messages destined to the control to our message map.

Of course, IDC_YOUR_LISTBOX_CTRL_ID and m_listBox are orbitrary names; you should use your own instead.


Download demo project - 23 Kb
Download source - 3 Kb


  • Thanx

    Posted by Avm on 04/12/2012 07:05am

    Thank you. Very helpful.

  • Thanks

    Posted by majoob on 02/01/2007 02:02am

    Works great!

  • Fonts

    Posted by fcoarturobn on 11/06/2006 07:30pm

    First of all, thanks a lot !!
      about the fonts :
    * to change the font after add all the strings, then ResetHExtent() must be called again. (GetTextExtent/GetTabbedTextExtent depends on the font)
    * try using SetFont before add the strings (that is the ideal situation),  or maybe write your own SetFont in order to call it at any time.
    void CHScrollListBox::SetFont( CFont* pFont, BOOL bRedraw){
        CListBox::SetFont( pFont );
    Thanks !

  • XVT Library?

    Posted by rbinu on 10/26/2005 02:03am

    That is a neat job! I am using XVT Library instead of MFC. Have a clue how I can make horizontal scrollbars appear using XVT? The library doesn't seem to have functions equivalent to SetHorizontalExtent....:(

    • XVT?

      Posted by Geno Carman on 11/24/2005 10:39pm

      Use the list box messages: LB_GETHORIZONTALEXTENT, etc.


    Posted by Legacy on 02/10/2004 08:00am

    Originally posted by: Roku

    It helps alot. You are the best!

  • Very useful

    Posted by Legacy on 01/31/2004 08:00am

    Originally posted by: James Bond

    Very useful.......good work.

  • Thanks...So Cool

    Posted by Legacy on 11/12/2003 08:00am

    Originally posted by: Ashish Shah

    Thank You Very Much...
    I almost wasted whole day in this problem...but its good to get this solution before going home!!!!!

  • Thanx!!!!!!!!!!!!!

    Posted by Legacy on 10/25/2003 07:00am

    Originally posted by: Skygod

    Thanks for this fix! I was about to go really mad/crazy before I found this fine little hack!

    "Respect to the man in the ice-cream van..."

  • Works on PocketPC too!

    Posted by Legacy on 10/02/2003 07:00am

    Originally posted by: Claire

    Apparently GetTabbedTextExtent() is not supported in
    WinCE, but that was the only difference...

    Thank you!!!

  • Thank you

    Posted by Legacy on 09/20/2003 07:00am

    Originally posted by: Tatobrutto

    Thank you.
    Simple and pretty...I'd like it.

  • Loading, Please Wait ...

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date