Introduction
A lot of approaches have been made in order to improve MFC classes like CListCtrl
(report - style), CListBox
, or even CTreeCtrl
(even though the last one allows for label editing), in the sense of allowing the user to edit their items "in-line". Those approaches are very elaborate, and require a lot of care to be taken when coming to treating as many of their events as possible in order to prevent unwanted behaviours. When trying to improve older applications, a user has to replace the declarations of his / her objects with new ones related to the provided derived classes, and should test the applications extensively against possible unwanted "features".
What if a class - a single one - would allow the modification, on an item base, of all the CListCtrl
, CListBox
, CTreeCtrl
, and even the CComboBox
(after all, the last class makes use of CListBox
) objects of an application without changing the object declarations and disregarding the style combinations that may apply to some or all of the objects?
Note: After the updates dated March 23, the content of the list box used by the CComboBox
control is visible while doing the local input.
The LocalInputDlg
comes as a sort of "add-on" to any application, it does not change the behaviour of the application at all but solves the "local edit" problem in a unitary way. Try this class and you will add only two lines of code to your application. In return, a right-click on items belonging to objects of the above mentioned type will offer you the possibility to locally edit them.
Using the LocalInputDlg
Here are the steps you have to follow to add the "local edit" functionality to your beloved application:
- Add LocalInputDlg.h and LocalInputDlg.cpp files to your project.
- Uncomment the
LIWNE_USE_LVN_ENDLABELEDIT
in LocalInputDlg.h if you handle the LVN_ENDLABELEDIT
notification in your application.
- Include LocalInputDlg.h in YourApp.h.
- Use the wizard to add the
PreTranslateMessage()
virtual function to YourApp.cpp.
-
- Add the
LocalInputDlg dlgInput
member to YourApp
class.
- Inside the generated function, write the following line of code, exactly after the TODO remark line:
if (dlgInput.DoLocalInput(pMsg, true)) return TRUE;
should the chosen style be "arrow", or:
if (dlgInput.DoLocalInput(pMsg, false)) return TRUE;
for the "in-line" style.
That's all! Compile your project and here you are!
The Things Behind
In its OnInitDialog()
function, LocalInputDlg
positions itself depending on the position of the clicked item, and positions and resizes its controls accordingly. The width of the input field reflects the current width of the item. The window region is set so that the dialog gets either the "arrow" style or the "in-line edit" style aspect.
The local edit process starts by calling the DoLocalInput()
with a MESSAGE
pointer, provided by the PreTranslateMessage()
function of the application, as parameter. The DoLocalInput()
function checks if it deals with a WM_RBUTTONDOWN
(other messages can be used in place). In the case of this message, the function decides the way the message is to be processed. The pMsg->hwnd
handle is used to get a pointer to the window posting the message, which is type-cast to a CListBox*
pointer. Let this pointer be pLB
. With pLB->GetCount()
returning a positive number, the function decides it is dealing with a CListBox
object (this is also the case of the CComboBox
, as the message is coming from a temporary window which, in fact, is a CListBox
). Otherwise, the function gets the run-time class of the window posting the message (in the previous decision making, this was not working, as the window was temporary). Should the run-time class be a CTreeCtrl
, the function deals with a CTreeCtrl
object. Should the run-time class be a CListCtrl
, the function deals with a CListCtrl
object. Depending on the decision made, the appropriate request processing function is called.
ProcessInputRequest(CListBox *pLB, CPoint point);
ProcessInputRequest(CListCtrl *pLC, CPoint point);
ProcessInputRequest(CTreeCtrl *pTree, CPoint point);
Each of these functions estimates the position of the hit item and invokes the modal LocalInputDlg
.
Supporting the LVN_ENDLABELEDIT Notification for list-view Controls
eFotografo made a good point (see his message in the FAQ section below). In case of
CListCtrl
objects, the corresponding
ProcessInputRequest()
processing function needs to send a
WM_NOTIFY
message to control's parent, so that
LVN_ENDLABELEDIT
can be handled, if the application is needed to decide whether to accept or not the modification in the list-view (input validation).
NMLVDISPINFO nminfo;
nminfo.hdr.code = LVN_ENDLABELEDIT;
nminfo.hdr.hwndFrom = pLC->GetSafeHwnd();
nminfo.hdr.idFrom = pLC->GetDlgCtrlID();
nminfo.item.iItem = info.iItem;
nminfo.item.iSubItem = info.iSubItem;
nminfo.item.pszText = m_strInput.GetBuffer(0);
nminfo.item.mask = LVIF_TEXT;
if (pLC->GetParent()->SendMessage(WM_NOTIFY, (WPARAM)IDDLOCALEDIT,
(LPARAM)(LPNMHDR)&nminfo))
{
pLC->SetItemText(info.iItem, info.iSubItem, m_strInput.GetBuffer(0));
}
Depending on the value returned by SendMessage(WM_NOTIFY)
, the text in the list cell is modified according to the user input or the modification is rejected. This value (either 1 or 0) is decided within the handler for the LVN_ENDLABELEDIT
notification:
void YourAppDlg::YourHandlerFor_LVN_ENDLABELEDIT (NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
CString itemtext(pDispInfo->item.pszText); *pResult = TRUE; MessageBox((LPCTSTR)pDispInfo->item.pszText,
(LPCTSTR)"Accepted input"); }
void YourAppDlg::YourHandlerFor_LVN_ENDLABELEDIT (NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
CString itemtext(pDispInfo->item.pszText); *pResult = FALSE; MessageBox((LPCTSTR)pDispInfo->item.pszText,
(LPCTSTR)"Accepted input"); }
In case no validation action is taken in the application using the LVN_ENDLABELEDIT
notification mechanism, i.e. no entry is provided in the message map of application's dialog:
BEGIN_MESSAGE_MAP(YourAppDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_NOTIFY(LVN_ENDLABELEDIT, .....)
END_MESSAGE_MAP()
pLC->GetParent()->SendMessage(WM_NOTIFY)
in the ProcessInputRequest()
code for CListCtrl
always returns 0 and LocalInputDlg
loses functionality. For that reason, the LIWNE_USE_LVN_ENDLABELEDIT
preprocessor variable is used in LocalInputDlg.h:
#ifndef LIWNE_USE_LVN_ENDLABELEDIT
#endif
The header file comes with this definition commented out, assuming the LVN_ENDLABELEDIT
event is not handled in your application. Please uncomment it should your application handle that event. The presence of that preprocessor variable turns on the code in ProcessInputRequest()
for CListCtrl
related to input validation. By removing it, the previously mentioned code is turned off and the pLC-> SetItemText(info.iItem, info.iSubItem, m_strInput.GetBuffer(0))
is always called to accomplish the local editing of the list-view cell.
History
- Created: March 21, 2005
- Updated: March 23, 2005, due to the welcome request of jdmulder and the excellent ideas of PJ Arends. Code lines not belonging to the author are marked as "courtesy of ...".
- Updated: April 5, 2005 - support for "arrow" style and "in-line" style
- Updated: March 10, 2006 - article content correction in the section "Using the LocalInputDlg 4.b.":
dlgInput.DoLocalInput(pMsg,...)
instead of dlgInput(pMsg,...)
(fortunately, the source code is ok :-))
- Updated: October 16, 2009 - VS2005 source code and
LVN_ENDLABELEDIT
notification support added, as a result of eFotografo's excellent remarks