Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

VCOMBOBX in MFC - A Virtual Combo Box Control

0.00/5 (No votes)
4 Jan 2011 1  
A MFC based virtual combo box

Source Layout

  • code_base
    • VComboBox.cpp
    • VComboBox.h
    • CustomDrawCommon.cpp
    • CustomDrawCommon.h
    • CustomDrawControl.cpp
    • CustomDrawControl.h
    • CustomDrawUtils.cpp
    • CustomDrawUtils.h
    • ControlAnchor.cpp
    • ControlAnchor.h
    • FileEnumerator.cpp
    • FileEnumerator.h
    • ObjInfoControl.cpp
    • ObjInfoControl.h
    • ObjInfoHolder.cpp
    • ObjInfoHolder.h
  • CustomControlDemo
    • Source files of CustomControlDemo
  • CVComboDemo
    • Source files of CVComboDemo

You should extract the source files to your computer as the same structure as listed above!

VCOMBO.gif

Preface

Before we start, we may first need to understand the concept of "virtual". A good place to start is virtual list control, if you are already familiar with it, you may skip to the Background part of this article. So let's see what a virtual list control is.

Introduction to Virtual List Control

In Windows, we can create a list control with the style LVS_OWNERDATA (which means the list control itself does not keep the data but instead it is the owner/parent window that keeps them) and handle the LVN_GETDISPINFO notification, that's so-called virtual list control.

A virtual list control is useful when we want to show numerous number of items on it, what's more, "adding/deleting" items of a virtual list control is fast (because a virtual list control does not actually keep any data, so it does not need to add or delete something, it is our job to tell it how many items should be shown on it - by calling SetItemCount(), and it will then ask us back - with the LVN_GETDISPINFO notification sent to the parent control - for the information to show), you may consider that as a callback.

The Reason of Using Virtual List Control

  1. According to MSDN, a list control has the default item count only extends to an int, and sometimes we may want to exceed that limitation.
  2. The list control must be populated before it starts showing anything, and once we need to add large number of items on it, that will be painfully slow which is unbearable.
  3. We may want to give some visual feedback dynamically in response to the user input, e.g. filter out some items on the list control while the user types the search field, you can imagine this as a Google Instant. This requires adding and deleting the items frequently, which as we know is also unacceptably slow.

With the help of virtual list control, all the above problems can be solved:

  1. A virtual list control supports an item count up to a DWORD.
  2. A virtual list control does not have any data, we don't even need to populate the items.
  3. As I have mentioned in the previous part, changing the item count can be done simply by calling SetItemCount(), we don't need to add or delete anything.

If you want to learn more about virtual list control, you may refer to:

As a demonstration of using virtual list control, here's the snapshot from my other article's demo application:

QOF_SnapShot.gif

As you see, a virtual list control is cool, however, what if we want to do the same thing for a combo box? Can we make the list box part within a combo box become virtual? Is that even possible? Sure, that's possible, see the address bar of your web browser and type something.

Background

