简体   繁体   English

如何在WinAPI中的ESC keydown上取消Tree-View控件中的项目标签编辑

[英]How to cancel item label editing in Tree-View control upon ESC keydown in WinAPI

I have a dialog box with a Tree-View control where the user can edit the item labels. 我有一个带有Tree-View控件的对话框,用户可以在其中编辑项目标签。 I want the user to be able to cancel the label edit by pressing ESC key. 我希望用户能够通过按ESC键取消标签编辑。

The problem is that pressing ESC closes the dialog window immediately. 问题是按ESC会立即关闭对话窗口。

I have tried getting the handle to the EditBox control by a TreeView_GetEditControl() call upon TVN_BEGINLABELEDIT message and subclassing it to trap the ESC key, but when I do that, typing in edit box becomes impossible. 我试图通过TVN_BEGINLABELEDIT消息上的TreeView_GetEditControl()调用来获取EditBox控件的句柄,并将其子类化以捕获ESC键,但是当我这样做时,输入编辑框变得不可能。

What is the problem? 问题是什么?

Here is the relevant code: 这是相关代码:

INT_PTR CALLBACK DlgProc(HWND hWnd, UINT message, 
                         WPARAM wParam, LPARAM lParam) {
    switch(message) {
        //...

        case WM_NOTIFY:
        {
            LPNMHDR pNmHdr = (LPNMHDR)lParam;
            switch(pNmHdr->code) {
                case TVN_BEGINLABELEDIT:
                {
                    HWND hwndTV = (HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA); // stored handle to Tree-View ctl
                    HWND hWndEditBox = TreeView_GetEditControl(hwndTV);

                    // subclass edit box
                    TreeViewGlobals::g_wpOrigEditBoxProc =
                        (WNDPROC)SetWindowLongPtr(hWndEditBox, 
                                                  GWLP_WNDPROC, (LONG_PTR)EditBoxCtl_SubclassProc);
                    break;
                }
                case TVN_ENDLABELEDIT:
                {
                    SetWindowLongPtr(hWnd, DWLP_MSGRESULT, (LONG)TRUE); // accept edit
                    return TRUE;
                }
                default:
                    break;
            }
        }
        default:
            break;
    }

    return FALSE;
}

INT_PTR CALLBACK EditBoxCtl_SubclassProc(HWND hWndEditBox, UINT message,
                                         WPARAM wParam, LPARAM lParam) {
    switch(message) {
        HANDLE_MSG(hWndEditBox, WM_GETDLGCODE, EditBoxCtl_OnGetDlgCode);
        HANDLE_MSG(hWndEditBox, WM_KEYDOWN, EditBoxCtl_OnKey); // does not receive WM_KEYDOWN for ESC unless I handle WM_GETDLGCODE above
        default:
            break;
    }

    return CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc, 
                          hWndEditBox, message, wParam, lParam);
}

UINT EditBoxCtl_OnGetDlgCode(HWND hWndEditBox, LPMSG lpmsg) {
    if(lpmsg) {
        if(lpmsg->message == WM_KEYDOWN && lpmsg->wParam == VK_ESCAPE) {
            return DLGC_WANTMESSAGE;
        }
    }

    return 0;
}

void EditBoxCtl_OnKey(HWND hWndEditBox, UINT vk, BOOL fDown, 
                      int cRepeat, UINT flags) {
    switch(vk) {
        case VK_ESCAPE:
                Beep(4000, 150); // never beeps
            break;
        default:
            break;
    }
}

PS I noticed that when I remove WM_GETDLGCODE handler in EditBoxCtl_SubclassProc() , it becomes possible to type in the edit box again, but then I can't trap WM_KEYDOWN for ESC key from that procedure. PS我注意到当我在EditBoxCtl_SubclassProc()删除WM_GETDLGCODE处理程序时,可以再次输入编辑框,但是我不能从该程序中为ESC键捕获WM_KEYDOWN。

Below is the solution that I found. 以下是我找到的解决方案。 The trick seems to be calling the original control proc with WM_GETDLGCODE intercepted in subclass proc, storing the return value and then returning it with DLGC_WANTALLKEYS or DLGC_WANTMESSAGE flag set to prevent system from further processing the keystroke. 诀窍似乎是调用原始控制proc,WM_GETDLGCODE在子类proc中截获,存储返回值,然后返回DLGC_WANTALLKEYS或DLGC_WANTMESSAGE标志设置,以防止系统进一步处理击键。

