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 , WPARAM ,
LPARAM , BOOL& )
{
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 , WPARAM ,
LPARAM , BOOL& )
{
c_lbEditList2.SubclassWindow(GetDlgItem(IDC_LIST2));
c_lbEditList2.SetWindowText(_T("&DragEditListBox Control"));
c_lbEditList2.ShowBrowseButtonInEdit(TRUE);
...
return TRUE;
}
BOOL OnBeginDrag(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST2 == nCtlID)
return c_lbEditList2.BeginDrag(ptCursor);
return FALSE;
}
void OnCancelDrag(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST2 == nCtlID)
c_lbEditList2.CancelDrag(ptCursor);
}
int OnDragging(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST2 == nCtlID)
return c_lbEditList2.Dragging(ptCursor);
return 0;
}
void OnDropped(int nCtlID, HWND , 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)
CHAIN_MSG_MAP_ALT(baseClass, 1)
END_MSG_MAP()
protected:
CBrush m_brush;
public:
CDragEditListBoxGreen()
{
m_brush.CreateSolidBrush(ELB_YELLOWBKCOLOR);
}
LRESULT OnCtlColor(UINT uMsg, WPARAM wParam,
LPARAM , BOOL& )
{
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 , WPARAM ,
LPARAM , BOOL& )
{
c_lbEditList3.SubclassWindow(GetDlgItem(IDC_LIST3));
c_lbEditList3.SetWindowText(_T("&Big Green"));
c_lbEditList3.ShowBrowseButtonInEdit(TRUE);
...
return TRUE;
}
BOOL OnBeginDrag(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST3 == nCtlID)
return c_lbEditList3.BeginDrag(ptCursor);
return FALSE;
}
void OnCancelDrag(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST3 == nCtlID)
c_lbEditList3.CancelDrag(ptCursor);
}
int OnDragging(int nCtlID, HWND , POINT ptCursor)
{
if(IDC_LIST3 == nCtlID)
return c_lbEditList3.Dragging(ptCursor);
return 0;
}
void OnDropped(int nCtlID, HWND , 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.