Thread: [C] Editing SubItems in ListView with Win32 API

  1. #1
    Registered User
    Join Date
    Apr 2008
    Posts
    49

    [C] Editing SubItems in ListView with Win32 API

    Good afternoon,

    With the Label Editing property of the Win32 ListView (or List Control), I'm able to edit only the items of a ListView, but not the SubItems.
    My question is: how can I edit SubItems in a ListView, like what I'm able to do with Label Editing?
    I've searched the web (including MSDN) and this forum, but I can only find examples using MFC or .NET.
    (I'm a beginner. I'm learning by myself, but the internet is my only resource.)

    Thank you in advance.

  2. #2
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  3. #3
    Registered User
    Join Date
    Apr 2008
    Posts
    49
    Quote Originally Posted by novacain View Post
    I read the post, but I don't know if I have enough knowledge to write the code on my own based on the instructions you wrote.
    but I will try to write it, and send the doubts as they arise.
    Thank you in advance.

  4. #4
    Registered User
    Join Date
    Apr 2008
    Posts
    49
    Good afternoon,

    I did what you (novacain) told (I didn't know how to do most of it, but I looked at MSDN and figured out), but I got stuck in one step. It is not an error, just something I couldn't figure out how to do.

    First of all, let me show what I've already got (with the code below, I can click in the ListView items/SubItems and have the EditBox created, and destroyed when it looses focus).
    I have a ListView (with LVS_EDITLABELS and LVS_REPORT styles), and created a Proc for it (ListViewProc), which looks for the WM_LBUTTONDOWN messages. Then, it uses SubItemHitTest to see which SubItem was clicked. Then, I use GetSubItemRect to create EditBoxs the same size as the SubItems.
    The code below is the ListViewProc, with further explanations, just in case. The code below works fine.
    Code:
    long _stdcall ListViewProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	switch(message){
    		case WM_LBUTTONDOWN:
    		{
    			/*It uses SubItemHitTest to see which SubItem was clicked,
    			and stores it in the struct itemclicked.*/
    			long x, y;
    			x = (long)LOWORD(lParam);
    			y = (long)HIWORD(lParam);
    			LVHITTESTINFO itemclicked;
    			itemclicked.pt.x = x;
    			itemclicked.pt.y = y;
    			int lResult = ListView_SubItemHitTest(hwnd,&itemclicked);
    			/*If SubItemHitTest doesn't return any error (lResult!=-1), it gets the Rect
    			of the SubItem (or Item) clicked, and creates an EditBox (hEdit) with the
    			same size as the SubItem. Then, it sets focus on hEdit and sets a
    			callback function (EditProc) for it.*/
    			if (lResult!=-1){
    				RECT subitemrect;
    				ListView_GetSubItemRect(hwnd,itemclicked.iItem,itemclicked.iSubItem,LVIR_BOUNDS,&subitemrect);
    				int altura = subitemrect.bottom - subitemrect.top;
    				int largura = subitemrect.right - subitemrect.left;
    				if (itemclicked.iSubItem==0){largura=largura/4;}; /*NOTE: the ListView has 4 columns;
    										when iSubItem == 0 (an item is clicked),
    										the width (largura) is divided by 4,
    										because for items (not subitems) the
    										width returned is that of the whole row.*/
    				hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD|WS_VISIBLE|ES_WANTRETURN, 
    				subitemrect.left, subitemrect.top, largura, 1.5*altura, hwnd, 0, GetModuleHandle(NULL), NULL);
    				if(hEdit == NULL){MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);};
    				SetFocus(hEdit);
    				EOldProc = (WNDPROC)SetWindowLong(hEdit, GWL_WNDPROC, (LONG)EditProc);
    			} else {
    				/*If SubItemHitTest does return error (lResult=-1),
    				it kills focus of hEdit in order to destroy it.*/
    				SendMessage(hEdit,WM_KILLFOCUS,0,0);
    			}
    			return 0;
    			break;
    		}
    	}
    	/*Other messages are sent to the original Proc
    	(LVOldProc), which is defined globally.*/
    	return CallWindowProc(LVOldProc, hwnd, message, wParam, lParam);
    }
    Then, there is the EditProc callback function, which hEdit uses to intercept WM_KILLFOCUS messages. The code is below.

    Code:
    long _stdcall EditProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	switch(message){
    		case WM_KILLFOCUS:
    		{
    			//Here, it needs to send the ListView a LVN_ENDLABELEDIT notification.
    			DestroyWindow(hwnd);
    			break;
    		}
    	}
    
    	return CallWindowProc(EOldProc, hwnd, message, wParam, lParam);
    }
    The problem is that, when hEdit looses focus, it needs to send the ListView a LVN_ENDLABELEDIT notification before it's destroyed (via DestroyWindow). I know that I have to set a LV_DISPINFO struct, but I have no idea how to start (I've looked at MSDN, but it got me confused).
    I'm also not sure how the ListView will handle the notification (I know it's through WM_NOTIFY) and change the SubItem's text.
    If you (or anyone) could help, I would appreciate it.

    Thank you in advance for your patience.

  5. #5
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Code:
    LV_DISPINFO lvDispinfo;
    ZeroMemory(&lvDispinfo,sizeof(LV_DISPINFO));
    lvDispinfo.hdr.hwndFrom = hWnd;
    lvDispinfo.hdr.idFrom = IDC_LV_EDIT;//the edits ID
    lvDispinfo.hdr.code = LVN_ENDLABELEDIT;
    lvDispinfo.item.mask = LVIF_TEXT | LVIF_PARAM;	
    lvDispinfo.item.iItem = iItem;//row
    lvDispinfo.item.iSubItem = iSubItem;//col
    
    //if user did not enter any text or cancelled
    lvDispinfo.item.pszText =NULL;
    
    //else provide pointer to string 
    lvDispinfo.item.pszText = szEditText;
    lvDispinfo.item.cchTextMax = lstrlen(szEditText);
    
    //pass additional info as the lParam
    lvDispinfo.item.lParam = dwSomeOtherData;
    
    //send the WM_NOTIFY to the LVs parent  window
    SendMessage(hWnd_LV_Parent, WM_NOTIFY, (WPARAM)IDC_LV, (LPARAM)&lvDispinfo); //the LV ID and the LVs Parent window's HWND
    
    //close the edit (will call DestroyWindow())
    PostMessage(WM_CLOSE);
    In the LVs parent callback handle the WM_NOTIFY.
    Check for the LVs ID and LVN_ENDLABELEDIT msg.
    When you receive one, send the lParam (the LV_DISPINFO) to the LV using a LVN_ENDLABELEDIT msg.

    In the LVs callback handle the LVN_ENDLABELEDIT msg.
    If the pszText member is not NULL, set the text in the LV using the LV_ITEM data.
    Last edited by novacain; 12-19-2009 at 03:59 AM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  6. #6
    Registered User
    Join Date
    Apr 2008
    Posts
    49
    Thank you.

    Just one more thing. Here's the WM_KILLFOCUS of the EditBox (I get the text and store it in szEditText right before the editbox gets destroyed... it works, but see if it looks right to you):
    Code:
    		case WM_KILLFOCUS:
    		{
    			LV_DISPINFO lvDispinfo;
    			ZeroMemory(&lvDispinfo,sizeof(LV_DISPINFO));
    			lvDispinfo.hdr.hwndFrom = hwnd;
    			lvDispinfo.hdr.idFrom = GetDlgCtrlID(hwnd);
    			lvDispinfo.hdr.code = LVN_ENDLABELEDIT;
    			lvDispinfo.item.mask = LVIF_TEXT | LVIF_PARAM;	
    			lvDispinfo.item.iItem = itemclicked.iItem;//row
    			lvDispinfo.item.iSubItem = itemclicked.iSubItem;//col
    			lvDispinfo.item.pszText = NULL;
    			char szEditText[10];
    			GetWindowText(hwnd,szEditText,10);
    			lvDispinfo.item.pszText = szEditText;
    			lvDispinfo.item.cchTextMax = lstrlen(szEditText);
    			SendMessage(GetParent(GetDlgItem(b,MATRIZ)),WM_NOTIFY,(WPARAM)MATRIZ,(LPARAM)&lvDispinfo);
    			DestroyWindow(hwnd);
    			break;
    		}
    The problem is where I should get iItem and iSubItem. In the code above, I'm getting it from the struct itemclicked (now defined globally), which, as you can see in a previous code, stores data from the hittesting of the ListView. But it doesn't get the right information, because, as soon as hEdit looses focus (that is, when I click in another place/item of the ListView), itemclicked receives by hittest another iItem and iSubItem information from the new place/item in which I've clicked. So, everytime hEdit looses focus, it also looses the correct informations about Item and SubItem.
    So, how should I retrieve Item and SubItem information?

    Thank you in advance.
    Last edited by pc2-brazil; 12-19-2009 at 11:07 AM.

  7. #7
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    The code looks right but missing some required tests.

    Remove the LVIF_PARAM mask (the mask tells which fields are being used, you are not using the lParam data). I use lParam for DB refs, array indexes etc.

    You need a way to tell if the user cancelled the editing the text (clicked away, pressed escape etc). I tend to use a global flag.

    EDIT: I would get the edit working well and then add these char tests later.

    If the user clicks away with an edit active (ie cancels the edit by clicking another cell) I do not start another edit. I consider the click to be only for cancelling the edit and require another click to start editing again (ie 1st click starts edit, 2nd click cancels 1st edit, 3rd click starts new edit).
    EDIT: This is more complex behaviour that was required by the app. I would ignore this functionality until you require it.


    If the user cancelled the edit, set the string to NULL. This is very important as it allows you to ignore some LVN_ENDLABELEDIT msgs.

    Set the szEditText to the text in the cell when editing begins. Check for changes, validate input (if required) and send a NULL string if the user does not change the string or supplies invalid input.

    In the LV callback LVN_ENDLABELEDIT handler, first check the string for NULL before changing the text.

    You will also need to process some WM_CHAR msgs (ie to get the escape msg). I also process the arrow keys to allow Excel like movement between cells of the listview.



    EDIT: I tested and you should get the kill focus msg before the LB down msg. Testing is hard as many actions (ie using break points) will cause the edit to lose focus.
    Last edited by novacain; 12-19-2009 at 10:22 PM. Reason: after confirming msg sequence
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  8. #8
    Registered User
    Join Date
    Apr 2008
    Posts
    49
    OK, I think I got the edit working. Thank you, you have been very helpful.
    I will try to implement those char tests later.
    Here is how the code looks like now (still basic, without the char tests). I believe it's extremely helpful to whoever is looking for something similar.
    ListViewProc:
    Code:
    long _stdcall ListViewProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	switch(message){
    		case WM_LBUTTONDOWN:
    		{
    			if (hEdit != NULL){SendMessage(hEdit,WM_KILLFOCUS,0,0);};
    			LVHITTESTINFO itemclicked;
    			long x, y;
    			x = (long)LOWORD(lParam);
    			y = (long)HIWORD(lParam);
    			itemclicked.pt.x = x;
    			itemclicked.pt.y = y;
    			int lResult = ListView_SubItemHitTest(hwnd,&itemclicked);
    			if (lResult!=-1){
    				RECT subitemrect;
    				ListView_GetSubItemRect(hwnd,itemclicado.iItem,itemclicado.iSubItem,LVIR_BOUNDS,&subitemrect);
    				int altura = subitemrect.bottom - subitemrect.top;
    				int largura = subitemrect.right - subitemrect.left;
    				if (itemclicado.iSubItem==0){largura=largura/4;};
    				hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", 
    				WS_CHILD|WS_VISIBLE|ES_WANTRETURN, 
    				subitemrect.left, subitemrect.top, largura, 1.5*altura, hwnd, 0, GetModuleHandle(NULL), NULL);
    				if(hEdit == NULL)
    				MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);
    				SetFocus(hEdit);
    				EOldProc = (WNDPROC)SetWindowLong(hEdit, GWL_WNDPROC, (LONG)EditProc);
    				iItem = itemclicked.iItem;
    				iSubItem = itemclicked.iSubItem;
    			}
    			return 0;
    			break;
    		}
    	}
    	return CallWindowProc(LVOldProc, hwnd, message, wParam, lParam);
    }
    See here that I send WM_KILLFOCUS to hEdit if one already exists (so that the hEdit sets the item's text and gets destroyed before LBUTTONDOWN does anything else, since, as you said, WM_KILLFOCUS needs to be processed before LBUTTONDOWN). LBUTTONDOWN does the hittesting and stores the iItem and iSubItem information.
    EditProc:
    Code:
    long _stdcall EditProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	switch(message){
    		case WM_KILLFOCUS:
    		{
    			LV_DISPINFO lvDispinfo;
    			ZeroMemory(&lvDispinfo,sizeof(LV_DISPINFO));
    			lvDispinfo.hdr.hwndFrom = hwnd;
    			lvDispinfo.hdr.idFrom = GetDlgCtrlID(hwnd);
    			lvDispinfo.hdr.code = LVN_ENDLABELEDIT;
    			lvDispinfo.item.mask = LVIF_TEXT;
    			lvDispinfo.item.iItem = iItem;
    			lvDispinfo.item.iSubItem = iSubItem;
    			lvDispinfo.item.pszText = NULL;
    			char szEditText[10];
    			GetWindowText(hwnd,szEditText,10);
    			lvDispinfo.item.pszText = szEditText;
    			lvDispinfo.item.cchTextMax = lstrlen(szEditText);
    			SendMessage(GetParent(GetDlgItem(b,MATRIZ)),WM_NOTIFY,(WPARAM)MATRIZ,(LPARAM)&lvDispinfo); //the LV ID and the LVs Parent window's HWND
    			DestroyWindow(hwnd);
    			break;
    		}
    	}
    
    	return CallWindowProc(EOldProc, hwnd, message, wParam, lParam);
    }
    When hEdit receives WM_KILLFOCUS, it sends iItem/iSubItem/text information to the ListView via LVN_ENDLABELEDIT and destroys itself.
    Look at the WM_NOTIFY intercepted by the main window proc:
    Code:
    	case WM_NOTIFY:
    	{
    		if(((LPNMHDR)z)->code == LVN_ENDLABELEDIT){
    			LVITEM LvItem;
    			LV_DISPINFO* dispinfo = (LV_DISPINFO*)z;
    			char text[10]="";
    			LvItem.iItem = dispinfo->item.iItem;
    			LvItem.iSubItem = dispinfo->item.iSubItem;
    			LvItem.pszText = dispinfo->item.pszText;
    			SendDlgItemMessage(w,MATRIZ,LVM_SETITEMTEXT,(WPARAM)LvItem.iItem,(LPARAM)&LvItem); // put new text
    		}
    		break;
    	}
    Here, MATRIZ is the ID chosen for the ListView.
    It seems to be working fine by now. If anything goes wrong I will let you know.

  9. #9
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Well done!

    Post again if you have any issues.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  10. #10
    Registered User
    Join Date
    Apr 2008
    Posts
    49
    Quote Originally Posted by novacain View Post
    Well done!

    Post again if you have any issues.
    Thank you again for your help.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Win32 API or Win32 SDK?
    By jverkoey in forum A Brief History of Cprogramming.com
    Replies: 2
    Last Post: 07-20-2005, 03:26 PM
  2. OpenSSL and Win32 SSL API :: SSL/TLS
    By kuphryn in forum Networking/Device Communication
    Replies: 0
    Last Post: 03-10-2004, 07:46 PM
  3. FILES in WinAPI
    By Garfield in forum Windows Programming
    Replies: 46
    Last Post: 10-02-2003, 06:51 PM
  4. OLE Clipboard :: Win32 API vs. MFC
    By kuphryn in forum Windows Programming
    Replies: 3
    Last Post: 08-11-2002, 05:57 PM
  5. Thread Synchronization :: Win32 API vs. MFC
    By kuphryn in forum Windows Programming
    Replies: 2
    Last Post: 08-09-2002, 09:09 AM