类似 Dev-studio 的 EditListBox 控件






4.73/5 (20投票s)
2004 年 10 月 26 日
9分钟阅读

65693

2350
关于 EditListBox 控件的文章。
引言
ListBox
控件非常有用且经常使用,但在大多数情况下,它需要辅助控件的支持才能执行其功能。换句话说,ListBox
控件通常会配有一对侧边按钮,用于向其中添加、删除、插入或从中移除项。许多 Win32 开发者应该对 EditListBox
控件很熟悉,因为它可以在 MS Dev-Studio 的 Tools/Options/Directory 菜单中找到。EditListBox
控件是一个自管理的控件。它拥有所有必要的支持(用于添加、删除、移动项以及编辑项内容的按钮)作为控件本身的一部分。市面上已经有很多实现,但出于某些原因,我决定再提供一个实现供您使用。
实现说明
我研究了其他实现中的所有精妙功能,并将它们提取到了我的版本中。以下是我在实现过程中大量参考的 URL 列表。
- viksoe.dk - Edit ListBox 控件。
- Catch22.net 中的教程
- GipsySoft
- 辅助按钮.
- CodeGuru
我的 EditListBox
控件的功能包括:
- 子类化的
ListBox
控件(它不是Static
控件,而是真正的ListBox
控件)。 - 与 CodeProject 上我其他实现中的 混合式 TitleTip 支持类配合使用。
- 内置位图按钮,用于添加新项、删除项以及向上或向下移动项。
- 内置位图按钮的
ToolTips
支持。 - 行内
Edit
控件,用于编辑项内容,并带有浏览按钮。 - 扩展的消息和通知,用于精细调整。
- 能够很好地兼容 WinXP 的视觉风格。
- 无需包含额外的资源文件(无 .RC 文件或 .BMP 文件)。
- 支持拖放操作来移动项。
- 热键(快捷键)支持。
- 兼容 UNICODE(已准备就绪)。
- 附送!
ButtonEdit
控件(三种不同的实现)。
使用代码
使用简单。在您的项目中包含 "EditListBox.h",然后子类化一个 ListBox
控件。
#include "EditListBox.h" using codeproject::CEditListBox; class CMainDlg : public CDialogImpl<CMainDlg> { public: enum { IDD = IDD_MAINDLG }; BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) ... END_MSG_MAP() protected: CEditListBox c_lbEditList1; public: LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { c_lbEditList1.SubclassWindow(GetDlgItem(IDC_LIST1)); c_lbEditList1.SetWindowText(_T("&Hot key is supported. Press ALT+H to set focus on this ListBox")); ... return TRUE; } };
如果您想添加拖放支持,会增加一点点复杂性。由于 REFLECT_NOTIFICATION()
宏不适用于 DragListBox
控件的 DL_XXX
通知,您必须手动将它们反射到您子类化的 ListBox
控件。
#include "EditListBox.h" using codeproject::CDragEditListBox; class CMainDlg : public CDialogImpl<CMainDlg>, public CDragListNotifyImpl<CMainDlg> { public: enum { IDD = IDD_MAINDLG }; BEGIN_MSG_MAP(CMainDlg) CHAIN_MSG_MAP(CDragListNotifyImpl<CMainDlg>) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) ... END_MSG_MAP() protected: CDragEditListBox c_lbEditList2; public: LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { c_lbEditList2.SubclassWindow(GetDlgItem(IDC_LIST2)); c_lbEditList2.SetWindowText(_T("&DragEditListBox Control")); c_lbEditList2.ShowBrowseButtonInEdit(TRUE); ... return TRUE; } // Overrides BOOL OnBeginDrag(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST2 == nCtlID) return c_lbEditList2.BeginDrag(ptCursor); return FALSE; } void OnCancelDrag(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST2 == nCtlID) c_lbEditList2.CancelDrag(ptCursor); } int OnDragging(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST2 == nCtlID) return c_lbEditList2.Dragging(ptCursor); return 0; } void OnDropped(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST2 == nCtlID) c_lbEditList2.Dropped(ptCursor); } };
如果您想从 CEditListBox
类或 CDragEditListBox
类派生您的类,您必须定义一个备用消息映射(ALT_MSG_MAP(1)
)并将一些消息链式传递到基类的备用消息映射(CHAIN_MSG_MAP_ALT(baseClass, 1)
)。
***[已弃用:请参考历史记录]***
#include "EditListBox.h" using codeproject::CDragEditListBox; class CDragEditListBoxGreen : public CDragEditListBox { public: typedef CDragEditListBoxGreen thisClass; typedef CDragEditListBox baseClass; BEGIN_MSG_MAP(thisClass) MESSAGE_HANDLER(OCM_CTLCOLORLISTBOX, OnCtlColor) DEFAULT_REFLECTION_HANDLER() CHAIN_MSG_MAP(baseClass) ALT_MSG_MAP(1) // obsolete CHAIN_MSG_MAP_ALT(baseClass, 1) // obsolete END_MSG_MAP() protected: CBrush m_brush; public: CDragEditListBoxGreen() { m_brush.CreateSolidBrush(ELB_YELLOWBKCOLOR); } LRESULT OnCtlColor(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { uMsg; ATLASSERT(OCM_CTLCOLORLISTBOX == uMsg); CDCHandle dc((HDC)wParam); dc.SetTextColor(ELB_GREENTEXTCOLOR); dc.SetBkColor(ELB_YELLOWBKCOLOR); return (LRESULT)(HBRUSH)m_brush; } }; class CMainDlg : public CDialogImpl<CMainDlg>, public CDragListNotifyImpl<CMainDlg> { public: enum { IDD = IDD_MAINDLG }; BEGIN_MSG_MAP(CMainDlg) CHAIN_MSG_MAP(CDragListNotifyImpl<CMainDlg>) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) ... REFLECT_NOTIFICATIONS() END_MSG_MAP() protected: CDragEditListBoxGreen c_lbEditList3; public: LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { c_lbEditList3.SubclassWindow(GetDlgItem(IDC_LIST3)); c_lbEditList3.SetWindowText(_T("&Big Green")); c_lbEditList3.ShowBrowseButtonInEdit(TRUE); ... return TRUE; } // Override BOOL OnBeginDrag(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST3 == nCtlID) return c_lbEditList3.BeginDrag(ptCursor); return FALSE; } void OnCancelDrag(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST3 == nCtlID) c_lbEditList3.CancelDrag(ptCursor); } int OnDragging(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST3 == nCtlID) return c_lbEditList3.Dragging(ptCursor); return 0; } void OnDropped(int nCtlID, HWND /*hWndDragList*/, POINT ptCursor) { if(IDC_LIST3 == nCtlID) c_lbEditList3.Dropped(ptCursor); } };
公共函数
HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL);
创建
ListBox
控件,然后内部对其进行子类化,将其转换为(Drag)EditListBox
控件。BOOL SubclassWindow(HWND hWnd);
对
ListBox
进行子类化,将其转换为(Drag)EditListBox
控件。HWND UnsubclassWindow(BOOL bForce = FALSE);
对
(Drag)EditListBox
进行反子类化,将其恢复为普通的(Drag)ListBox
控件。HWND GetEditControl(); 或 (HWND)(LRESULT)SendMessage(ELB_GETEDITCONTROL, 0, 0L);
获取行内
Edit
控件的窗口句柄。如果失败,返回NULL
。void HideMoveButtons(BOOL bHide);
指定是否隐藏“向上移动项”和“向下移动项”的位图按钮。
void ShowBrowseButtonInEdit(BOOL bShow);
指定是否在行内
Edit
控件中显示浏览按钮。当点击按钮时,ELBN_EX_BROWSE
通知将以WM_NOTIFY
消息的形式发送给父窗口。void SetLastDummyText(LPCTSTR lpszDummy);
或void SetLastDummyText(ATL::_U_STRINGorID text, HINSTANCE hResourceInstance = NULL);
设置将在最后一个虚拟项中显示的文本。例如,“双击此处添加新项”。如果您将
lpszDummy
设置为NULL
,控件将在最后一个虚拟项中使用(..........)十个点。void SetBitmapButtonTipText(LPCTSTR lpszTipText);
或void SetBitmapButtonTipText(ATL::_U_STRINGorID text, HINSTANCE hResourceInstance = NULL);
设置位图按钮的提示文本。您必须提供一个**以制表符分隔**的字符串。例如,“New(INS)\tDelete(DEL)\tItem Up(ALT+UP)\tItem Down(ALT+DN)”。如果您将
lpszTipText
设置为NULL
,位图按钮的提示将被禁用。void UseFlatCaptionBorder(BOOL bUse);
使用扁平(细)边框用于标题区域。(此函数在使用通用控件版本 6 的属性页中可能很有用)
扩展的消息和通知
扩展的 EditListBox 消息(LB_XXX 消息的扩展)
ELB_GETEDITCONTROL
- (
UINT
)uMsg
,ELB_GETEDITCONTROL
。 - (
WPARAM
)wParam
, 未使用,必须为零。 - (
LPARAM
)lParam
, 未使用,必须为零。 - 结果:(
LRESULT
)(HWND
),返回行内Edit
控件的窗口句柄。如果行内 Edit 控件尚未创建,则返回NULL
。
扩展的 EditListBox 通知 #1(LBN_XXX 通知消息的扩展)
ELBN_EDITCHANGE
- 在用户执行了可能已更改
EditListBox
的行内Edit
控件文本的操作后,会发送ELBN_EDITCHANGE
通知消息。与ELBN_EDITUPDATE
通知消息不同,此通知消息在系统更新屏幕后发送。 EditListBox
的父窗口通过WM_COMMAND
消息接收此通知消息。- (
UINT
)uMsg
,WM_COMMAND
。 - (
WPARAM
)wParam
。低位字指定EditListBox
的控件标识符。高位字指定通知消息(ELBN_EDITCHANGE
)。 - (
LPARAM
)lParam
。EditListBox
的句柄。 - 结果:(
LRESULT
)。未使用。
ELBN_EDITUPDATE
- 当
EditListBox
的行内Edit
控件即将显示已更改的文本时,会发送ELBN_EDITUPDATE
通知消息。此通知消息在控件格式化文本之后,显示文本之前发送。 EditListBox
的父窗口通过WM_COMMAND
消息接收此通知消息。- (
UINT
)uMsg
,WM_COMMAND
。 - (
WPARAM
)wParam
。低位字指定EditListBox
的控件标识符。高位字指定通知消息(ELBN_EDITUPDATE
)。 - (
LPARAM
)lParam
。EditListBox
的句柄。 - 结果:(
LRESULT
)。未使用。
扩展的 EditListBox 通知 #2(自定义通知消息)
用于打包自定义通知附加信息的自定义结构
typedef struct tag { NMHDR hdr; int iSrc; int iDst; } NMEDITLIST, *LPNMEDITLIST;
ELBN_EX_ITEMCHANGING
- 通知
EditListBox
控件的父窗口项即将被更改。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是内容即将被更改的项的索引。 - 返回
FALSE
以允许更改项,否则返回TRUE
以阻止更改。
ELBN_EX_ITEMCHANGED
- 通知
EditListBox
控件的父窗口项已被更改。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是内容已被更改的项的索引。 - 返回值将被忽略。
ELBN_EX_INSERTITEM
- 通知
EditListBox
控件的父窗口新项即将被插入。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是即将被插入的新项的索引。 - 返回
FALSE
以允许插入新项,否则返回TRUE
以阻止插入。
ELBN_EX_DELETEITEM
- 通知
EditListBox
控件的父窗口项即将被删除。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是内容即将被删除的项的索引。 - 返回
FALSE
以允许删除项,否则返回TRUE
以阻止删除。
ELBN_EX_BEGINLABELEDIT
- 通知
EditListBox
控件的父窗口项的标签编辑即将开始。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是内容将通过行内Edit
控件编辑的项的索引。 - 返回
FALSE
以允许用户编辑该项,否则返回TRUE
以阻止编辑。
ELBN_EX_ENDLABELEDIT
- 通知
EditListBox
控件的父窗口项的标签编辑已结束。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是行内Edit
内容已编辑的项的索引。 - 返回值将被忽略。
ELBN_EX_ITEMMOVING
- 通知
EditListBox
控件的父窗口项即将被移动。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是要移动的项的原始索引,而lpnel->iDst
是要移动到的项的索引。 - 返回
FALSE
以允许移动该项,否则返回TRUE
以阻止移动。
ELBN_EX_ITEMMOVED
- 通知
EditListBox
控件的父窗口项已被移动。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是已移动的项的原始索引,而lpnel->iDst
是已移动到的项的索引。 - 返回值将被忽略。
ELBN_EX_BROWSE
- 通知
EditListBox
控件的父窗口行内Edit
控件中的浏览按钮已被点击。 EditListBox
的父窗口通过WM_NOTIFY
消息接收此通知消息。- (
UINT
)uMsg
,WM_NOTIFY
。 - (
WPARAM
)(UINT_PTR
)idFrom
。发送消息的EditListBox
控件的标识符。 - (
LPARAM
)(LPNMEDITLIST
)lpnel
。NMEDITLIST
结构的地址。lpnel->iSrc
是已点击浏览按钮的项的索引。 - 返回值将被忽略。
实例子类化
通过调用 Win32 API ::MakeDragList(HWND hwndListBox);
可以简单地将 ListBox
控件转换为 DragListBox
。但是 DragListBox
实现有一个很大的缺陷,即无法将项拖放到列表的最后一个位置,也无法将最后一个项从其位置拖出。为了解决这个问题,EditListBox
控件使用了一个虚拟的最后一个项。为了让 EditListBox
控件的工作方式看起来好像没有虚拟的最后一个项存在一样,我通过**控件子类化**修改了 ListBox
控件的一些默认行为。
由于我没有使用所有 LB_XXX
消息,一些不常用消息的实现完全基于 MSDN 和我的理解。但大多数常用的 ListBox
消息都经过了充分测试,并被发现运行良好。
简单来说,我只修改了与操作 ListBox
项内容相关的消息的默认行为,例如 LB_GETCOUNT
、LB_ADDSTRING
、LB_INSERTSTRING
、LB_DELETESTRING
、LB_RESETCONTENT
、LB_SETSEL
、LB_FINDSTRING
等等。例如,LB_GETCOUNT
将返回 ListBox
中项的数量,**不包括**最后一个虚拟项。
其他消息,如 LB_GETITEMRECT
、LB_GETCURSEL
、LB_SETITEMHEIGHT
等,将保持默认行为。详情请参考源代码。
历史
版本:1.03 - 2004/11/04
- 更改了内部消息结构,用于重定向行内 Edit 控件的消息到 EditListBox 控件。因此,从 (Drag)EditListBox 派生的类不再需要定义备用消息映射或将消息链式传递到基类的备用消息映射。
版本:1.02 - 2004/10/27
- 在行内编辑模式下,增加了 TAB 键在控件之间导航的支持。
版本 1.01 - 2004/10/26
- 添加了边框绘制代码,使其更符合 WinXP 视觉风格和经典风格。边框将根据当前窗口主题绘制。(DLL 延迟加载以使用视觉风格 API)
- 添加了
WM_THEMECHANGED
消息处理程序 - 添加了
void UseFlatCaptionBorder(BOOL bUse);
函数
版本 1.0 - 2004/10/22
- 在 CodeProject 上首次发布。