ZaiRoN

Editable Listview control

Rating: 3 votes, 2.33 average.
Few days ago I started renewing my PE editor's gui, I wanted to replace some edit box controls with a listview control. Everything was going well until I had to edit the value inside a cell. With the original listview control you can change the text of the first subitem of a row only, but I would like to edit every single subitem. How can I solve the problem? I didn't want to waste time solving the problem so I decided to take a look at the usual programming places starting from Code Project. Hm, nothing. I'm not so good in searching information through the net, but seems like there are working samples on mfc, .net and vb only. No win32 programming stuff... Well, I decided to give it a try subclassing the control.
I have never subclass-ed a control before, it's my first try. I don't know if there's a better approach. I don't even know if it's the correct way to solve the problem, but it seems to works well. Let's start.

The steps to follow are:
1. Create an edit box that will be used to insert the new text
2. Set the new window procedure able to handle edit control's messages
3. Apply/abort text modification

I use VS creating a win32 project. Add a listview control to your dialog setting "Edit labels" to FALSE. If you set the option to TRUE the OS will handle subitem modification, I prefer to avoid this behaviour.

The idea is to change the subitem's text when a double click occours. I catch the event in the main window procedure calling the function (named SubClass_ListView_Editable) which subclasses the control.

The first step consists of creating the edit box over the clicked subitem. To create the edit box I need to know where to put it. LVM_GETSUBITEMRECT returns information about the rectangle for a subitem of a listview control. With this information I can create the new control:
Code:
ListView_GetSubItemRect(hListView, _lParam->iItem, _lParam->iSubItem, LVIR_LABEL, &r);
//    Time to create the new edit box
hEditable = CreateWindowEx(0, "EDIT", "Edit me", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE, r.left, r.top, r.right-r.left, r.bottom-r.top, _lParam->hdr.hwndFrom, NULL, hInst, 0);
"r" is defined as a RECT structure:

Code:
typedef struct _RECT {
LONG left;        //      x-coordinate of the upper-left corner of the rectangle
LONG top;        //    y-coordinate of the upper-left corner of the rectangle
LONG right;      //    x-coordinate of the lower-right corner of the rectangle
LONG bottom;   //    y-coordinate of the lower-right corner of the rectangle
} RECT, *PRECT;
As you can see I use "r" inside CreateWindowEx function specifying the coordinates of the new control.
The third parameter of CreateWindowEx is the text that will be shown in the control. I use a static text but you can leave it blank or display the subitem's text, it's up to you. Now, the new control has been created and I'm going to set some features:

Code:
SendMessage(hEditable, EM_LIMITTEXT, 8, 0);        //    It accepts no more than 8 chars
SendMessage(hEditable, EM_SETSEL, 0, 8);        //    Text selected
SetFocus(hEditable);                    //    Focus to the new box
If you don't need a particular behaviour (limit text, accept only numbers...) you can avoid the first two calls but I think the third one is useful, it gives the focus to the new edit box.

The control is complete, I have to add the new window procedure. This can be done using SetWindoLong function:

Code:
LONG SetWindowLong(
HWND hWnd,    //    Handle of the new edit control
int nIndex,        //    The attribute to change
LONG dwNewLong    //    The new value
);
The function changes an attribute of a specified window, in this case I'm going to change the address of the dialog procedure. The aim is to add a new window procedure for handling edit box's messages only. I'll pass over all the other messages forwarding them towards the old window procedure.

Code:
wpOld = (WNDPROC)SetWindowLong(hEditable, GWL_WNDPROC, SubClass_ListView_WndProc);
SetProp(hEditable, "WP_OLD", (HANDLE)wpOld);
SubClass_ListView_WndProc represents the new dialog procedure.
I have to save the address of the original window procedure because I have to restore it when I'll destroy the edit box. To save the address I use SetProp function, but if you prefer you can use global variables. To end this piece of code I save some more useful information: row and column of the subitem to change:

Code:
SetProp(hEditable, "ITEM", (HANDLE)_lParam->iItem);
SetProp(hEditable, "SUBITEM", (HANDLE)_lParam->iSubItem);
Which kind of messages will I have to catch? WM_KEYDOWN and WM_DESTROY only, all the other messages are passed to the original window procedure in this way:

Code:
return CallWindowProc((WNDPROC)GetProp(hEditable, "WP_OLD"), hwnd, uMsg, wParam, lParam);
I catch WM_KEYDOWN because I have to handle ENTER ans ESC key. When you hit ENTER the text will be saved in the subitem, and when you click ESC the operation will be aborted:

Code:
case WM_KEYDOWN:
if (LOWORD(wParam) == VK_RETURN)
{
...
// Item and suibtem to change
LvItem.iItem = GetProp(hEditable, "ITEM");
LvItem.iSubItem = GetProp(hEditable, "SUBITEM");
// Where to store the new text
LvItem.pszText = text;
// Get new text and set it in the subitem
GetWindowText(hEditable, text, sizeof(text));
SendMessage(hListView, LVM_SETITEMTEXT, (WPARAM)GetProp(hEditable, "ITEM"), (LPARAM)&LvItem);
DestroyWindow(hEditable);
}
else if (LOWORD(wParam) == VK_ESCAPE)
DestroyWindow(hEditable);
break;
If you press ESC the edit box will be destroyed without changing the subitem's text.
When ENTER is pressed I simply get the text storing it in the subitem. After that I can destroy the edit box.
There's something more to say about VK_RETURN. Look at CreateWindowEx parameters, there's a ES_MULTILINE value. The value is necessary otherwise the application refuses to catch ENTER.
The last thing I check in the new window procedure is WM_DESTROY message, I simply remove the saved properties and then I restore the original window procedure calling SetWindowLong again.

What about mouse click when I'm changing the subitem's value? It's like VK_ESCAPE key, I remove the edit box aborting the operation. See the attached source code for details.

The picture represents the subclassing method in action:


The code can be optimized for sure, just fix/change/remove/add everything you need.
Feel free to post your comment/suggestion/criticism here.
Download the VS project from http://www.box.net/shared/static/kcurtvm8v7.zip

Game over!

Submit "Editable Listview control" to Digg Submit "Editable Listview control" to del.icio.us Submit "Editable Listview control" to StumbleUpon Submit "Editable Listview control" to Google

Updated November 6th, 2007 at 07:05 by ZaiRoN

Categories
Uncategorized

Comments