在无模式对话框中阻止 ESC 和 Enter 键(Win32,非 MFC)

Posted

技术标签:

【中文标题】在无模式对话框中阻止 ESC 和 Enter 键(Win32,非 MFC)【英文标题】:Block ESC and Enter keys in modeless dialog box (Win32, non-MFC) 【发布时间】:2019-07-27 21:36:27 【问题描述】:

有一些关于这个主题的文章,但没有一篇对我有用。我正在使用 Win32(无 MFC)编写以下内容。目的是防止ESCENTER 键关闭无模式对话框。

这是对话框模板:

IDD_DIALOG_1 DIALOGEX 0, 0, 345, 179
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION ""
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    CONTROL         "New Pt",IDC_CHECK_NEW_PT,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,7,3,39,12
    CONTROL         "Lines",IDC_CHECK_LINES,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,54,3,39,12
    CONTROL         "Curves",IDC_CHECK_CURVES,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,94,3,39,12
    CONTROL         "Ellipses",IDC_CHECK_ELLIPSE,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,134,3,39,12
    CONTROL         "Circles",IDC_CHECK_CIRCLE,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,174,3,39,12
    LTEXT           "Pen Size:",IDC_STATIC,242,7,30,8
    EDITTEXT        IDC_EDIT_PEN_SIZE,275,3,40,14,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER
    CONTROL         "",IDC_SPIN_PEN_SIZE,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,316,3,11,14
    EDITTEXT        IDC_EDIT_SRC,7,19,331,106,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL
END

为了捕获这两个键,我将消息循环更改为:

MSG msg;

// Main message loop:
for(int nR;;)

    nR = ::GetMessage(&msg, nullptr, 0, 0);
    if(!nR)
    
        break;
    
    else if(nR == -1)
    
        //Error
        ASSERT(NULL);
        break;
    

    if(ghActiveModelessDlg)
    
        BOOL bProcessAsDlgMsg = TRUE;

        if(msg.message == WM_KEYDOWN ||
            msg.message == WM_KEYUP)
        
            //Try to trap ESC & Enter keys
            if(msg.wParam == VK_ESCAPE)
            
                //Do not process
                bProcessAsDlgMsg = FALSE;
            
            else if(msg.wParam == VK_RETURN)
                goto lbl_check_enter;
        
        else if(msg.message == WM_CHAR)
        
            //Try to trap ESC & Enter key
            if(msg.wParam == 27)
            
                //ESC - Do not process
                bProcessAsDlgMsg = FALSE;
            
            else if(msg.wParam == '\r')
            
lbl_check_enter:
                //See what window is it
                WCHAR buffClass[256];
                if(::GetClassName(msg.hwnd, buffClass, _countof(buffClass)) &&
                    lstrcmpi(buffClass, L"edit") == 0 &&
                    (::GetWindowLongPtr(msg.hwnd, GWL_STYLE) & ES_WANTRETURN))
                
                    //This is edit ctrl that can handle its own Enter keystroke
                
                else
                
                    //Do not process
                    bProcessAsDlgMsg = FALSE;
                
            
        

        if(bProcessAsDlgMsg)
        
            if(::IsDialogMessage(ghActiveModelessDlg, &msg))
            
                continue;
            
        
    

    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

ghActiveModelessDlg 是在 DlgProc 中设置的,用于无模式对话框:

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

    switch(hDlg)
    
        //...

        case WM_ACTIVATE:
        
            //Needed to ensure that keyboard shortcuts are properly processed in the message loop
            ghActiveModelessDlg = wParam != WA_INACTIVE ? hDlg : NULL;
        
        break;
    

    return 0;

这在大多数情况下都有效。除了这个。

这是顺序。将焦点放在多行编辑框中,然后按任意字母/数字键,然后ESC

然后它将关闭对话框。

知道它如何通过上面的覆盖代码吗?

PS。有趣的观察。

1) 如果我先点击ESC,我的代码会捕获它。只有当我按下其他键然后ESC 它失败了。

2) 如果我注释掉调用IsDialogMessage(以及随后的continue)的行,它将停止接受ESC。所以我的猜测是不是编辑控件执行此操作。

【问题讨论】:

goto lbl_check_enter; -- 请不要这样做。构造你的代码,这样就不需要了(使标签正在寻址一个函数的代码,然后调用该函数)。您正在跳入嵌套else 的中间。 Windows API 已经够难了——引入意大利面条逻辑无济于事。 @PaulMcKenzie:是的,干得好,伙计。你解决了问题! 不确定它是如何解决问题的,但我知道跳到 elseif 块的中间绝不是一个好主意。 Other tricks with WM_GETDLGCODE. 编辑控件一团糟。 Just because you're a control doesn't mean that you're necessarily inside a dialog box可能是相关的。 【参考方案1】:

