I have a main window in a process that is not owned by the program I'm creating. I'm using a Windows Hook to inject a DLL into this process for the purpose of adding a child window to this main window.
My end goal was to create a WS_EX_LAYERED window that allows me to create an internal colored border but allow the center portion to be transparent and allow mouse clicks through. This part works perfectly.
WNDCLASS wndClass = {};
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = OverlayProc;
wndClass.hInstance = g_TargetInstance;
wndClass.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 255, 255));
wndClass.lpszClassName = "OVERLAY";
RegisterClass(&wndClass);
g_Window = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT, "OVERLAY", nullptr,
WS_CHILDWINDOW, rect.left, rect.top, rect.right+1, rect.bottom+1, data->hwnd, nullptr, g_TargetInstance, nullptr);
SetLayeredWindowAttributes(g_Window, RGB(0, 255, 255), 0, LWA_COLORKEY);
ShowWindow(g_Window, SW_SHOW);
UpdateWindow(g_Window);
The 2nd part to this is a I wanted to conditionally block all mouse input to the parent window. I couldn't do this with the transparent portion of the WS_EX_LAYERED window so I tried creating a 2nd transparent STATIC control as a child of the main window but this doesn't block mouse input either.
I'm also sending simulated mouse clicks to the parent window through calls to PostMessage, passing WM_LBUTTONDOWN and WM_LBUTTONUP. How could I block all mouse input to the parent window via a transparent window?
It appears this is not possible to do with a simple transparent window drawn over sibling controls. What I ended up doing was using SetWindowHookEx to add a WH_GETMESSAGE hook into the process from which I use to replace the main window's WndProc function and intercept mouse messages. I tag my simulated mouse messages with a specific value in the wParam argument so the proc will now it was simulated and removes that value, passing it along to the parent window.
If it does not detect my "tag" value in the click message, it will swallow the mouse message and not pass it along to the original WndProc function.
Injected WndProc replacement
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
wParam -= 11141008;
if (wParam != MK_LBUTTON && !g_Paused)
return 0;
break;
case WM_LBUTTONUP:
wParam -= 11141008;
if (wParam != 0 && !g_Paused)
return 0;
break;
case WM_MOUSEHOVER:
case WM_MOUSEMOVE:
if (!g_Paused)
return 0;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
Snippet from Windows Hook function
//...
switch (data->message)
{
case (WM_USER + 1):
{
g_Paused = FALSE;
//...
SetWindowSubclass(data->hwnd, WndProc, 1, 0);
break;
}
case (WM_USER + 2):
{
RemoveWindowSubclass(data->hwnd, WndProc, 1);
//...
break;
}
}
//...
The code inside the window hook function is used to subclass the main process window and inject my own WndProc function which in turn processes mouse input the way I want.
This is the code used to "simulate" mouse clicks without physically clicking in the window. Note the added value to wParam to identify this click as simulated and not generated by the user.
void Window::LeftClick(DWORD x, DWORD y, DWORD delayMillis)
{
LPARAM lparam = MAKELPARAM(x, y);
lock_guard<mutex> lock(this->m_ClickMutex);
PostMessage(this->m_Window, WM_LBUTTONDOWN, 11141008 + MK_LBUTTON, lparam);
this_thread::sleep_for(std::chrono::milliseconds(delayMillis));
PostMessage(this->m_Window, WM_LBUTTONUP, 11141008, lparam);
}
Also, just for the person in the comments who was ridiculing my choice of the word simulated and the added criticism for using PostMessage to simulate keyboard input, here is my keyboard input test method which (for my purposes) works flawlessly and very reliably
void GameWindow::KeyPress(UINT vkCode) const
{
UINT scanCode = MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);
LPARAM lparam1 = MAKELPARAM(1, scanCode);
LPARAM lparam2 = MAKELPARAM(1, 0xC000 | scanCode);
PostMessage(this->m_Window, WM_KEYDOWN, vkCode, lparam1);
this_thread::sleep_for(chrono::milliseconds(25));
PostMessage(this->m_Window, WM_KEYUP, vkCode, lparam2);
}
Related
So my program works, all apart from one thing, I would like for my button, 'pushBtn' , aka BTN_PUSH_TALK , to send a BN_PUSHED or BN_UNPUSHED message so I can handle it accordingly.
Following steps online, as well as trial and improvement, right now the only response I ever get is once I am done holding / clicking the button.
pushBtn = CreateWindowEx(0, L"BUTTON", L"TALK", WS_CHILD |
WS_VISIBLE |
BS_DEFPUSHBUTTON , 0 , 290 , 50, 50,
hWnd,(HMENU)BTN_PUSH_TALK, GetModuleHandle(NULL), NULL);
Handler (or at least what matters) :
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
bool asd;
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case BTN_PUSH_TALK:
switch (HIWORD(wParam))
{
case BN_UNPUSHED:
if (connected && inputChoiceStr == "Push To Talk") {
tplug->setDuck(false);
}
break;
case BN_PUSHED:
if (connected && inputChoiceStr == "Push To Talk") {
tplug->setDuck(true);
}
break;
}
break;
I expected once i clicked and held down the button , that the BN_PUSHED case would be entered, however it is not.
On letting go, I expect the BN_UNPUSHED case to be entered, but this was not the case either.
case BTN_PUSH_TALK is reached, meaning the button is identifiable, however the switch case within this block of code is never reached.
Buttons send WM_COMMAND on click. To achieve a push/release notification you must subclass the button class (SetWindowLongPtr() with GWLP_WNDPROC) and then handle WM_LBUTTONDOWN and WM_LBUTTONUP in your new Window Proc.
If I'm reading the question right, your goal is to get notifications when a standard push button is initially pushed by the user, whereas standard notification behavior of buttons only posts WM_COMMANDs on "clicks" where a click is the whole mouse down plus mouse up sequence.
Historically in order to get the BN_PUSHED and BN_UNPUSHED notifications in your WM_COMMAND handler you had to use the BS_NOTIFY window style when creating the button. However, if you read the documentation for BN_PUSHED or BN_UNPUSHED you will see
This notification code is provided only for compatibility with 16-bit versions of Windows earlier than version 3.0. Applications should use the BS_OWNERDRAW button style and the DRAWITEMSTRUCT structure for this task.
These were very old notifications that from what I can tell are not just deprecated but no longer even supported. You can do, however, as the documentation suggests: use an owner drawn button i.e. a button created with the BS_OWNERDRAW style.
This turns out to be more difficult than just creating the button with BS_NOTIFY turned on, because the button will no longer perform default painting by itself. Given this added chore, I'd recommend not doing it this way unless you want to custom paint your buttons anyway -- unless you happen to want some nonstandard visual look-and-feel for these buttons as well as nonstandard notification behavior. Otherwise, I would probably just do Win32 subclassing as someone else suggested to trap WM_LBUTTONDOWN etc. and then call the standard button WNDPROC after doing some action on the events i cared about.
Anyway the minimal owner drawn button that reports button down and button up events is like the following. (I post the button events as custom messages but you could do whatever you wish there)
#include <windows.h>
#define BTN_ID 101
#define WM_PUSHBUTTONDOWN WM_APP + 1
#define WM_PUSHBUTTONUP WM_APP + 2
HINSTANCE g_instance = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
g_instance = hInstance;
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND);
wc.lpszClassName = L"owner_draw_btn";
if (!RegisterClass(&wc))
return -1;
if (!CreateWindow(wc.lpszClassName, L"foobar", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL))
return -1;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT HandleDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
auto* dis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
if (dis->CtlType != ODT_BUTTON)
return 0;
auto style = (dis->itemState & ODS_SELECTED) ?
DFCS_BUTTONPUSH | DFCS_PUSHED :
DFCS_BUTTONPUSH;
auto rect = &dis->rcItem;
DrawFrameControl(dis->hDC, rect, DFC_BUTTON, style);
TCHAR text[512];
auto n = GetWindowText(dis->hwndItem, text, 512);
DrawText(dis->hDC, text, n, rect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
if (dis->itemAction == ODA_SELECT) {
PostMessage(
hWnd,
(dis->itemState & ODS_SELECTED) ? WM_PUSHBUTTONDOWN : WM_PUSHBUTTONUP,
dis->CtlID,
0
);
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
CreateWindow(
L"button", L"foobar",
BS_OWNERDRAW | WS_CHILD | WS_VISIBLE,
10, 10, 150, 35, hWnd,
(HMENU) BTN_ID,
g_instance,
0
);
return 0;
case WM_DRAWITEM:
return HandleDrawItem(hWnd, wParam, lParam);
case WM_PUSHBUTTONDOWN:
OutputDebugString(L"Button down event\n");
break;
case WM_PUSHBUTTONUP:
OutputDebugString(L"Button up event\n");
break;
case WM_CLOSE:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
The code below, sends a WM_CHANGEUISTATE to the window procedure itself, with the arguments:
LOWORD(wParam) = UIS_CLEAR
HIWORD(wParam) = UISF_HIDEACCEL
lParam = 0x00000000
when the window client area is left clicked with the mouse.
According to this blog by Raymond Chen this should make the mnemonics in the System menu to be shown, when the menu is accessed with the mouse. The paragraph below was extracted from this article:
Clearing a flag shows the corresponding indicator. For example, if you
have a UIS_CLEAR for UISF_HIDEĀFOCUS, that means that you want to show
focus indicators.
In my case, I have a UIS_CLEAR for UISF_HIDEACCEL, meaning that I want to show the menu accelerators.
If you run the code below and click with the mouse left button on the app client area, you should make the accelerators visible in the System menu, even when this menu is accessed with the mouse. But that doesn't happen, i.e., if you activate the system menu with a left click on the window's icon, or with a right click on the window's title bar, the mnemonics in the System menu will not be shown. What am I missing?
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCmdLine, int nCmdShow)
{
WNDCLASSEX wndclassx;
wchar_t szAppName[] = L"WM_CHANGEUISTATE";
wndclassx.cbSize = sizeof(WNDCLASSEX);
wndclassx.style = CS_HREDRAW | CS_VREDRAW;
wndclassx.lpfnWndProc = WndProc;
wndclassx.cbClsExtra = 0;
wndclassx.cbWndExtra = 0;
wndclassx.hInstance = hInstance;
wndclassx.hIcon = 0;
wndclassx.hCursor = LoadCursor(0, IDC_ARROW);
wndclassx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndclassx.lpszClassName = szAppName;
wndclassx.lpszMenuName = nullptr;
wndclassx.hIconSm = 0;
if (!RegisterClassEx(&wndclassx)) return 0;
HWND hWnd = CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, 0);
ShowWindow(hWnd, SW_MAXIMIZE);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONUP:
{
BOOL b;
// Note that in my system (Windows 10) I have:
//
// Control Panel > Ease of Access > Ease of Access Center > Make the keyboard easier
//
// and the option "Underline keyboard shortcuts and access keys" unmarked (the default). Therefore, the value
// returned in b below will be 0x00000000 (FALSE).
SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &b, 0);
// If b = FALSE, the SendMessage() below should underline the accelerators in the System menu, when this menu is
// accessed with the mouse. But that doesn't work. Why?
if( !b ) SendMessage(hwnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL), NULL);
}
break;
// Setting a break in the return below, one can see that WM_CHANGEUISTATE message is being sent to the window and
// passed to DefWindowProc().
case WM_CHANGEUISTATE:
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
This seems like a bug/oversight in Windows. DefWindowProc does not send WM_QUERYUISTATE before displaying the system menu. The menu implementation checks the last input event directly and if it was from a keyboard it displays the underlines.
I tried sending and posting WM_CHANGEUISTATE and WM_UPDATEUISTATE in response to WM_ENTERMENULOOP, WM_INITMENUPOPUP, WM_NCRBUTTONDOWN and WM_SYSCOMMAND without any luck.
The only workaround I was able to come up with is to hack the HTSYSMENU/icon menu by changing SC_MOUSEMENU to SC_KEYMENU:
case WM_SYSCOMMAND:
if ((wParam & 0xFFF0) == SC_MOUSEMENU)
{
return SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, ' ');
}
return DefWindowProc(hwnd, message, wParam, lParam);
SC_KEYMENU has special handling in DefWindowProc and forces underlines if applicable.
This does not handle right-click on the icon, caption nor task bar...
I've a Button made using Win32Api that I want to be able to notify whenever the user put the mouse inside the button rectangle.
I noticed that when user does that the WM_NOTIFY is called but I don't know which flag to use for ensure that the user has the mouse inside it's area.
Here is my button:
HWND Button = CreateWindow("BUTTON", "Test",
WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | BS_NOTIFY,
20, 240, 120, 20,
hwnd, (HMENU)101, NULL, NULL);
And my WndProc:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NOTIFY:
{
//??? Here is where I want to do it
}
case WM_CREATE: //On Window Create
{
wHWND = hwnd;
if (onCreate != NULL)
onCreate(hwnd);
break;
}
case WM_COMMAND: //Command execution
{
//...
break;
}
case WM_DESTROY: //Form Destroyed
{
if (onDestroy != NULL)
onDestroy(hwnd);
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
I don't know if I'm in the right path using WM_NOTIFY or not, maybe there is an easier way to do that. Thank you!
Try catching the WM_SETFOCUS message instead if you want a generic way of tracking focus.
BN_SETFOCUS is sent as WM_COMMAND in the upper 16 bits of WPARAM (HIWORD):
The parent window of the button receives this notification code through the WM_COMMAND message
If you are not talking about focus but just the mouse position then you can use ChildWindowFromPointEx or RealChildWindowFromPoint and a timer.
In my Win32 CPP program, I have defined some Child Window to display various text strings using something like:
hnd_to_this_ch_window = CreateWindow(
L"EDIT",L"Some initial text", WS_VISIBLE | WS_CHILD | ES_LEFT,
position_of_this_window_X,
position_of_this_window_Y,
TEXTOUT_DEFAULT_WIDTH,
TEXTOUT_DEFAULT_HEIGHT,
handle_to_my_parent_window, NULL,
hinstance_variable_used_by_create_window,
NULL )
My problem is that if I click with my mouse to select the text in one of such child windows (to, say, copy it somewhere), the focus of the application goes to this child window and so any keypresses which used to be handled through my main windows CALLBACK (with case WM_KEYDOWN:) are now captured into the child window, where they appear as inputted characters. What magic function do I call to have the focus go back to the parent (so that my WM_KEYDOWN) can work again? I was hoping I could just click on the main Window's title bar and that would take it back to normal, but that isn't working (because, obviously, my program is lacking some extra logic).
Handle the WM_KILLFOCUS message in the window procedure of the window you want to focus, and restore the focus using the SetFocus function.
If you want to focus the window when it is clicked, handle the WM_LBUTTONDOWN message.
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
// Restore the focus when it was lost.
if (Msg == WM_KILLFOCUS) {
SetFocus(hWnd);
// Msg was handled, return zero.
return 0;
}
// Or when the window is clicked.
if (Msg == WM_LBUTTONDOWN) {
SetFocus(hWnd);
// Msg was handled, return zero.
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
case WM_KEYDOWN:
SetFocus(Parent_Hwnd);
return SendMessage(Parent_Hwnd,WM_KEYDOWN,wParam,lParam);
I want to change the background color of a button in runtime.
The problem is, the button does not have a black background which is what my code should produce.
Instead, it looks like is has the arrow of a drop-down control on it.
What exactly am I doing wrong here?
First I subclassed the Button:
// HWND hParent is the parent window
// HINSTANCE hInstance is the current module
HWND h = CreateWindow("Button", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
340, 10, 20, 20,
hParent, NULL, hInstance, NULL);
SetWindowSubclass(h, &MyWndProc, MyButtonId, NULL);
The ID is defined as:
enum
{
MyButtonId = 100,
};
And the subclass procedure:
LRESULT CALLBACK MyWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if( uIdSubclass == MyButtonId )
{
switch( msg )
{
case WM_ERASEBKGND:
{
HDC dc = (HDC)wParam;
SetBkColor(dc, RGB(127,127,127));
return 0;
}
}
}
return DefSubclassProc(hWnd, msg, wParam, lParam);
}
You did not pass the button ID to the CreateWindow function, so your button does not have the ID you think it does.
The SetBkColor does not set backgrounds for buttons. It sets backgrounds for subsequent calls to TextOut.
You probably meant to use BS_OWNERDRAW, not SS_OWNERDRAW.
When you use the owner draw style you have to draw the button background and text and border. You do this in the parent window handler for WM_DRAWITEM. So you don't need to subclass the button at all.