Блокировка клавиш ESC и Enter в немодальном диалоговом окне (Win32, не MFC)

На эту тему написано несколько статей, но ни одна из них не сработала в моем случае. Я пишу следующее с помощью Win32 (без MFC). Цель состоит в том, чтобы предотвратить ESC или же ENTER ключи от закрытия немодального диалогового окна.

Вот шаблон диалога:

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, Так что я думаю, что это не элемент управления редактирования, который делает это.

3 ответа

Решение

Если мы хотим, чтобы закрыть диалоговое окно только нажав кнопку Закрыть X кнопка в системном меню (или ALT+F4) и отключить рядом ESC а также ENTER ключ - все что нам нужно - звоните DestroyWindow когда процесс (WM_SYSCOMMAND, SC_CLOSE) и ничего не делать на (WM_COMMAND, IDCANCEL, IDOK), нам не нужен специальный цикл обработки сообщений или подкласс каких-либо элементов управления. и не иметь кнопок с идентификатором IDOK/ IDCANCEL в диалоге

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;
        ....
        }
    }
    ....
}

IsDialogMessage переводит ключ ESC в WM_COMMAND IDCANCEL и войти в 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...
}

RbMm имеет хорошее решение. Поэтому я отмечу это как ответ.

В ожидании ответа я смог настроить свой первоначальный цикл сообщений и придумал собственное решение. Так и здесь.

Блокировка Enter Ключ прост. Все, что мне нужно было сделать, это определить кнопку по умолчанию (либо в диалоговом редакторе VS, либо отправив DM_SETDEFID сообщение), и он будет обрабатывать все Enter нажатия клавиш.

Суть для блокировки ESC нажатие клавиш не должно было передавать какие-либо сообщения клавиатуры, содержащие ESC нажатие клавиш на любые общие элементы управления (или дочерние элементы диалогового окна). Как указано в комментариях @IInspectable, некоторые из этих общих элементов управления довольно старые и не реализованы в соответствии со спецификацией. Более того, Microsoft обычно не исправляет старые ошибки пользовательского интерфейса и просто называет их функциями.

Таким образом, я выполнил исправление с помощью следующей модификации, которая перенаправит (или отразит) все такие сообщения на мой DlgProc который также имеет преимущество перед кодом RbMm в том, что он также позволяет мне создать собственную обработку для ESC нажатия клавиш.

Также устранено 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);
    }
}
Другие вопросы по тегам