VCOMBOBX in MFC - A Virtual Combo Box Control
A MFC based virtual combo box
源代码布局
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
CustomControlDemo
的源文件
CVComboDemo
CVComboDemo
的源文件
您应该将源文件提取到您的计算机上,并保持与上面列出的相同的结构!

前言
在我们开始之前,我们可能需要先理解“虚拟”的概念。一个好的起点是虚拟列表控件,如果您已经熟悉它,可以跳过本文的背景部分。那么,让我们看看什么是虚拟列表控件。
虚拟列表控件简介
在Windows中,我们可以创建一个带有LVS_OWNERDATA
样式的列表控件(这意味着列表控件本身不保留数据,而是由所有者/父窗口保留数据),并处理LVN_GETDISPINFO
通知,这就是所谓的虚拟列表控件。
当我们要显示大量项目时,虚拟列表控件很有用,更重要的是,“添加/删除”虚拟列表控件的项目速度很快(因为虚拟列表控件实际上不保留任何数据,所以不需要添加或删除任何东西,我们只需要告诉它应该显示多少项目 - 通过调用SetItemCount()
,然后它会反过来询问我们 - 向父控件发送LVN_GETDISPINFO
通知 - 来获取要显示的信息),您可以将其视为回调。
使用虚拟列表控件的原因
- 根据MSDN,列表控件的默认项目计数仅限于
int
,有时我们可能希望超出此限制。 - 列表控件必须在开始显示任何内容之前进行填充,一旦我们需要在其上添加大量项目,这将非常缓慢且难以忍受。
- 我们可能希望动态地向用户输入做出一些视觉反馈,例如,当用户在搜索字段中键入时,筛选出列表控件中的一些项目,您可以将其想象成Google Instant。这需要频繁地添加和删除项目,正如我们所知,这也是不可接受的慢。
借助虚拟列表控件,以上所有问题都可以得到解决
- 虚拟列表控件支持高达
DWORD
的项目计数。 - 虚拟列表控件没有任何数据,我们甚至不需要填充项目。
- 正如我在上一部分提到的,更改项目计数可以通过调用
SetItemCount()
来完成,我们不需要添加或删除任何东西。
如果您想了解更多关于虚拟列表控件的信息,您可以参考
作为使用虚拟列表控件的演示,这是我其他文章的演示应用程序的快照
正如您所看到的,虚拟列表控件很棒,但是,如果我们想对组合框执行相同的操作呢?我们能否使组合框中的列表框部分虚拟化?这可能吗?当然,这是可能的,看看您的网络浏览器的地址栏,然后输入一些内容。
背景
众所周知,组合框实际上由列表框控件和一个(可选的,当它不是CBS_SIMPLE
时)编辑控件组成。就像列表控件的LVS_OWNERDATA
样式一样,如果我们使用LBS_NODATA
样式创建
列表框控件,它也可以变成虚拟的,请参阅MSDN上的描述。
指定一个无数据列表框。当列表框中的项目数超过一千时,请指定此样式。无数据列表框还必须具有LBS_OWNERDRAWFIXED
样式,但不得具有LBS_SORT
或LBS_HASSTRINGS
样式。
虚拟列表框的工作原理
首先,我们需要创建一个类似于CVListBox
的类,该类派生自CListBox
,要创建它的实例,我们需要调用CWnd::Create(LBS_NODATA|LBS_OWNERDRAWFIXED, ...)
。
像这样覆盖DrawItem()
/MeasureItem()
/CompareItem()
方法
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;
}
另一件事就像虚拟列表控件一样,当您想更新列表框时,只需尝试这样做
SendMessage(myListbox.m_hWnd, LB_SETCOUNT, nCount, 0)
这看起来有点容易,是吗?您可能还认为我们可以创建组合框,然后尝试GetComboBoxInfo()
和ModifyStyle()
等操作,但遗憾的是,这是一条错误的道路!正如我们中的一些人可能已经经历过的,并非所有样式都可以动态设置/移除,某些样式如LVS_OWNERDRAWFIXED/LBS_OWNERDRAWFIXED
只能在控件创建期间指定,LBS_NODATA
样式也在此类中,并且我们不能在创建期间更改组合框中列表框的样式,因此似乎无法更改列表框的样式以使其虚拟化。
解决方案
答案一开始很简单:我们必须自己从头开始创建组合框!这意味着我们需要创建编辑控件和listbox
并设法使它们协同工作,换句话说,我们需要处理各种消息/通知来响应用户的输入,这就像重新发明轮子一样!
Mathias Tunared在他的文章AdvComboBox中发布了他的文章,这在某种程度上符合我的要求,但是,我发现他的实现并未利用虚拟列表框。
好消息是,微软提供了一个名为VCOMBOBX的示例代码!这篇文章发布于2007年,然后我在网上搜索,但找不到它的MFC版本(也许我错过了),所以我决定自己来做。
Using the Code
使用虚拟组合框就像使用虚拟列表框一样,如果您熟悉虚拟列表控件,您会发现它很容易使用。
理解CVComboBox类
基本上,CVComboBox
类是此示例的核心,您可以在源文件VComboBox.h/cpp中找到它。
CVComboBox
是一个MFC包装类,它封装了一个自定义组合框控件,该控件由一个虚拟列表框控件组成,其窗口类名为Virtual_ComboBox32
。
CVComboBox
支持几乎所有普通CComboBox
可以拥有的样式,但使用不同的宏(尽管它们的值相同),列出如下
#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
提供了几乎所有CComboBox
的方法,只不过它还额外提供了以下方法
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();
这些方法相当直接,我想您已经能够理解它们的用途了,这里我将仅对CVComboListBox
和CVComboEdit
类进行简单描述
CVComboListBox
是一个封装虚拟列表框控件的类,它派生自CComboListBox
,而CComboListBox
又派生自CListBox
。
CVComboEdit
只是一个派生自CEdit
的类。
由于上述类被封装在CVComboBox
类中,并且在大多数情况下,我们不需要关心它们,所以现在让我们看看如何使用CVComboBox
类。
在基于对话框的应用程序中使用CVComboBox的基本步骤
以下是演示如何使用CVComboBox
类的基本步骤
- 创建一个名为
CVComboDemo
的基于对话框的MFC项目。 - 将以下源文件(在code_base文件夹下)添加到您的工作区
- CustomDrawCommon.cpp
- CustomDrawCommon.h
- CustomDrawControl.cpp
- CustomDrawControl.h
- CustomDrawUtils.cpp
- CustomDrawUtils.h
- VComboBox.cpp
- VComboBox.h
- 打开预编译头文件,通常是StdAfx.h,将此行添加到其中
#include "..\code_base\CustomDrawCommon.h"
- 如果您使用的是Visual Studio 6.0,您还需要将以下内容放在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
- 声明一个派生自
CVComboBox
的自定义类,并覆盖必要的方法,通常是GetItemText()
,例如像这样#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; } };
- 使用资源编辑器打开对话框,从控件工具栏拖放一个自定义控件,并按如下所示填写属性
- 将组合框控件拖放到您的对话框上,将其ID设置为
IDC_VCOMBO2
。 - 向您的对话框类添加两个成员变量,如下所示,当然您需要添加
#include
指令以包含声明CMyVComboBox
类的必要头文件CMyVComboBox m_vComboBox1; CMyVComboBox m_vComboBox2;
- 在您的对话框类的
DoDataExchange()
方法中添加下面粗体显示的行void CCVComboDemoDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_VCOMBO1, m_vComboBox1); }
- 将此代码放在您的
OnInitDialog()
中m_vComboBox1.SetItemCount(50); m_vComboBox1.SetDroppedVisibleItemCount(10); m_vComboBox2.CreateFromCtrl(this, IDC_VCOMBO2); m_vComboBox2.SetItemCount(10); m_vComboBox2.SetDroppedVisibleItemCount(5);
- 编译并运行应用程序!
关注点
起初,我试图使用WH_CBT
钩子来拦截列表框的创建,然后修改其样式,但是,如果启用了Common Control版本6(当使用清单文件时),此解决方案似乎会失败,这就是我寻求虚拟列表框实现的原因。
当我编写组合框代码时,我发现自定义绘制控件很有趣,我想知道如何实现Vista/Win7中那种漂亮的控件,并将其引入WinXP等早期操作系统,所以我还编写了一些实现了自定义绘制的其他控件类。
您可以在CustomControlDemo
演示项目中找到一些有趣/有用的控件类,如下所示
CCustomDrawHeaderCtrl
CSortHeaderCtrl
CCustomDrawToolTipCtrl
CCustomDrawListCtrl
CCustomDrawListBox
CCustomDrawTreeCtrl
CTriCheckStateTreeCtrl
CCustomDrawComboBox
这些类能够一些能力来表示/模拟Win7在WinXP下的漂亮外观,它们兼容VC6/VS2010。
出于某些原因,我无意详细介绍它们或发布另一篇文章来介绍它们,但是,您仍然可以自己探索源代码,并可以随心所欲地使用/修改它们。
这是演示应用程序的快照,希望您玩得开心
历史
- 2010/11/25: 初始发布
- 2010/12/29: 改进绘制代码,修复了一些小错误