Click here to Skip to main content
15,879,184 members
Articles / Desktop Programming / WTL
Article

Dev-studio like EditListBox Control

Rate me:
Please Sign up or sign in to vote.
4.73/5 (21 votes)
7 Nov 20049 min read 64.5K   2.3K   48   5
An article on EditListBox Control.

Image 1

Introduction

ListBox control is very useful and frequently used, but in most cases, it requires the buddy control's support to perform its function. In other words, ListBox control is usually deployed with a couple of side buttons to add, delete, insert an item into or from it. Many Win32 developers should be familiar to EditListBox control since it can be found in Tools/Options/Directory menu of MS Dev-Studio. EditListBox control is a self-managed control. It has all the necessary supports (Button to add, delete, move an item, as well as to edit content of an item) as a part of control itself. There have been many implementations available all around, but for some reason, I decided to make one more implementation available to you.

Implementation Note

I took a look at all the neat features of other implementations and extracted them into mine. Given below is the list of URLs to which I intensively referred during my implementation.

And my EditListBox control's features are:

  • Subclassed ListBox control (it is not a Static control but a true ListBox control).
  • Works with a mix-in TitleTip support class of my other implementation available in CodeProject.
  • Built-in bitmap buttons to add a new item, to delete an item, and move an item up or down.
  • ToolTips support for built-in bitmap buttons.
  • In-place Edit control to edit an item with Browse button.
  • Extended messages and notifications for fine tuning.
  • Comfortable to WinXP visual style look quite well.
  • No additional resource file (no .RC file nor .bmp file) required to be included.
  • Drag'n Drop support to move item.
  • HotKey (Shortcut-key) support.
  • UNICODE-aware (ready).
  • Bonus! ButtonEdit control (three different implementations).

Using the code

It is easy to use. Include "EditListBox.h" in your project, then subclass a ListBox control.

#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;
    }
};

If you want to add Drag'n Drop support, it adds just a bit of complication. Since REFLECT_NOTIFICATION() macro does not work for DragListBox control's DL_XXX notifications, you must manually reflect them to the ListBox control you subclassed.

#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);
    }
};

If you want to derive your class from CEditListBox class or CDragEditListBox class, you must define an alternative message map (ALT_MSG_MAP(1)) and chain down some messages to base class' alternative message map (CHAIN_MSG_MAP_ALT(baseClass, 1)).
***[Obsolete: refer to History]***

#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);
    }
};

Public Functions

  • HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL, DWORD dwStyle = 0, DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL);

    Creates a ListBox control, then subclasses it internally to turn it to (Drag)EditListBox control.

  • BOOL SubclassWindow(HWND hWnd);

    Subclasses ListBox to turn it to (Drag)EditListBox control.

  • HWND UnsubclassWindow(BOOL bForce = FALSE);

    Un-subclasses (Drag)EditListBox to turn it back to a normal (Drag)ListBox control.

  • HWND GetEditControl(); or (HWND)(LRESULT)SendMessage(ELB_GETEDITCONTROL, 0, 0L);

    Gets the window handle to the in-place Edit control. Return NULL if failed.

  • void HideMoveButtons(BOOL bHide);

    Specifies whether or not to hide "Move Item Up" and "Move Item Down" bitmap buttons.

  • void ShowBrowseButtonInEdit(BOOL bShow);

    Specifies whether or not, to show the browse button in in-place Edit Control. ELBN_EX_BROWSE notification will be issued to parent window in the form of WM_NOTIFY when button clicked.

  • void SetLastDummyText(LPCTSTR lpszDummy); or void SetLastDummyText(ATL::_U_STRINGorID text, HINSTANCE hResourceInstance = NULL);

    Sets the text which will be shown in the last dummy item. For example, "Double click here to add a new item". If you set lpszDummy as NULL, the control will use (..........) ten dots in the last dummy item.

  • void SetBitmapButtonTipText(LPCTSTR lpszTipText); or void SetBitmapButtonTipText(ATL::_U_STRINGorID text, HINSTANCE hResourceInstance = NULL);

    Sets the tips for bitmap buttons. You must provide a tab separated string. For example, "New(INS)\tDelete(DEL)\tItem Up(ALT+UP)\tItem Down(ALT+DN)". If you set lpszTipText as NULL, tips for bitmap buttons will be disabled.

  • void UseFlatCaptionBorder(BOOL bUse);

    Uses flat(thin) borders for caption area. (This function might be useful when you use this control in property page in Common Control version 6)

Extended Messages and Notifications

Extended EditListBox Message (Extent of LB_XXX message)

ELB_GETEDITCONTROL

  • (UINT)uMsg, ELB_GETEDITCONTROL.
  • (WPARAM)wParam, not used, must be zero.
  • (LPARAM)lParam, not used, must be zero.
  • Result: (LRESULT)(HWND), returns in-place Edit control's window handle. If in-place Edit control has not been created yet, it returns NULL.

Extended EditListBox Notification #1 (Extent of LBN_XXX notification message)

ELBN_EDITCHANGE

  • The ELBN_EDITCHANGE notification message is sent after the user has taken an action that may have altered the text in the in-place Edit control portion of a EditListBox. Unlike the ELBN_EDITUPDATE notification message, this notification message is sent after the system updates the screen.
  • The parent window of the EditListBox receives this notification message through WM_COMMAND message.
  • (UINT)uMsg, WM_COMMAND.
  • (WPARAM)wParam. The low-order word specifies the control identifier of the EditListBox. The high-order word specifies the notification message (ELBN_EDITCHANGE).
  • (LPARAM)lParam. Handle to the EditListBox.
  • Result: (LRESULT). Not used.

