使用可编辑的子项正确处理listview中的子项目编辑(或取消子项目编辑)

介绍:

我正在尝试使用可编辑的子项实现listview控件。 对于项目/子项目的就地编辑,我使用编辑控件。

我相信我已经成功地将编辑控件放置在item / subitem上面。

问题:

我不知道我应该在哪些事件上结束/取消子项目编辑(隐藏编辑控件,设置子项目文本等)以及我该怎么做。

为了澄清,我说的是用户完成/取消现场编辑的时刻。

此时不再需要编辑控件,所以我应该隐藏它(我不喜欢每次都重新创建它;我相信创建它一次然后在需要时显示/隐藏它更有效)。

我的目标是Visual Studio中的属性窗口所具有的行为(请参阅附图,以查看我所指的窗口)。

在此处输入图像描述

当用户按下ESC键/点击另一个窗口/点击滚动条等时,我希望以与此窗口相同的方式实现编辑/取消。

我努力解决这个问题:

使用谷歌,我发现很少的例子,但它们已经陈旧,并没有解决所有相关案例,所以这就是我在这里寻求帮助的原因。

但是,我能够发现我必须考虑的事件之一是EN_KILLFOCUS ,用户按ESC / ENTER键的情况和用户点击编辑控件以外的其他情况。

编辑:

我设法处理ESC和ENTER键,以及用户点击另一个兄弟控件或用ALT + TAB切换窗口时的情况。 我已经更新了SSCCE的相关更改

题:

为了实现网格的默认行为(如果有一个用于Windows应用程序),我必须处理哪些消息/事件?

你还能指出我应该在哪里编辑子项并隐藏编辑控件,我应该在哪里隐藏编辑控件?

编辑:

我唯一的问题是当用户点击列表视图滚动条或主窗口的背景时处理这种情况。 我只是不知道如何处理这个,并希望得到我能得到的所有帮助。

相关信息:

我在Windows 7 x86上使用Visual Studio 2013;

我正在使用原始WinAPI在C ++中开发;

SSCCE:

以下是我到目前为止的解决方案。 我试图彻底评论它,但如果需要更多信息,请留言,我将更新我的post。

 #include  #include  // various listview macros etc #include  #include  // 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 with 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 the focus take it away and give to listview if (GetFocus() == GetDlgItem(GetParent(hwnd), 5000)) SetFocus(hwnd); // use WM_NEXTDLGCTL for dialogbox !!!! break; case WM_NCDESTROY: ::RemoveWindowSubclass(hwnd, ListViewSubclassProc, uIdSubclass); return DefSubclassProc(hwnd, message, wParam, lParam); } return ::DefSubclassProc(hwnd, message, wParam, lParam); } // subclass procedure for 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 this message to 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: { // get listview handle HWND hwndLV = GetDlgItem(GetParent(hwnd), 2000); // get edit control's client rectangle RECT rc = { 0 }; GetClientRect(hwnd, &rc); // since edit control lies inside item rectangle // we can test any coordinate inside edit control's // client rectangle // I chose ( rc.left, rc.top ) MapWindowPoints(hwnd, hwndLV, (LPPOINT)&rc, (sizeof(RECT) / sizeof(POINT))); // get item and subitem indexes LVHITTESTINFO lvhti = { 0 }; lvhti.pt.x = rc.left; lvhti.pt.y = rc.top; ListView_SubItemHitTest(hwndLV, &lvhti); // get 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 to listview // this triggers EN_KILLFOCUS // which will 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 the same font as listview HFONT hf = (HFONT)SendMessage(hwndLV, WM_GETFONT, 0, 0); if (hf) SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hf, (LPARAM)TRUE); // subclass edit control, so we can edit subitem with ENTER, or // cancel editing with 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 some columns LVCOLUMN lvc = { 0 }; lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; for (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 some items LVITEM lvi; lvi.mask = LVIF_TEXT; for (lvi.iItem = 0; lvi.iItem < 10000; lvi.iItem++) { for (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(&lvi)); else // sub-item SendDlgItemMessage(hwnd, 2000, LVM_SETITEM, 0, reinterpret_cast(&lvi)); } } } return 0L; case WM_NOTIFY: { if (((LPNMHDR)lParam)->code == NM_DBLCLK) { switch (((LPNMHDR)lParam)->idFrom) { case 2000: // remember, this was our listview's ID { LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam; // SHIFT/ALT/CTRL/their combination, must not be pressed if ((lpnmia->uKeyFlags || 0) == 0) { // store item/subitem rectangle RECT rc = { 0, 0, 0, 0 }; // helper values, needed for handling partially visible items int topIndex = ListView_GetTopIndex(lpnmia->hdr.hwndFrom); int visibleCount = ListView_GetCountPerPage(lpnmia->hdr.hwndFrom); // if item is vertically partially visible, make it fully visible if ((topIndex + visibleCount) == lpnmia->iItem) { // get the rectangle of the above item -> lpnmia->iItem - 1 ListView_GetSubItemRect(lpnmia->hdr.hwndFrom, lpnmia->iItem - 1, lpnmia->iSubItem, LVIR_LABEL, &rc); // ensure clicked item is visible ListView_EnsureVisible(lpnmia->hdr.hwndFrom, lpnmia->iItem, FALSE); } else // item is fully visible, just get its 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 is horizontally partially visible -> from the right side if (rcClient.right hdr.hwndFrom, rc.right - rcClient.right, 0); // adjust rectangle so edit control is properly displayed rc.left -= rc.right - rcClient.right; rc.right = rcClient.right; } // item is horizontally partially visible -> from the left side if (rcClient.left > rc.left) { // show the whole item ListView_Scroll(lpnmia->hdr.hwndFrom, rc.left - rcClient.left, 0); // adjust rectangle so edit control is properly displayed rc.right += rcClient.left - rc.left; rc.left = rcClient.left; } // it is time to position edit control, we start by getting its window handle HWND hwndEdit = GetDlgItem(hwnd, 5000); // get item text and set it as 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 to parent rectangle // so edit control can be properly placed above the item MapWindowPoints(lpnmia->hdr.hwndFrom, hwnd, (LPPOINT)&rc, (sizeof(RECT) / sizeof(POINT))); // move the edit control SetWindowPos(hwndEdit, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); // set focus to 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 for 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; } 

更新:

第二个想法,我之前发布的方法是错误的。 我认为在编辑框中使用SetCapture是一种设计错误,它会干扰其他一些东西。 我要删除我的旧答案,假装没有人看到它!

你自己的方法可以检查KILLFOCUS ,你只需要ListView的子类来检查滚动消息来模仿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(...); }