65.9K
CodeProject 正在变化。 阅读更多。
Home

类似 Dev-studio 的 EditListBox 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (20投票s)

2004 年 10 月 26 日

9分钟阅读

viewsIcon

65693

downloadIcon

2350

关于 EditListBox 控件的文章。

引言

ListBox 控件非常有用且经常使用,但在大多数情况下,它需要辅助控件的支持才能执行其功能。换句话说,ListBox 控件通常会配有一对侧边按钮,用于向其中添加、删除、插入或从中移除项。许多 Win32 开发者应该对 EditListBox 控件很熟悉,因为它可以在 MS Dev-Studio 的 Tools/Options/Directory 菜单中找到。EditListBox 控件是一个自管理的控件。它拥有所有必要的支持(用于添加、删除、移动项以及编辑项内容的按钮)作为控件本身的一部分。市面上已经有很多实现,但出于某些原因,我决定再提供一个实现供您使用。

实现说明

我研究了其他实现中的所有精妙功能,并将它们提取到了我的版本中。以下是我在实现过程中大量参考的 URL 列表。

我的 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)lParamEditListBox 的句柄。
  • 结果:(LRESULT)。未使用。

ELBN_EDITUPDATE

  • EditListBox 的行内 Edit 控件即将显示已更改的文本时,会发送 ELBN_EDITUPDATE 通知消息。此通知消息在控件格式化文本之后,显示文本之前发送。
  • EditListBox 的父窗口通过 WM_COMMAND 消息接收此通知消息。
  • (UINT)uMsg, WM_COMMAND
  • (WPARAM)wParam。低位字指定 EditListBox 的控件标识符。高位字指定通知消息(ELBN_EDITUPDATE)。
  • (LPARAM)lParamEditListBox 的句柄。
  • 结果:(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)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是内容即将被更改的项的索引。
  • 返回 FALSE 以允许更改项,否则返回 TRUE 以阻止更改。

ELBN_EX_ITEMCHANGED

  • 通知 EditListBox 控件的父窗口项已被更改。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是内容已被更改的项的索引。
  • 返回值将被忽略。

ELBN_EX_INSERTITEM

  • 通知 EditListBox 控件的父窗口新项即将被插入。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是即将被插入的新项的索引。
  • 返回 FALSE 以允许插入新项,否则返回 TRUE 以阻止插入。

ELBN_EX_DELETEITEM

  • 通知 EditListBox 控件的父窗口项即将被删除。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是内容即将被删除的项的索引。
  • 返回 FALSE 以允许删除项,否则返回 TRUE 以阻止删除。

ELBN_EX_BEGINLABELEDIT

  • 通知 EditListBox 控件的父窗口项的标签编辑即将开始。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是内容将通过行内 Edit 控件编辑的项的索引。
  • 返回 FALSE 以允许用户编辑该项,否则返回 TRUE 以阻止编辑。

ELBN_EX_ENDLABELEDIT

  • 通知 EditListBox 控件的父窗口项的标签编辑已结束。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是行内 Edit 内容已编辑的项的索引。
  • 返回值将被忽略。

ELBN_EX_ITEMMOVING

  • 通知 EditListBox 控件的父窗口项即将被移动。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是要移动的项的原始索引,而 lpnel->iDst 是要移动到的项的索引。
  • 返回 FALSE 以允许移动该项,否则返回 TRUE 以阻止移动。

ELBN_EX_ITEMMOVED

  • 通知 EditListBox 控件的父窗口项已被移动。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是已移动的项的原始索引,而 lpnel->iDst 是已移动到的项的索引。
  • 返回值将被忽略。

ELBN_EX_BROWSE

  • 通知 EditListBox 控件的父窗口行内 Edit 控件中的浏览按钮已被点击。
  • EditListBox 的父窗口通过 WM_NOTIFY 消息接收此通知消息。
  • (UINT)uMsg, WM_NOTIFY
  • (WPARAM)(UINT_PTR)idFrom。发送消息的 EditListBox 控件的标识符。
  • (LPARAM)(LPNMEDITLIST)lpnelNMEDITLIST 结构的地址。lpnel->iSrc 是已点击浏览按钮的项的索引。
  • 返回值将被忽略。

实例子类化

通过调用 Win32 API ::MakeDragList(HWND hwndListBox); 可以简单地将 ListBox 控件转换为 DragListBox。但是 DragListBox 实现有一个很大的缺陷,即无法将项拖放到列表的最后一个位置,也无法将最后一个项从其位置拖出。为了解决这个问题,EditListBox 控件使用了一个虚拟的最后一个项。为了让 EditListBox 控件的工作方式看起来好像没有虚拟的最后一个项存在一样,我通过**控件子类化**修改了 ListBox 控件的一些默认行为。

由于我没有使用所有 LB_XXX 消息,一些不常用消息的实现完全基于 MSDN 和我的理解。但大多数常用的 ListBox 消息都经过了充分测试,并被发现运行良好。

简单来说,我只修改了与操作 ListBox 项内容相关的消息的默认行为,例如 LB_GETCOUNTLB_ADDSTRINGLB_INSERTSTRINGLB_DELETESTRINGLB_RESETCONTENTLB_SETSELLB_FINDSTRING 等等。例如,LB_GETCOUNT 将返回 ListBox 中项的数量,**不包括**最后一个虚拟项。

其他消息,如 LB_GETITEMRECTLB_GETCURSELLB_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 上首次发布。
© . All rights reserved.