Related
On Windows 10 I have been experimenting with replacing the "Window snap" feature to work better with ultra wide monitors. While I have had no problem capturing the Windows Key+arrow cursors to handle the keyboard shortcut, I now want to detect when another application Window has been dragged to the top/right/left/bottom of the current monitor.
Current code:
#include <iostream>
#include <Windows.h>
HHOOK _hook_keyboard;
KBDLLHOOKSTRUCT kbdStruct;
CONST int HORIZONTAL_SLOTS = 4;
CONST int VERTICAL_SLOTS = 1;
// horizontalPosition/verticalPosition specifies which "slot" starting at 0 to place Window in
// horizontalSlots/verticalSlots specifies how many slots to divide the screen into
void MoveAndResizeActiveWindow(int horizontalPosition, int verticalPosition, int horizontalSlots, int verticalSlots)
{
// get work area on primary monitor
HWND currentWindow = GetForegroundWindow();
if (currentWindow != NULL)
{
HMONITOR currentMonitor = MonitorFromWindow(currentWindow, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(currentMonitor, &monitorInfo))
{
long width = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
long height = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
long snappedWidth = width / horizontalSlots;
long snappedHeight = height / verticalSlots;
long snappedLeft = (snappedWidth * horizontalPosition) + monitorInfo.rcWork.left;
long snappedTop = (snappedHeight * verticalPosition) + monitorInfo.rcWork.top;
MoveWindow(currentWindow, snappedLeft, snappedTop, snappedWidth, snappedHeight, true);
}
}
}
LRESULT __stdcall HookCallbackKeyboard(int nCode, WPARAM wParam, LPARAM lParam)
{
BOOL bEatkeystroke = false;
short keyState;
if (nCode >= 0)
{
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
switch (wParam)
{
case WM_KEYDOWN:
keyState = GetAsyncKeyState(VK_LWIN);
if (keyState)
{
switch (kbdStruct.vkCode)
{
case VK_LEFT:
bEatkeystroke = true;
break;
case VK_RIGHT:
bEatkeystroke = true;
break;
case VK_UP:
bEatkeystroke = true;
break;
case VK_DOWN:
bEatkeystroke = true;
break;
};
};
break;
case WM_KEYUP:
keyState = GetAsyncKeyState(VK_LWIN);
if (keyState)
{
switch (kbdStruct.vkCode)
{
case VK_LEFT:
MoveAndResizeActiveWindow(0, 0, 4, 1);
bEatkeystroke = true;
break;
case VK_RIGHT:
MoveAndResizeActiveWindow(3, 0, 4, 1);
bEatkeystroke = true;
break;
break;
case VK_UP:
MoveAndResizeActiveWindow(1, 0, 4, 1);
bEatkeystroke = true;
break;
case VK_DOWN:
MoveAndResizeActiveWindow(2, 0, 4, 1);
bEatkeystroke = true;
break;
};
}
break;
};
}
if (bEatkeystroke)
{
return 1;
}
else
{
return CallNextHookEx(_hook_keyboard, nCode, wParam, lParam);
}
}
void SetHook()
{
if (!(_hook_keyboard = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallbackKeyboard, NULL, 0)))
{
MessageBox(NULL, L"Failed to install hook on keyboard!", L"Error", MB_ICONERROR);
}
}
int main(int argc, char** argv[])
{
SetHook();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Any suggestions how to identify when Windows have been dragged to a particular location on the screen?
As per advice in replies to original question I have tried used SetWinEventHook with the following code, planning to restrict EVENT_MIN and EVENT_MAX once correct events to watch for worked out.
g_hook_winevent = SetWinEventHook(
EVENT_MIN, EVENT_MAX,
NULL, // Handle to DLL.
HandleWinEvent, // The callback.
0, 0, // Process and thread IDs of interest (0 = all)
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
}
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD dwEventThread, DWORD dwmsEventTime)
{
// process event here
}
While this easily tracks start or end of a Windows move with EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND I can't see an event here that tracks the moving of Window prior to EVENT_SYSTEM_MOVESIZEEND.
While that will work if only good option, ideally I want to be able to detect Window location from start of EVENT_SYSTEM_MOVESIZESTART until EVENT_SYSTEM_MOVESIZEEND completes. Testing with notepad the only event getting raised during the move is EVENT_OBJECT_NAMECHANGE, which seems to constantly trigger during Window move, at least with Notepad. However based on description in documentation I'm not sure if this is suitable for my use case: "An object's Name property has changed. The system sends this event for the following user interface elements: check box, cursor, list-view control, push button, radio button, status bar control, tree view control, and window object. Server applications send this event for their accessible objects."
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);
}
}
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.
I'm trying to change the row text-color of a list-view I made from a resource. For that, I handle NM_CUSTOMDRAW inside my dialog process. It's a modal dialog box, if that matters. According to the documentation,
dwDrawStage should equal CDDS_ITEMPREPAINT after returning CDRF_NOTIFYITEMDRAW. But this is not the case. I'm receiving CDDS_PREPAINT for every item.
What did I do wrong?
This is how I respond to the message:
case WM_NOTIFY:
if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwnd, IDC_List2) && ((LPNMHDR)lParam)->code == NM_CUSTOMDRAW)
{
int result = CDRF_DODEFAULT;
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
switch (lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
result = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT: //never gets executed
lplvcd->clrText = RGB(255, 0, 0);
result = CDRF_NEWFONT;
break;
}
//SetWindowLongPtr(hEdit, DWLP_MSGRESULT, result);
//return TRUE;
return result;
}
break;
these are the properties of the ListView:
IDC_List2,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_NOLABELWRAP | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,205,18,363,197,WS_EX_CLIENTEDGE
I figured out the problem:
WM_NOTIFY is handled inside a dialog so of course I have to return the values accordingly. The issue was, that I used the wrong variable for the window (hEdit in this case).
This is the corrected version if anyone cares:
case WM_NOTIFY:
if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwnd, IDC_List2) && ((LPNMHDR)lParam)->code == NM_CUSTOMDRAW)
{
int result = CDRF_DODEFAULT;
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
switch (lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
result = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
lplvcd->clrText = RGB(255, 0, 0);
result = CDRF_NEWFONT;
break;
}
SetWindowLongPtr(hDialog, DWLP_MSGRESULT, result);
return TRUE;
}
break;
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...)