ELBN_EDITUPDATE

  • The ELBN_EDITUPDATE notification message is sent when the in-place Edit control portion of a EditListBox is about to display altered text. This notification message is sent after the control has formatted the text, but before it displays the text.
  • The parent window of the EditListBox receives this notification message through WM_COMMAND message.
  • (UINT)uMsg, WM_COMMAND.
  • (WPARAM)wParam. The low-order word specifies the control identifier of the EditListBox. The high-order word specifies the notification message (ELBN_EDITUPDATE).
  • (LPARAM)lParam. Handle to the EditListBox.
  • Result: (LRESULT). Not used.

Extended EditListBox Notification #2 (Custom notification message)

Custom structure to pack the additional information for custom notifications

typedef struct tag
{
    NMHDR hdr;
    int iSrc;
    int iDst;
} NMEDITLIST, *LPNMEDITLIST;

ELBN_EX_ITEMCHANGING

  • Notifies a EditListBox control's parent window that the item is about to be changed.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose content is being changed.
  • Return FALSE to allow the item to be changed, otherwise return TRUE to prevent the change.

ELBN_EX_ITEMCHANGED

  • Notifies a EditListBox control's parent window that the item has been changed.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose content has been changed.
  • Return value will be ignored.

ELBN_EX_INSERTITEM

  • Notifies a EditListBox control's parent window that a new item is about to be inserted.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the new item which is being inserted.
  • Return FALSE to allow the new item to be inserted, otherwise return TRUE to prevent the insertion.

ELBN_EX_DELETEITEM

  • Notifies a EditListBox control's parent window that an item is about to be deleted.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose content is being deleted.
  • Return FALSE to allow the item to be deleted, otherwise return TRUE to prevent the deletion.

ELBN_EX_BEGINLABELEDIT

  • Notifies a EditListBox control's parent window about the start of label editing for an item.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose content will be edited through the in-place Edit control.
  • Return FALSE to allow the user to edit the item, otherwise return TRUE to prevent the edition.

ELBN_EX_ENDLABELEDIT

  • Notifies a EditListBox control's parent window about the end of label editing for an item.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose in-place Edit's content has been edited.
  • Return value will be ignored.

ELBN_EX_ITEMMOVING

  • Notifies a EditListBox control's parent window that the item is about to be moved.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the the item which is being moved from, and lpnel->iDst is the index of the item which it is being moved into.
  • Return FALSE to allow the item to be moved, otherwise return TRUE to prevent the move.

ELBN_EX_ITEMMOVED

  • Notifies a EditListBox control's parent window that the item has been moved.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item which it has been moved from, and lpnel->iDst is the index of the item which it has been moved into.
  • Return value will be ignored.

ELBN_EX_BROWSE

  • Notifies a EditListBox control's parent window that a browse button in in-place Edit control has been clicked.
  • The parent window of the EditListBox receives this notification message through WM_NOTIFY message.
  • (UINT)uMsg, WM_NOTIFY.
  • (WPARAM)(UINT_PTR)idFrom. Identifier of the EditListBox control sending a message.
  • (LPARAM)(LPNMEDITLIST)lpnel. Address of NMEDITLIST structure. lpnel->iSrc is the index of the item whose browse button has been clicked.
  • Return value will be ignored.

Instance Subclassing

The ListBox control can be turned to DragListBox by simply calling ::MakeDragList(HWND hwndListBox); Win32 API. But there is a big flaw in DragListBox implementation, which is that an item can not be dropped into the last position in the list nor the last item can be dragged from its position. To make around this problem, EditListBox control uses a dummy last item. In order to make EditListBox control work as if there is no dummy last item existing, I changed some default behaviors of ListBox control using control subclassing.

Since I have never used all the LB_XXX messages, some implementation for uncommonly-used messages was written completely based on MSDN and my understanding of it. But most of the commonly-used ListBox messages have been well tested and were found to be working fine.

Simply, I only changed the default behavior of messages that are related with manipulating a ListBox item's content such as LB_GETCOUNT, LB_ADDSTRING, LB_INSERTSTRING, LB_DELETESTRING, LB_RESETCONTENT, LB_SETSEL, LB_FINDSTRING, and so on. Take for an example, LB_GETCOUNT will return the count of items in the ListBox excluding the last dummy item.

The others such as LB_GETITEMRECT, LB_GETCURSEL, LB_SETITEMHEIGHT and a few more will behave default. Refer to source code to find out details.

History

Version: 1.03 - 11/04/2004

  • Changed the internal message structure for redirecting In-place Edit controls' message to EditListBox control. Thus the class that is derived from (Drag)EditListBox isn't required to define alternative message map nor to chain down messages to base class' alternative message map anymore.

Version: 1.02 - 10/27/2004

  • Added TAB key navigation b/w controls support when In-place editing mode.

Version 1.01 - 10/26/2004

  • Added border drawing codes to make it more compliant with WinXP Visual Style as well as Classic Style. The border will be drawn according to the current Window Theme. (DLL delayed loading to use Visual Style APIs)
  • Added WM_THEMECHANGED message handler
  • Added void UseFlatCaptionBorder(BOOL bUse); function

Version 1.0 - 10/22/2004

  • Initial release on CodeProject.

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


Written By
Other
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralDoes not work on Windows Vista Pin
Alan Wen3-Jan-08 2:56
Alan Wen3-Jan-08 2:56 
Generalgdi leak Pin
meepmeep6631-Aug-05 8:33
meepmeep6631-Aug-05 8:33 
GeneralC# version Pin
DVoracek17-Nov-04 11:38
professionalDVoracek17-Nov-04 11:38 
Generalgood Pin
tom_dx8-Nov-04 15:08
tom_dx8-Nov-04 15:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.