3

我有一个带有树视图控件的对话框,用户可以在其中编辑项目标签。我希望用户能够通过按 ESC 键取消标签编辑。

问题是按 ESC 会立即关闭对话窗口。

我尝试通过TreeView_GetEditControl()调用 TVN_BEGINLABELEDIT 消息来获取 EditBox 控件的句柄并将其子类化以捕获 ESC 键,但是当我这样做时,在编辑框中输入变得不可能。

问题是什么?

以下是相关代码:

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我注意到当我删除 WM_GETDLGCODE 处理程序时EditBoxCtl_SubclassProc(),可以再次在编辑框中键入,但是我无法从该过程中为 ESC 键捕获 WM_KEYDOWN。

4

3 回答 3

2

以下是我找到的解决方案。诀窍似乎是调用原始控制过程,并在子类过程中截获 WM_GETDLGCODE,存储返回值,然后设置 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.

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);
}
于 2017-09-13T12:17:08.463 回答
0

问题是模态对话框有自己的消息循环和自己的 IsDialogMessage 翻译。使用 MFC 我会说,只需使用 PreTranslateMessage 但这在普通 WinApi 中不可用。您无权访问内部消息循环和键盘界面。

所以 Escape 键是在消息循环中处理的。并导致发送带有 IDCANCEL 的 WM_COMMAND 消息。(请参阅有关对话框的 MSDN 规范

也许最简单的方法是拦截发送到对话框的 WM_COMMAND 消息,检查是否有焦点以及就地编辑控件是否有焦点,您只需将焦点设置回树控件并忘记 IDCANCEL 并且不要关闭对话框。

于 2017-09-13T10:02:03.423 回答
0

您需要在收到时记住树视图 hwnd TVN_BEGINLABELEDIT(在类成员中,与对话框相关联)并在收到时将其归零TVN_ENDLABELEDIT。当用户按下esc或在模式对话框中输入WM_COMMAND时 - 您会收到( IDCANCELon esc ) 或IDOK( on enter )。您需要检查保存的树视图 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;
    }
于 2017-09-13T11:36:34.477 回答