Блокировка клавиш 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);
}
}