如果我们只想通过单击系统菜单中的关闭X 按钮(或通过ALT+F4)关闭对话框并通过ESCENTER 键禁用关闭 - 所有我们需要的 - 调用DestroyWindow 时处理(WM_SYSCOMMAND, SC_CLOSE) 并且在(WM_COMMAND, IDCANCEL, IDOK) 上什么也不做。我们不需要特殊的消息循环或包含任何控件。并且在对话框中没有带有 IDOK/IDCANCEL id 的按钮

INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)


    switch (uMsg)
    
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_CLOSE) DestroyWindow(hwndDlg);
        break;
    case WM_COMMAND:
        switch (wParam)
        
        case MAKEWPARAM(IDOK, BN_CLICKED):
        case MAKEWPARAM(IDCANCEL, BN_CLICKED):
            // ignore this
            break;
        ....
        
    
    ....

【讨论】:

【参考方案2】:

IsDialogMessage 将 ESC 键转换为 WM_COMMAND IDCANCEL 并将 ENTER 转换为 WM_COMMAND IDOK。要禁止默认处理(关闭对话框),请在对话框过程中处理它们:

switch (message)

case WM_CLOSE:
    // Handle WM_CLOSE here so it wouldn't generate WM_COMMAND IDCANCEL
    // that would be ignored in WM_COMMAND handler.
    DestroyWindow(hDlg);
    return TRUE;
case WM_COMMAND:
    if ( LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK )
        // Prevent default handling by original dialog procedure.
        return TRUE;
    break;
// other cases...

【讨论】:

所以关于你的第一个例子。我如何区分来自按下ESC 键的IDCANCEL 通知与用户单击标题栏中的X 按钮,或在系统菜单中选择Close 我忘记了这个案子。在将WM_CLOSE 翻译成WM_COMMAND IDCANCEL 之前处理它。我在第一个代码 sn-p 中添加了case WM_CLOSE 处理。 编辑控件子类过程请求所有键,但随后不处理其中一些键,导致系统以默认警告声音响应。 真的更简单。不需要任何子类。如果我们只想通过单击关闭菜单(按钮)或 ALT+F4 来关闭对话框 - 需要在 (WM_SYSCOMMAND, SC_CLOSE) 上调用 DestroyWindow 而在 (WM_COMMAND, IDCANCEL) 上什么也不做 抱歉,我无法将此标记为答案。有很多类似的建议可以做这种事情。不幸的是,尽管将对话框中的每个控件子类化以捕获 ESC 并不是我建议任何人做的事情。不过感谢您的努力。 @RbMm 是对的,有一个更优雅的解决方案。【参考方案3】:

RbMm 有一个很好的解决方案。所以我会把它标记为答案。

在等待回复时,我能够调整我原来的消息循环并提出自己的解决方案。就是这样。

阻止Enter 键很容易。我需要做的就是定义一个默认按钮(在 VS 的对话框编辑器中,或者通过发送DM_SETDEFID 消息),它将处理所有Enter 击键。

阻止ESC 击键的要点是不要将带有ESC 击键的任何键盘消息传递给任何常用控件(或对话框窗口的子项)。作为@IInspectable quoted in the comments,其中一些常用控件是很老,没有按照规范实现。此外,Microsoft 通常不会修复旧的 UI 错误,而只是将它们称为 features

所以我通过以下修改完成了修复,该修改将所有此类消息重新路由(或反映)到我的DlgProc,这也比 RbMm 的代码具有优势,因为它还允许我想出我自己对ESC 击键的处理。

还为 goto-purists 消除了 goto

MSG msg;

// Main message loop:
for(int nR; nR = ::GetMessage(&msg, nullptr, 0, 0);)

    if(nR == -1)
    
        //Error
        ASSERT(NULL);
        break;
    

    //Need special processing for modeless dialogs
    if(ghActiveModelessDlg)
    
        //Try to catch ESC keystrokes
        if(
            ((msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) && msg.wParam == VK_ESCAPE) ||
            (msg.message == WM_CHAR && msg.wParam == 27)
            )
        
            //Was this message sent to the dialog window?
            if(ghActiveModelessDlg != msg.hwnd)
            
                //If no, then reflect it to our dialog window
                ::PostMessage(ghActiveModelessDlg, msg.message, msg.wParam, msg.lParam);

                continue;
            
        
        else
        
            //Dialog's special message-processing
            if(::IsDialogMessage(ghActiveModelessDlg, &msg))
            
                continue;
            
        
    

    //Regular processing
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

【讨论】:

这称为预翻译消息。我建议将对话框处理重构为单独的函数并使用函数指针来激活/停用它。当您决定添加更多无模式对话框时,它将有所帮助,例如从通用对话框中查找对话框。

以上是关于在无模式对话框中阻止 ESC 和 Enter 键(Win32,非 MFC)的主要内容,如果未能解决你的问题,请参考以下文章

如何防止在Enter和Escape键上关闭MFC对话框?

MFC-对话框屏蔽ENTER和ESC防止自动退出程序

MFC中截获最大化最小化消息,取消Esc退出和Enter退出

数据分析必备工具Jupyter notebook使用

开机提示Press Del to enter SETUP ESC to skip memory test 请问如何处理,

jupyter notebook 快捷操作