As we know, the combo box, in fact, is composed of a list box control and (optionally, when it's not CBS_SIMPLE) an edit control. Like the LVS_OWNERDATA style for a list control, the list box control can also become virtual if we create it with the style LBS_NODATA, see the description on MSDN for it.

Specifies a no-data list box. Specify this style when the count of items in the list box will exceed one thousand. A no-data list box must also have the LBS_OWNERDRAWFIXED style, but must not have the LBS_SORT or LBS_HASSTRINGS style.

How a Virtual List Box Works

First, we to create a class like CVListBox that derived from CListBox, to create an instance of it, we need to call CWnd::Create(LBS_NODATA|LBS_OWNERDRAWFIXED, ...).

Override the method DrawItem()/MeasureItem()/CompareItem() like this:

void CVListBox::DrawItem( LPDRAWITEMSTRUCT lpDIS )
{
 if ( (int)lpDIS->itemID < 0 )
  return;

 CDC* pDC = CDC::FromHandle(lpDIS->hDC);
 int nSavedDC = pDC->SaveDC();
 
 CString strText = GetItemText(lpDIS->itemID); // retrieve the text to show for this item
 CRect rectItem(lpDIS->rcItem);
 pDC->DrawText(strText, rectItem, DT_SINGLELINE);
 
 pDC->RestoreDC(nSavedDC);
}

void CVListBox::MeasureItem( LPMEASUREITEMSTRUCT  )
{
 // nothing to do
}

int CVListBox::CompareItem( LPCOMPAREITEMSTRUCT  )
{
 return 0;
}

CString CVListBox::GetItemText( UINT nItem )
{
 CString str;
 // Here you can send a custom message (like WM_USER+0x100) to the parent window 
 // to ask/fetch the content for the specified item, that's like simulating the 
 // LVN_GETDISPINFO notification.
 // Of course you can also implement this in your own way.
 return str;
}

The other thing is like a virtual list control, when you want to update the list box, you just simply try this:

SendMessage(myListbox.m_hWnd, LB_SETCOUNT, nCount, 0)

That looks kind of easy, huh? You might as well have thought that we can create the combo box and then try GetComboBoxInfo() and ModifyStyle() stuff, but unfortunately that's a wrong way to go! As some of us might have experienced, it's not true that every style can be set/removed dynamically, some style like LVS_OWNERDRAWFIXED/LBS_OWNERDRAWFIXED can only be specified during the creation of the control, the style LBS_NODATA is also in such case, and we cannot change the style of the list box within a combo box during the creation, so that seems like there is no way to change the style of the list box to make it virtual.

Solution

The answer is at first simple: we have to create the combo box ourselves from scratch! This means we need to create the edit control and the listbox and manage to make them cooperate together, in other words, we need to handle all kind of messages/notification to respond the user's input, that's like reinventing the wheel!

Mathias Tunared had post his/her article AdvComboBox on CodeProject which kind of fit my requirements, however, I found that his/her implementation does not take advantage of virtual list box.

The good news is that Microsoft has provided a sample code called VCOMBOBX for this! That article is published in 2007, then I searched over the web but couldn't find a MFC version of that (maybe I missed), so I decided to do the job.

Using the Code

Using a virtual combo box is just like using a virtual list box, if you are familiar with the virtual list control you will find that easy to use.

Understanding the Class CVComboBox

Basically the class CVComboBox is the core of this sample, you can locate it in the source file VComboBox.h/cpp.

CVComboBox is a MFC wrapper class that encapsulates a custom combo box control composed with a virtual list box control, the window class name of it is Virtual_ComboBox32.

CVComboBox supports almost all the styles that a normal CComboBox can have, but with a different macro (although the values of them are the same), listed as follows:

#define VCBS_SIMPLE    0x0001L
#define VCBS_DROPDOWN   0x0002L
#define VCBS_DROPDOWNLIST  0x0003L
#define VCBS_AUTOHSCROLL  0x0010L
#define VCBS_OEMCONVERT   0x0020L
#define VCBS_DISABLENOSCROLL 0x0040L
#define VCBS_UPPERCASE   0x0100L
#define VCBS_LOWERCASE   0x0200L

#define VCBS_DEFAULT    (WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_CLIPSIBLINGS | \
          WS_CLIPCHILDREN | VCBS_AUTOHSCROLL)

#define VCBS_DEFAULT_SIMPLE   (VCBS_DEFAULT | VCBS_SIMPLE)
#define VCBS_DEFAULT_DROPDOWN  (VCBS_DEFAULT | VCBS_DROPDOWN)
#define VCBS_DEFAULT_DROPDOWNLIST (VCBS_DEFAULT | VCBS_DROPDOWNLIST)

CVComboBox provides almost all the methods that a CComboBox have, except that it also provides the following methods in addition:

virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);

// Create a virtual combo box to replaced the control with the specified ID, 
// the newly created combo box will be placed at the same position as that control,
// after that, the control with the specified ID will be destroyed.
virtual BOOL CreateFromCtrl
	(CWnd* pParent, int nID, DWORD dwStyle = VCBS_DEFAULT_DROPDOWN); 
 
int    GetItemCount();
void   SetItemCount(int nCount);
void   SetDroppedVisibleItemCount(int nCount, BOOL bRepaint = TRUE);

virtual CVComboListBox&  GetVComboListBox();
virtual CComboListBox&  GetComboListBox();
virtual CVComboEdit&  GetComboEdit();

These methods are rather straightforward, I guess you can already understand what they are used for, here I will just give a simple description about the class CVComboListBox and CVComboEdit

CVComboListBox is a class that encapsulate the virtual list box control, it derives from CComboListBox which derives from CListBox.

CVComboEdit is just a class that derives from CEdit.

Since the above classes are encapsulated inside the class CVComboBox, and in most cases, we don't need to care about them, so now let's see how to use the class CVComboBox.

The Basic Steps to Use CVComboBox in a Dialog Based Application

Here are the basic steps to show how to use the class CVComboBox:

  1. Create a dialog based MFC project called CVComboDemo.
  2. Add the following source files (under the folder code_base) into your workspace:
    • CustomDrawCommon.cpp
    • CustomDrawCommon.h
    • CustomDrawControl.cpp
    • CustomDrawControl.h
    • CustomDrawUtils.cpp
    • CustomDrawUtils.h
    • VComboBox.cpp
    • VComboBox.h
  3. Open the precompiled header file, which is normally StdAfx.h, append this line to it:
    #include "..\code_base\CustomDrawCommon.h"
  4. If you are using Visual Studio 6.0, you also need to put these at the beginning of StdAfx.h:
    #pragma warning(disable: 4786)  // try to disable the annoying warning in VC6
    
    #ifndef WINVER
     #define WINVER 0x0501
    #endif // WINVER
    
    #ifndef _WIN32_WINNT
     #define _WIN32_WINNT 0x0501
    #endif // _WIN32_WINNT
  5. Declare your own class derived from CVComboBox and override the necessary methods, normally it would be GetItemText(), e.g. like this one:
    #include "..\code_base\VComboBox.h"
    
    class CMyVComboBox : public CVComboBox
    {
    public:
     virtual CString GetItemText(UINT nItem)
     {
      CString strText;
      strText.Format(_T("Item %d"), nItem);
      return strText;
     }
    };
  6. Open the dialog with resource editor, drag and drop a custom control from the control toolbar, fill the properties as shown below:
    CVComboDemo.PNG
  7. Drag and drop a combo box control onto your dialog, set the id of it to IDC_VCOMBO2.
  8. Add two member variables to your dialog class like this, and of course you need to put the #include directive to include necessary header files where you declare the class CMyVComboBox:
    CMyVComboBox  m_vComboBox1;
    CMyVComboBox  m_vComboBox2;
  9. Add the line in bold below to your dialog class's DoDataExchange() method:
    void CCVComboDemoDlg::DoDataExchange(CDataExchange* pDX)
    {
     CDialog::DoDataExchange(pDX);
     DDX_Control(pDX, IDC_VCOMBO1, m_vComboBox1);
    }
  10. Put this code in your OnInitDialog():
    m_vComboBox1.SetItemCount(50);
    m_vComboBox1.SetDroppedVisibleItemCount(10);
    
    m_vComboBox2.CreateFromCtrl(this, IDC_VCOMBO2);
    
    m_vComboBox2.SetItemCount(10);
    m_vComboBox2.SetDroppedVisibleItemCount(5);
  11. Compile and run the app!

Points of Interest

At first, I tried to use a WH_CBT hook to intercept the creation of the list box and then modify its style, however, this solution seems to fail if Common Control version 6 is enabled (when a manifest file is used), that's the reason that I seek for a virtual list box implementation.

As I was writing code for combo box, I found that custom drawing the control is kind of fun, and I wonder how do I implement the fancy looking control in Vista/Win7 and bring them into former OS like WinXP, so I also wrote some other control classes that implemented custom draw.

You may find some interesting/useful control classes in the demo project CustomControlDemo, as listed here:

  • CCustomDrawHeaderCtrl
  • CSortHeaderCtrl
  • CCustomDrawToolTipCtrl
  • CCustomDrawListCtrl
  • CCustomDrawListBox
  • CCustomDrawTreeCtrl
  • CTriCheckStateTreeCtrl
  • CCustomDrawComboBox

These classes have some ability to represent/simulate the fancy Win7 appearance under WinXP and they are VC6/VS2010 compatible.

For some reasons, I have no intention to introduce them in detail or post another article for them, however, you can still explore the source code yourself and you are free to use/modify them in whatever way you like.

Here's a snapshot of the demo application, hope you can have fun playing with it:

CustomControlDemo.png

History

  • 11/25/2010: Initial release
  • 12/29/2010: Improve the drawing code, some minor bugs were fixed

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