Block ESC and Enter keys in modeless dialog box (Win32, non-MFC) - c++

There're some articles written on this subject, but none of them worked in my case. I'm writing the following using Win32 (no MFC). The goal is to prevent ESC or ENTER keys from closing the modeless dialog box.
Here's the dialog template:
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
To trap those two keys, I change the message loop to this:
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);
}
}
And ghActiveModelessDlg is set from within DlgProc for the modeless dialog as such:
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;
}
This works ... in most cases. Except this one.
Here's the sequence. Put the focus into the multi-line edit box, then hit any letter/number key and then ESC:
It will then close the dialog.
Any idea how can it pass my override code above?
PS. Interesting observations.
1) If I just hit ESC first, my code traps it. It's only when I hit some other key and then ESC it fails.
2) If I comment out the line that calls IsDialogMessage (and a subsequent continue) it stops accepting ESC. So my guess is that it's not the edit control that does this.

if we want let close dialog only by clicking close X button in system menu (or by ALT+F4) and disable close by ESC and ENTER key - all what we need - call DestroyWindow when process (WM_SYSCOMMAND, SC_CLOSE) and do nothing on (WM_COMMAND, IDCANCEL, IDOK). we not need special message loop or subcluss any controls. and not have buttons with IDOK/ IDCANCEL id in dialog
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 translates ESC key into WM_COMMAND IDCANCEL and ENTER into WM_COMMAND IDOK. To suppress default handling (closing dialog), process them in your dialog procedure:
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 has a good solution. So I'll mark it as the answer.
While waiting for a reply I was able to adjust my original message loop and came up with my own solution. So here it is.
Blocking the Enter key is easy. All I needed to do was to define a default button (either in the dialog editor in VS, or by sending the DM_SETDEFID message) and it will handle all the Enter keystrokes.
The gist for blocking ESC keystrokes was not to pass any keyboard messages bearing the ESC keystroke to any common controls (or children of the dialog window.) As #IInspectable quoted in the comments, some of those common controls are quite old and are not implemented up to spec. Moreover, Microsoft usually doesn't fix old UI bugs and simply calls them features.
So I accomplished the fix by the following modification that will re-route (or reflect) all such messages to my DlgProc, which also has the benefit over RbMm's code in that it also allows me to come up with my own processing for the ESC keystrokes.
Also eliminated goto for goto-purists:
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);
}
}

Related

Question about WM_NOTIFY message for SysLink controls

In this Microsoft example on how to process the WM_NOTIFY message for a SysLink control they have this code,g_hLinkbeing the handle of the SysLink control:
// g_hLink is the handle of the SysLink control.
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case NM_CLICK: // Fall through to the next case.
case NM_RETURN:
{
PNMLINK pNMLink = (PNMLINK)lParam;
LITEM item = pNMLink->item;
if ((((LPNMHDR)lParam)->hwndFrom == g_hLink) && (item.iLink == 0))
{
ShellExecute(NULL, L"open", item.szUrl, NULL, NULL, SW_SHOW);
}
else if (wcscmp(item.szID, L"idInfo") == 0)
{
MessageBox(hDlg, L"This isn't much help.", L"Example", MB_OK);
}
break;
}
}
break;
I don't uderstand why the (((LPNMHDR)lParam)->hwndFrom == g_hLink) condition is not needed for the else clause?
Or is this simply an error in the example?

Button not updating

I have an application, a game, that I've been working on and I stuck with a button not been (visually) updated.
The button is a pause button. When the app starts it is disabled (has WS_DISABLED style), but when the user starts a game, I simply remove that style responsable to disable it (WS_DISABLED).
The problem is: the button remains (visually) with the disabled style (when removing the style).
Or remains (visually) with the enable style (when adding the style).
However, the button is correctly updated when I click in it. I assume that this is a repaint/update issue.
I tried to repaint the window (no sucess):
RedrawWindow(hWnd,NULL,NULL,RDW_ALLCHILDREN | RDW_UPDATENOW);
Here is the code fragment located in WinProc function:
switch (message) {
case WM_CREATE:
CreateControls(hWnd);
break;
case WM_COMMAND:
{
switch (HIWORD(wParam)) {
case BN_CLICKED:
switch (LOWORD(wParam)) {
case 3:
{
char text[50];
GetDlgItemTextA(hWnd, 3, text, 50);
HWND pauseB = GetDlgItem(hWnd, 4);
LONG style = GetWindowLong(pauseB, GWL_STYLE);
if (strncmp(text, "Start", strlen(text)) == 0) {
SetDlgItemTextA(hWnd, 3, "Stop");
SetWindowLong(pauseB, GWL_STYLE, style & ~WS_DISABLED);
std::thread bt(RunGame, hWnd);
bt.detach();
} else {
SetDlgItemTextA(hWnd, 3, "Start");
SetWindowLong(pauseB,GWL_STYLE,style | WS_DISABLED);
}
RedrawWindow(hWnd,NULL,NULL,RDW_ALLCHILDREN | RDW_UPDATENOW);
//SendMessage(hWnd, WM_PAINT, NULL, NULL);
}
default:
break;
}
break;
default:
break;
}
}
break;
// other cases...
}
I may confess that I don't know much about c++. So sorry for my mistakes.

Breaking a while loop in a called Function

On clicking the start button the function RunB() executes the while loop . I need to break the while loop execution midway using the Stop Button .
switch (msg)
{
case WM_CREATE:
CreateWindow(TEXT("button"), TEXT("Browser"),
WS_VISIBLE | WS_CHILD,
30, 100, 80, 25,
hwnd, (HMENU)1, NULL, NULL);
CreateWindow(TEXT("button"), TEXT("Stop"),
WS_VISIBLE | WS_CHILD,
30, 200, 80, 25,
hwnd, (HMENU)2, NULL, NULL);
break;
case WM_COMMAND:
if (LOWORD(wParam) == 1) {
Obj.RunB();
}
break;
if (LOWORD(wParam) == 2) {
//Code to break the while loop
}
break;
Code for the Called Function RunB:
void RunB(){
while(n<15){
//Some statements here:
}
How do I break the while loop using the Stop Button ?
The event driven action (i.e. pressing a button), shouldn't engage a looping or business logic.... Otherwise the whole windows will be halt until it gets the synchronous response.
If you find you have some modal action that is going to take a long time to complete and you absolutely can't break it up, then you need to periodically process messages from within your modal action.
For example:
while(n<15){
DoAPieceOfWork(n);
FlushPendingMessages();
}
And FlushPendingMessages would be implemented something like:
void FlushPendingMessages(){
MSG msg;
while(PeekMessage(&msg,0,0,0,PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
This will keep your application mostly responsive. Normaly you would also display a modeless dialog at the same time, so that people know your application is busy and cannot be interacted with.
To be more correct, before calling TranslateMessage/DispatchMessage you must also check for WM_QUIT and repost it to terminate your app, and you can also explicitly check for WM_APP+x messages- using code something like this:
#define WM_APP_QUITLOOP (WM_APP+1)
// use this from anywhere to tell your processing loop to quit
PostMessage(NULL,WM_APP_QUITLOOP,0,0L);
BOOL FlushPendingMessages(){
MSG msg;
while(PeekMessage(&msg,0,0,0,PM_REMOVE)){
switch(msg.message){
case WM_APP_QUITLOOP:
return FALSE;
case WM_QUIT:
PostQuitMessage(msg.wParam);
return FALSE;
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return TRUE;
}
Your processing loop would need to check the return value from FlushPendingMessages and abort if its FALSE of course.
Edit: as per #IInspectable's comment.

Using WINAPI how do I change the value of a checkbox button?

Currently I have a checkbox created in WM_CREATE:
hwndButtonPollFlag =
CreateWindow(
TEXT("BUTTON"),
TEXT(sA.getMonitor(monitorSelected)->szDevice),
WS_CHILD | WS_VISIBLE | SS_WHITERECT | BS_CHECKBOX,
0,
0,
0,
0,
hwnd,
(HMENU)IDB_PollFlag,
hInstance,
NULL);
I am trying to change it's value whenever another button is clicked with:
if (sA.getScreenArray(monitorSelected)->getPollFlag())
{
SetWindowLongPtr(hwndButtonPollFlag, GCL_STYLE, WS_VISIBLE | BST_CHECKED);
}
else
{
SetWindowLongPtr(hwndButtonPollFlag, GCL_STYLE, WS_VISIBLE | BST_UNCHECKED);
}
SetWindowText(hwndButtonPollFlag, TEXT(sA.getMonitor(monitorSelected)->szDevice));
This does change the text displayed next to the checkbox but not the actual state of the button. Also I would like the checkbox to have only two states (checked or unchecked) is there any other way to create that effect other than in the button return have something along the lines of:
switch (HIWORD(wParam))
{
case BST_CHECKED:
sA.getScreenArray(monitorSelected)->setPollFlag(true);
return 0;
case BST_INDETERMINATE:
if (sA.getScreenArray(monitorSelected)->getPollFlag())
{
SetWindowLongPtr(hwndButtonPollFlag, GCL_STYLE, WS_VISIBLE | BST_UNCHECKED);
}
else
{
SetWindowLongPtr(hwndButtonPollFlag, GCL_STYLE, WS_VISIBLE | BST_CHECKED);
}
return 0;
case BST_UNCHECKED:
sA.getScreenArray(monitorSelected)->setPollFlag(false);
return 0;
}
EDIT: As Mark Ransom said I used messages with the BM_GETCHECK and BM_SETCHECK flag as so:
case IDB_MONITOR:
monitorSelected = LOWORD(lParam);
if (sA.getScreenArray(monitorSelected)->getPollFlag())
{
SendMessage(hwndButtonPollFlag, BM_SETCHECK, BST_CHECKED, NULL);
}
else
{
SendMessage(hwndButtonPollFlag, BM_SETCHECK, BST_UNCHECKED, NULL);
}
SetWindowText(hwndButtonPollFlag, TEXT(sA.getMonitor(monitorSelected)->szDevice));
return 0;
case WM_COMMAND:
//sA.getScreenArray(monitorSelected)->setPollFlag(LOWORD(lParam));
switch (LOWORD(wParam))
{
case IDB_PollFlag:
if (SendMessage(GetDlgItem(hwnd, IDB_PollFlag), BM_GETCHECK, 0, 0) == BST_CHECKED)
{
SendMessage(hwndButtonPollFlag, BM_SETCHECK, BST_CHECKED, NULL);
sA.getScreenArray(monitorSelected)->setPollFlag(true);
}
else {
SendMessage(hwndButtonPollFlag, BM_SETCHECK, BST_UNCHECKED, NULL);
sA.getScreenArray(monitorSelected)->setPollFlag(false);
}
break;
}
return 0;
You need to send the BM_SETCHECK message.
SendMessage(hwndButtonPollFlag, BM_SETCHECK, BST_CHECKED, 0);
BST_CHECKED and BST_UNCHECKED aren't window styles, they're simply flag values used by the CheckDlgButton and IsDlgButtonChecked API functions. CheckDlgButton is the function to call to change its state.
(Or, you can send BM_SETCHECK and BM_GETCHECK messages directly to the button for the same effect).

C++ Accelerator keys do not work

I am having trouble getting Accelerators to work. I am using C++.
After my window is setup and shown.
MENUITEMINFOW mAbout;
mAbout.cbSize = sizeof(MENUITEMINFO);
mAbout.fMask = MIIM_TYPE | MIIM_ID;
mAbout.wID = (UINT) ID_ABOUT;
mAbout.fType = MFT_STRING;
mAbout.dwTypeData = (LPWSTR)L"&About";
InsertMenuItemW(HelpMenu, 0, TRUE, &mAbout);
My menu is working just fine, and calls my "About" box, no issue there.
Now, before the message loop, I load the accelerators:
// Load accelerators.
HACCEL hAccelerators = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR));
Then my main message loop:
while(GetMessageW(&msg, NULL, 0, 0) > 0) {
if (! TranslateAcceleratorW(msg.hwnd, hAccelerators, &msg)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
My WndProc Message handle (Again, works from the menu)
case WM_COMMAND: {
if (HIWORD(wParam) == 0) {
if (LOWORD(wParam) == 101) {
testDialog(hInstance ,hWnd,(LPSTR)"Testing");
}
if (LOWORD(wParam) == ID_ABOUT) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_ABOUTDIALOG), hWnd, &AboutDialogProc);
return 0;
}
}
break;
}
My resource.rc file:
//
// Accelerator resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDR_ACCELERATOR ACCELERATORS
{
"a", ID_ABOUT, VIRTKEY, ALT
}
and my resource.h file:
#define IDR_ACCELERATOR 122
#define ID_ABOUT 401
And... well, Alt-a does not bring up the about box. I have walked all over the Microsoft
Website, and was vary careful, but I can't find anything glaring I am doing different.
I am on Windows 7 (64bit) using MinGW and compiling in the application for Unicode.
Everything else works but this, what am I missing???
Found it!
From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646373(v=vs.85).aspx
To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.
This was the issue, I was checking wParam for a value of 0, I didn't catch that it is 1 if sent by TranslateAcceleratorW
if (HIWORD(wParam) == 0) {
if (LOWORD(wParam) == 101) {
testDialog(hInstance ,hWnd,(LPSTR)"Testing");
}
if (LOWORD(wParam) == ID_ABOUT) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_ABOUTDIALOG), hWnd, &AboutDialogProc);
return 0;
}
} else if (HIWORD(wParam) == 1) { // Accelerator input
if (LOWORD(wParam) == ID_ABOUT) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_ABOUTDIALOG), hWnd, &AboutDialogProc);
return 0;
}
}
Geeze, that was a tiny detail.
in your resource.rc, try to remove VIRTKEY.
You should try to put 0x41 instead of "a" in your .rc
It's the ascii code for 'A' (uppercase...)