c++ - Properly handle subitem editing ( or canceling of subitem editing ) in listview with editable subitems -
introduction:
i trying implement listview control editable subitems. in-place editing of items/subitems use edit control.
i believe have managed code placing of edit control above item/subitem.
problem:
i not know on events should end/cancel subitem editing ( hide edit control, set subitem text etc ) , how should it.
to clarify, speak of moment when user finishes/cancels in place editing.
at point edit control no longer needed, should hide ( not recreating every time; believe creating once , showing/hiding when needed more efficient ).
i targeting behavior properties window has in visual studio ( see attached image see window refer ).
i want achieve editing/canceling same way window when user presses esc key/clicks on window/clicks on scrollbar etc.
my efforts solve this:
using google, found few examples old , not address of relevant cases, why ask here help.
however, able find out 1 of events must consider en_killfocus
, case when user presses esc/enter key , case when user clicks on other edit control.
edit:
i have managed handle esc , enter keys, case when user clicks on sibling control or switches windows alt + tab. have updated sscce relevant changes
question:
in order achieve default behavior grid ( if there 1 windows apps ), messages/events must handle?
can point out should edit subitem , hide edit control, , should hide edit control?
edit:
my problem remained handle case when user clicks on listview scrollbars, or on background of main window. not know how handle , appreciate can get.
relevant information:
i use visual studio 2013, on windows 7 x86;
i developing in c++ using raw winapi;
sscce:
below solution have far. have tried thoroughly comment it, if more info required leave comment , update post.
#include <windows.h> #include <windowsx.h> // various listview macros etc #include <commctrl.h> #include <stdio.h> // swprintf_s() // enable visual styles #pragma comment( linker, "/manifestdependency:\"type='win32' \ name='microsoft.windows.common-controls' version='6.0.0.0' \ processorarchitecture='*' publickeytoken='6595b64144ccf1df' \ language='*'\"") // link common controls library #pragma comment( lib, "comctl32.lib") //global variables hinstance hinst; // listview subclass procedure lresult callback listviewsubclassproc(hwnd hwnd, uint message, wparam wparam, lparam lparam, uint_ptr uidsubclass, dword_ptr dwrefdata) { switch (message) { case wm_vscroll: case wm_hscroll: // if edit control has focus take away , give listview if (getfocus() == getdlgitem(getparent(hwnd), 5000)) setfocus(hwnd); // use wm_nextdlgctl dialogbox !!!! break; case wm_ncdestroy: ::removewindowsubclass(hwnd, listviewsubclassproc, uidsubclass); return defsubclassproc(hwnd, message, wparam, lparam); } return ::defsubclassproc(hwnd, message, wparam, lparam); } // subclass procedure edit control lresult callback inplaceeditcontrol_subclassproc(hwnd hwnd, uint message, wparam wparam, lparam lparam, uint_ptr uidsubclass, dword_ptr dwrefdata) { switch (message) { case wm_getdlgcode: return (dlgc_wantallkeys | defsubclassproc(hwnd, message, wparam, lparam)); case wm_killfocus: showwindow(hwnd, sw_hide); return defsubclassproc(hwnd, message, wparam, lparam); case wm_char: //process message avoid message beeps. switch (wparam) { case vk_return: return 0l; case vk_escape: return 0l; default: return ::defsubclassproc(hwnd, message, wparam, lparam); } break; case wm_keydown: switch (wparam) { case vk_return: { // listview handle hwnd hwndlv = getdlgitem(getparent(hwnd), 2000); // edit control's client rectangle rect rc = { 0 }; getclientrect(hwnd, &rc); // since edit control lies inside item rectangle // can test coordinate inside edit control's // client rectangle // chose ( rc.left, rc.top ) mapwindowpoints(hwnd, hwndlv, (lppoint)&rc, (sizeof(rect) / sizeof(point))); // item , subitem indexes lvhittestinfo lvhti = { 0 }; lvhti.pt.x = rc.left; lvhti.pt.y = rc.top; listview_subitemhittest(hwndlv, &lvhti); // edit control's text wchar_t txt[50] = l""; edit_gettext(hwnd, txt, 50); // edit cell text listview_setitemtext(hwndlv, lvhti.iitem, lvhti.isubitem, txt); // restore focus listview // triggers en_killfocus // hide edit control setfocus(hwndlv); } return 0l; case vk_escape: setfocus(getdlgitem(getparent(hwnd), 2000)); return 0l; default: return ::defsubclassproc(hwnd, message, wparam, lparam); } break; case wm_ncdestroy: ::removewindowsubclass(hwnd, inplaceeditcontrol_subclassproc, uidsubclass); return defsubclassproc(hwnd, message, wparam, lparam); } return ::defsubclassproc(hwnd, message, wparam, lparam); } // main window procedure lresult callback wndproc(hwnd hwnd, uint msg, wparam wparam, lparam lparam) { switch (msg) { case wm_create: { //================ create controls rect rec = { 0 }; getclientrect(hwnd, &rec); hwnd hwndlv = createwindowex(0, wc_listview, l"", ws_child | ws_visible | ws_border | ws_clipsiblings | lvs_report, 50, 50, 250, 200, hwnd, (hmenu)2000, hinst, 0); // in place edit control hwnd hwndedit = createwindowex(0, wc_edit, l"", es_autohscroll | ws_child | ws_border, 200, 265, 100, 25, hwnd, (hmenu)5000, hinst, 0); // edit control must have same font listview hfont hf = (hfont)sendmessage(hwndlv, wm_getfont, 0, 0); if (hf) sendmessage(hwndedit, wm_setfont, (wparam)hf, (lparam)true); // subclass edit control, can edit subitem enter, or // cancel editing esc setwindowsubclass(hwndedit, inplaceeditcontrol_subclassproc, 0, 0); // set extended listview styles listview_setextendedlistviewstyle(hwndlv, lvs_ex_fullrowselect | lvs_ex_gridlines | lvs_ex_doublebuffer); // subclass listview setwindowsubclass(hwndlv, listviewsubclassproc, 0, 0); // add columns lvcolumn lvc = { 0 }; lvc.mask = lvcf_fmt | lvcf_width | lvcf_text | lvcf_subitem; lvc.fmt = lvcfmt_left; (long nindex = 0; nindex < 5; nindex++) { wchar_t txt[50]; swprintf_s(txt, 50, l"column %d", nindex); lvc.isubitem = nindex; lvc.cx = 60; lvc.psztext = txt; listview_insertcolumn(hwndlv, nindex, &lvc); } // add items lvitem lvi; lvi.mask = lvif_text; (lvi.iitem = 0; lvi.iitem < 10000; lvi.iitem++) { (long nindex = 0; nindex < 5; nindex++) { wchar_t txt[50]; swprintf_s(txt, 50, l"item %d%d", lvi.iitem, nindex); lvi.isubitem = nindex; lvi.psztext = txt; if (!nindex) // item senddlgitemmessage(hwnd, 2000, lvm_insertitem, 0, reinterpret_cast<lparam>(&lvi)); else // sub-item senddlgitemmessage(hwnd, 2000, lvm_setitem, 0, reinterpret_cast<lparam>(&lvi)); } } } return 0l; case wm_notify: { if (((lpnmhdr)lparam)->code == nm_dblclk) { switch (((lpnmhdr)lparam)->idfrom) { case 2000: // remember, our listview's id { lpnmitemactivate lpnmia = (lpnmitemactivate)lparam; // shift/alt/ctrl/their combination, must not pressed if ((lpnmia->ukeyflags || 0) == 0) { // store item/subitem rectangle rect rc = { 0, 0, 0, 0 }; // helper values, needed handling partially visible items int topindex = listview_gettopindex(lpnmia->hdr.hwndfrom); int visiblecount = listview_getcountperpage(lpnmia->hdr.hwndfrom); // if item vertically partially visible, make visible if ((topindex + visiblecount) == lpnmia->iitem) { // rectangle of above item -> lpnmia->iitem - 1 listview_getsubitemrect(lpnmia->hdr.hwndfrom, lpnmia->iitem - 1, lpnmia->isubitem, lvir_label, &rc); // ensure clicked item visible listview_ensurevisible(lpnmia->hdr.hwndfrom, lpnmia->iitem, false); } else // item visible, ectangle listview_getsubitemrect(lpnmia->hdr.hwndfrom, lpnmia->iitem, lpnmia->isubitem, lvir_label, &rc); rect rcclient = { 0 }; // listview client rectangle, needed if item partially visible getclientrect(lpnmia->hdr.hwndfrom, &rcclient); // item horizontally partially visible -> right side if (rcclient.right < rc.right) { // show whole item listview_scroll(lpnmia->hdr.hwndfrom, rc.right - rcclient.right, 0); // adjust rectangle edit control displayed rc.left -= rc.right - rcclient.right; rc.right = rcclient.right; } // item horizontally partially visible -> left side if (rcclient.left > rc.left) { // show whole item listview_scroll(lpnmia->hdr.hwndfrom, rc.left - rcclient.left, 0); // adjust rectangle edit control displayed rc.right += rcclient.left - rc.left; rc.left = rcclient.left; } // time position edit control, start getting window handle hwnd hwndedit = getdlgitem(hwnd, 5000); // item text , set edit control's text wchar_t text[51]; listview_getitemtext(lpnmia->hdr.hwndfrom, lpnmia->iitem, lpnmia->isubitem, text, 50); edit_settext(hwndedit, text); // select entire text edit_setsel(hwndedit, 0, -1); // map listview client rectangle parent rectangle // edit control can placed above item mapwindowpoints(lpnmia->hdr.hwndfrom, hwnd, (lppoint)&rc, (sizeof(rect) / sizeof(point))); // move edit control setwindowpos(hwndedit, hwnd_top, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, swp_showwindow); // set focus our edit control hwnd previouswnd = setfocus(hwndedit); } } break; default: break; } } } break; case wm_close: ::destroywindow(hwnd); return 0l; case wm_destroy: { ::postquitmessage(0); } return 0l; default: return ::defwindowproc(hwnd, msg, wparam, lparam); } return 0; } // winmain int winapi winmain(hinstance hinstance, hinstance hprevinstance, lpstr lpcmdline, int ncmdshow) { // store hinstance in global variable later use hinst = hinstance; wndclassex wc; hwnd hwnd; msg msg; // register main window class wc.cbsize = sizeof(wndclassex); wc.style = 0; wc.lpfnwndproc = wndproc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinst; wc.hicon = loadicon(hinstance, idi_application); wc.hcursor = loadcursor(null, idc_arrow); wc.hbrbackground = getsyscolorbrush(color_window); wc.lpszmenuname = null; wc.lpszclassname = l"main_window"; wc.hiconsm = loadicon(hinstance, idi_application); if (!registerclassex(&wc)) { messagebox(null, l"window registration failed!", l"error!", mb_iconexclamation | mb_ok); return 0; } // initialize common controls initcommoncontrolsex iccex; iccex.dwsize = sizeof(initcommoncontrolsex); iccex.dwicc = icc_listview_classes | icc_standard_classes; initcommoncontrolsex(&iccex); // create main window hwnd = createwindowex(0, l"main_window", l"grid control", ws_overlappedwindow, 50, 50, 400, 400, null, null, hinstance, 0); showwindow(hwnd, ncmdshow); updatewindow(hwnd); while (getmessage(&msg, null, 0, 0) > 0) { translatemessage(&msg); dispatchmessage(&msg); } return msg.wparam; }
update:
on second thought, method posted earlier wrong. think it's design error use setcapture
in edit-box that, can interfere bunch of other things. going delete old answer , pretend nobody saw it!
your own method fine checking killfocus
, need subclass listview check scroll messages mimic lvn_xxxlabeledit
void hideedit(bool save) { //save or not... showwindow(hedit, sw_hide); } lresult callback editproc... { if (msg == wm_killfocus) hideedit(1); if (msg == wm_char) { if (wparam == vk_escape){ hideedit(0); return 0; } if (wparam == vk_return){ hideedit(1); return 0; } } return defsubclassproc(...); } lresult callback listproc... { if (msg == wm_vscroll || msg == wm_hscroll) hideedit(1); return defsubclassproc(...); }
Comments
Post a Comment