I'm using the following code to create a button and change it's proc:
INT_PTR CALLBACK Proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
//switch( LOWORD(wParam) )
//switch( HIWORD(wParam) )
switch (message)
{
case 200:
case BN_CLICKED:
MessageBox(NULL,NULL,NULL,NULL);
break;
default: return oldproc(hDlg, message, wParam, lParam);
}
return (INT_PTR)FALSE;
}
and
HWND handle = CreateWindowEx( NULL,
L"button",
L"TEXT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
50,
50,
500,
500,
hWnd,
(HMENU)200,
hInstance,
nullptr);
oldproc = (WNDPROC)SetWindowLong(handle, GWL_WNDPROC, (LONG)Proc);
The problem is that no matter how I handle messages in Proc no messagebox is being created.
Note: commenting last line and handling it in the window proc like so:
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case 200:
if(wmEvent == BN_CLICKED)
MessageBox(NULL,NULL,NULL,NULL);
... }
works fine but I can't use this due to how I designed things.
Is there a way to get this working?
You don't see your message box because BM_CLICK is not a message that the button receives. It is a message you send to the button when you want it to simulate a click action.
What you are looking for is the BN_CLICKED notification instead. However, BN_CLICKED is wrapped inside of a WM_COMMAND message that is sent to the button's parent window, not to the button itself. So you need to subclass the parent window in order to receive it.
If that is not an option, then you can use a thread-specific message hook via SetWindowsHookEx() instead and have that callback look for messages being sent to the button's parent window. For example:
HWND hBtn, hBtnParent;
HHOOK hHook;
LRESULT CALLBACK BtnMsgProc(int iCode, WPARAM wParam, LPARAM lParam)
{
if ((iCode == HC_ACTION) && (wParam == PM_REMOVE))
{
MSG *msg = reinterpret_cast<MSG*>(lParam);
if ((msg->hwnd == hBtnParent) &&
(msg->message == WM_COMMAND) &&
(HIWORD(msg->wParam) == BN_CLICKED) &&
(reinterpret_cast<HWND>(msg->lParam) == hBtn))
{
// button has been clicked...
}
}
return CallNextHookEx(hHook, iCode, wParam, lParam);
}
.
hBtnParent = hWnd;
hBtn = CreateWindowEx(
NULL,
L"button",
L"TEXT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
50,
50,
500,
500,
hWnd,
(HMENU)200,
hInstance,
NULL);
hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)BtnMsgProc, NULL, GetCurrentThreadId());
...
UnhookWindowsHookEx(hHook);
DestroyWindow(hBtn);
For your question about an example for SetWindowsHookEx: MSDN
Related
I have created a Windows application. The elements that I create are using subclassing as I wanted to handle mouse hover events.
DWORD dwStyleOfIcons = SS_BITMAP | SS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER;
img1 = CreateWindow(L"STATIC", NULL, dwStyleOfIcons,
posX, posY, imgWt, imgHt,
hWnd, (HMENU)ICON1_CLICKED, hInst, NULL);
SetWindowSubclass(img1, StaticSubClassWndProc, ICON1_CLICKED, 0);
In my StaticSubClassWndProc(), I handle WM_MOUSEMOVE, WM_MOUSELEAVE, and WM_MOUSEHOVER:
LRESULT CALLBACK StaticSubClassWndProc (HWND hwndsubclass, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR uidsubclass , DWORD_PTR dwrefdata)
{
...
switch(Msg)
{
case WM_MOUSEHOVER: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture2.bmp";
modifyImage(texture);
}
break;
}
case WM_MOUSELEAVE: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture.bmp";
modifyImage(texture);
}
break;
}
There are many STATIC items in my application, which all I wanted the behavior of a pop up context menu, like when I hover over the image it changes to a selected image, and when the cursor is out of view the image changes back to normal. I was able to do that.
I was able to do this for images which act as icons, but how do I do it for static text controls? Essentially, in a pop up menu, the selected text is all highlighted:
Is there no simpler way to make my elements in this window behave like a pop up menu? All I want is this custom structure of pop up menu behavior.
I think you did not handle the TrackMouseEvent function correctly, which caused your child window to be unable to process the WM_MOUSEHOVER and WM_MOUSELEAVE messages.
I tested the following code and it worked for me:
HBITMAP hBmp1 = (HBITMAP)LoadImage(NULL, L"test1.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HBITMAP hBmp2 = (HBITMAP)LoadImage(NULL, L"test2.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HWND img1;
LRESULT CALLBACK StaticSubClassWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uidsubclass, DWORD_PTR dwrefdata)
{
switch (msg)
{
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT lpEventTrack;
lpEventTrack.cbSize = sizeof(TRACKMOUSEEVENT);
lpEventTrack.dwFlags = TME_HOVER | TME_LEAVE;
lpEventTrack.hwndTrack = img1;
lpEventTrack.dwHoverTime = 100;
TrackMouseEvent(&lpEventTrack);
break;
}
case WM_MOUSEHOVER:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp1);
}
break;
}
case WM_MOUSELEAVE:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp2);
}
break;
}
default:
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
}
But you need to be aware that WM_MOUSEHOVER and WM_MOUSELEAVE will trigger frequently, so I don't think you should use this method to load pictures when the mouse is hovered or left, which will frequently trigger the loading of pictures.
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);
}
I want to make a panel, which groups buttons by itself:
HWND my_panel = CreateWindow(
"STATIC",
"",
WS_VISIBLE | WS_CHILD | WS_BORDER,
30,
100,
300,
300,
main_window, // main dialog
NULL,
( HINSTANCE ) GetWindowLong( main_window, GWL_HINSTANCE ),
NULL
);
Then I add a button to this panel:
HWND button_in_a_group = CreateWindow(
"BUTTON",
"Hello world",
WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
20,
20,
50,
50,
my_panel, // as a child for above
NULL,
( HINSTANCE ) GetWindowLong( main_window, GWL_HINSTANCE ),
NULL
);
When I click the button, it doesn't send a WM_COMMAND but WM_PARENTNOTIFY to callback function. Then, if I press Enter, it works - WM_COMMAND is sent by the button.
How to enable mouse click on nested button, and why nested windows doesn't work as expected?
Messages are sent to parent window. In this case the static windows is the button's parent. So the main window is not receiving button messages, except WM_PARENTNOTIFY.
You can subclass the static window:
SetWindowSubclass(my_panel, ChildProc, 0, 0);
Define a ChildProc to catch the button messages. See also Subclassing Controls
The button also requires an identifier as follows:
CreateWindow("BUTTON", "Hello world", ... my_panel, HMENU(BUTTON_ID) ...);
WM_COMMAND message is sent to ChildProc when button is clicked. The BN_CLICKED notification carries BUTTON_ID
Note, SetWindowSubclass needs additional header and library:
#include <CommCtrl.h>
#pragma comment(lib, "Comctl32.lib") //Visual Studio option for adding libraries
...
LRESULT CALLBACK ChildProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR)
{
switch(msg) {
case WM_COMMAND:
switch(LOWORD(wParam)) {
case BUTTON_ID:
MessageBox(0, "hello world", 0, 0);
break;
}
break;
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, ChildProc, 0);
break;
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
I am working on Zoom SDK which is based on win32 gui.
I have created 3 buttons using CreateWindow method on the window handle, which is provided by the ZoomSDK.
Code + Screenshot - 1
Now there are two problems with this.
As soon as I click the buttons, they disappear.
See the Screen Shots BEFORE
See the Screen Shots AFTER
I want to know the reason why this is happening and how can I fix this?
HWND hFirstView, hSecondView;
cntrl->GetMeetingUIWnd(hFirstView, hSecondView);
cntrl->MoveFloatVideoWnd(100, 100);
HWND btnHwnd = CreateWindow(
TEXT("button"),
L"Open App",
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
0, 0,
50, 25,
hFirstView,
(HMENU)100,
hInst,
NULL);
HWND btnHwnd2 = CreateWindow(
TEXT("button"),
L"Other",
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
50, 0,
50, 25,
hFirstView,
(HMENU)101,
hInst,
NULL);
HWND btnHwnd3 = CreateWindow(
TEXT("button"),
L"Raise Hand",
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
100, 0,
50, 25,
hFirstView,
(HMENU)103,
hInst,
NULL);
HDC hdc = GetDC(btnHwnd);
SetBkColor(hdc, GetSysColor(COLOR_BTNSHADOW));
SetTextColor(hdc, GetSysColor(COLOR_BACKGROUND));
ReleaseDC(btnHwnd, hdc);
int btnId = GetDlgCtrlID(btnHwnd);
//oldWndProc = (WNDPROC) GetWindowLong(hFirstView, GWL_WNDPROC);
oldWndProc = (WNDPROC) SetWindowLong(hFirstView,
GWL_WNDPROC, (LONG)WndProc);
SendMessage(btnHwnd, BM_SETSTATE, 1, 0);
SetWindowText(hFirstView, L"Title");
I want to handle click event for these buttons. I have tried to use SetWindowsLong to set another WndProc
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int id = GetWindowLong(hWnd, GWL_ID);
switch (message)
{
case WM_COMMAND: {
MessageBox(NULL, L"Sign of releaf!", L"Whoaa!", 0);
if (wParam == 1023) {
MessageBox(NULL, L"Sign of releaf!", L"Whoaa!", 0);
}
}
break;
}
return CallWindowProc(oldWndProc, hWnd, message, wParam, lParam);
}
Now, this WndProc gets called for other events such mouse move etc. It is not working for my three buttons. I want to handle click event i.e WM_COMMAND or any other technique possible.
Because I can not go inside the sdk (they don't provide sources, only .lib) so I can not change their WndProc, nor their internal WM_PAINT. The buttons are sort of overlay on top.
You should not be calling SetBkColor() and SetTextColor() from outside a WM_PAINT handler. The correct way to color a button is to either:
have the parent window handle the WM_CTLCOLORBTN notification.
The WM_CTLCOLORBTN message is sent to the parent window of a button before drawing the button. The parent window can change the button's text and background colors.
give the button the BS_OWNERDRAW style, and then have the parent window handle the WM_DRAWITEM notification.
Sent to the parent window of an owner-drawn button, combo box, list box, or menu when a visual aspect of the button, combo box, list box, or menu has changed.
Also, when a button sends a BN_CLICKED notification to its parent window, your subclass WndProc() doesn't need to use GetWindowLong(GWL_ID). First, you are calling it on the wrong HWND. And second, the button ID is carried in the message's wParam data.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_COMMAND: {
if (HIWORD(wParam) == BN_CLICKED) {
switch (LOWORD(wParam)) {
case 100:
case 101:
case 103:
MessageBox(NULL, L"Sign of relief!", L"Whoaa!", 0);
break;
}
}
break;
}
}
return CallWindowProc(oldWndProc, hWnd, message, wParam, lParam);
}
I have code like this :
In WM_CREATE
hCheckBox = CreateWindowEx(0,"Button","Random text", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS| BS_AUTOCHECKBOX | BS_TEXT | WS_GROUP | WS_TABSTOP,150,323,300,20,hwnd,0,hInstance,0) ;
In WM_COMMAND
if ( SendMessage( hCheckBox , BM_GETCHECK, (WPARAM) NULL, (LPARAM) NULL ) == BST_CHECKED )
MessageBox( 0, "Working", "Msg", 0 );
Doesn't matter if the checkbox is checked or not, it never return BST_CHECKED.
Trying to make it work for the last 2 hours :(
It is not entirely apparent whether hCheckBox is a local automatic variable. If so, I can assure you it is not holding its value from the time the CreateWindow fires until the time the WM_COMMAND message is received. Use this instead:
LRESULT chk = SendDlgItemMessage(hDlg, IDC_CHECKBOX_CTRL_ID, BM_GETCHECK, 0, 0);
Where hDlg is your dialog or main window handle, and IDC_CHECKBOX_CTRL_ID is the control ID. To that, you need to specify the control id as a non-zero value for the HMENU parameter to your create-call:
hCheckBox = CreateWindowEx(0,"Button","Random text", WS_CHILD | WS_VISIBLE |
WS_CLIPSIBLINGS| BS_AUTOCHECKBOX | BS_TEXT | WS_GROUP |
WS_TABSTOP,150,323,300,20,hwnd, IDC_CHECKBOX_CTRL_ID, hInstance,0);
And in case it wasn't obvious. Define IDC_CHECKBOX_CTRL_ID as a non-zero integer.
EDIT
Assuming the control is setup correctly, you should be able to handle the WM_COMMAND for this checkbox as follows in your WndProc
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
switch (message)
{
case WM_CREATE:
CreateWindowExA(0,"Button","Random text", WS_CHILD | WS_VISIBLE |
WS_CLIPSIBLINGS| BS_AUTOCHECKBOX | BS_TEXT | WS_GROUP |
WS_TABSTOP,100,100,300,48, hWnd, (HMENU)IDC_CHECKBOX_CTRL_ID, hInst, 0);
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
if (wmId == IDC_CHECKBOX_CTRL_ID)
{
if (wmEvent == BN_CLICKED)
{
LRESULT chkState = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
if (chkState == BST_CHECKED)
MessageBoxA(hWnd, "Checkbox is checked!", "CheckBox", MB_OK);
}
break;
};
// fall-thru intentional
default:
return DefWindowProc(hWnd, message, wParam, lParam);
};
return 0;
}
I just slammed this into a stock generic WIN32 app with a blank window. the results are in the image below: