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!
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
- 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.
- 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.
- 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:
- A virtual list control supports an item count up to a
DWORD
.
- A virtual list control does not have any data, we don't even need to populate the items.
- 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:
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); CRect rectItem(lpDIS->rcItem);
pDC->DrawText(strText, rectItem, DT_SINGLELINE);
pDC->RestoreDC(nSavedDC);
}
void CVListBox::MeasureItem( LPMEASUREITEMSTRUCT )
{
}
int CVListBox::CompareItem( LPCOMPAREITEMSTRUCT )
{
return 0;
}
CString CVListBox::GetItemText( UINT nItem )
{
CString str;
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);
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
:
- Create a dialog based MFC project called
CVComboDemo
.
- 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
- Open the precompiled header file, which is normally StdAfx.h, append this line to it:
#include "..\code_base\CustomDrawCommon.h"
- If you are using Visual Studio 6.0, you also need to put these at the beginning of StdAfx.h:
#pragma warning(disable: 4786)
#ifndef WINVER
#define WINVER 0x0501
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
- 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;
}
};
- Open the dialog with resource editor, drag and drop a custom control from the control toolbar, fill the properties as shown below:
- Drag and drop a combo box control onto your dialog, set the id of it to
IDC_VCOMBO2
.
- 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;
- 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);
}
- 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);
- 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:
History
- 11/25/2010: Initial release
- 12/29/2010: Improve the drawing code, some minor bugs were fixed