The upside to this approach is that pressing ESC cancels editing and reverts the item label to its original text, and pressing ENTER while editing no longer just closes the dialog(which was another problem) without any additional code to handle those cases. 这种方法的好处是按ESC取消编辑并将项标签恢复为原始文本, 在编辑时按ENTER不再关闭对话框(这是另一个问题),而没有任何其他代码来处理这些情况。

Here is the code that works: 这是有效的代码:

INT_PTR CALLBACK EditBoxCtl_SubclassProc(HWND hWndEditBox, UINT message,
                                         WPARAM wParam, LPARAM lParam) {
    switch(message) {
        //HANDLE_MSG(hWndEditBox, WM_GETDLGCODE, EditBoxCtl_OnGetDlgCode);  // can't use this: need wParam and lParam for CallWindowProc()

        case WM_GETDLGCODE: {   
            INT_PTR ret = CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc, 
                                         hWndEditBox, message, wParam, lParam);
            MSG* lpmsg = (MSG*)lParam;  
            if(lpmsg) {
                if(lpmsg->message == WM_KEYDOWN && 
                  (lpmsg->wParam == VK_ESCAPE || lpmsg->wParam == VK_RETURN) ) 
                {
                    return ret | DLGC_WANTALLKEYS;
                }
            }

            return ret;
        }

        default:
            break;
    }

    return CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc, 
                          hWndEditBox, message, wParam, lParam);
}

The problem is that the modal dialog has its own message loop and its own translation with IsDialogMessage. 问题是模态对话框有自己的消息循环和它自己的IsDialogMessage转换。 Using the MFC I would say, just use PreTranslateMessage but this isn't available in plain WinApi. 我会说使用MFC,只需使用PreTranslateMessage,但在普通的WinApi中不可用。 You don't have access to the internal message loop and the keyboard interface. 您无权访问内部消息循环和键盘界面。

So the Escape key is handled inside the message loop. 因此,Escape键在消息循环内处理。 And causes a WM_COMMAND message with IDCANCEL to be sent. 并导致发送带有IDCANCEL的WM_COMMAND消息。 (See the MSDN specs about dialogs ) (请参阅有关对话框MSDN规范

Maybe the easiest way is to interrcept the WM_COMMAND message sent to the dialog, check if who has the focus and if the inplace edit control has the focus you just set the focus back to the tree control and eat forget the IDCANCEL and don't close the dialog. 也许最简单的方法是拦截发送到对话框的WM_COMMAND消息,检查是否有焦点,如果就地编辑控件有焦点,你只需将焦点设置回树控件并吃掉忘记IDCANCEL并且不要关闭对话框。

you need remember the tree-view hwnd when you receive TVN_BEGINLABELEDIT (in class member, associated with dialog) and zero it when you receive TVN_ENDLABELEDIT . 当你收到TVN_BEGINLABELEDIT (在类成员中,与对话框相关联)时,你需要记住树视图hwnd,并在收到TVN_ENDLABELEDIT时将其TVN_ENDLABELEDIT when user press esc or enter in modal dialog box - you receive WM_COMMAND with IDCANCEL (on esc ) or IDOK ( on enter ). 当用户按下esc进入模态对话框时 - 你会收到带有IDCANCEL (在esc上 )或IDOK (在输入时 )的WM_COMMAND you need check saved tree-view hwnd and if it not 0 - call TreeView_EndEditLabelNow 你需要检查保存的树视图hwnd,如果它不是0 - 调用TreeView_EndEditLabelNow

    switch (uMsg)
    {
    case WM_INITDIALOG:
        m_hwndTV = 0;
        break;
    case WM_NOTIFY:
        switch (reinterpret_cast<NMHDR*>(lParam)->code)
        {
        case TVN_BEGINLABELEDIT:
            m_hwndTV = reinterpret_cast<NMHDR*>(lParam)->hwndFrom;
            return TRUE;
        case TVN_ENDLABELEDIT:
            m_hwndTV = 0;
            //set the item's label to the edited text
            SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
            return TRUE;
        }
        break;
    case WM_CLOSE:
        EndDialog(hwndDlg, 0);
        break;
    case WM_COMMAND:
        switch (wParam)
        {
        case IDCANCEL:
            if (m_hwndTV)
            {
                TreeView_EndEditLabelNow(m_hwndTV, TRUE);
            }
            else
            {
                EndDialog(hwndDlg, IDCANCEL);
            }
            break;
        case IDOK:
            if (m_hwndTV)
            {
                TreeView_EndEditLabelNow(m_hwndTV, FALSE);
            }
            else
            {
                EndDialog(hwndDlg, IDOK);
            }
            break;
        }
        break;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM