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);
}
Related
I am creating a window with the following 2 aims both of which are requird:
when person clicks outside of the window, the window is destroyed.
when a person clicks a button inside the window, we process the WM_COMMAND message, and then the window is destroyed.
To achieve both these goals, I looked and found there is a WM_KILLFOCUS message which is sent when the window looses focus.
I wrote destroy window code in the WM_KILLFOCUS handler, but then the button click message box does not come. I searched and found the destroy event is not serialized and hence it could be that it is being sent before the window button click could be caught. I have hence changed the code like below, where it also sends a WM_COMMNAD to kill the window.
case WM_KILLFOCUS:
SendMessage(hwnd, WM_COMMAND, KILLTHEWIN, 0);// hope it serializes message
break;
case WM_COMMAND:
switch (wp)
{
case KILLTHEWIN:
DestroyWindow(hwnd);
break;
case BUTTON_CLICKED:
default:
MessageBox(NULL, L"Hurray", L"Hurray MSG reveid", MB_OK);
break;
}
I see that when I click the button inside the window, the window is destroyed and the MessageBox "Hurray" is not received.
How can I ensure that the messages are processed before destroying the window?
Update 1: Minimal code as asked. Now handling the WM_ACTIVATE message instead for handling WM_KILLFOCUS . Both the two aims are written above:
#include <windows.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <windowsx.h>
#include <map>
#include <commctrl.h>
#define KILLTHEWIN 10
#define BUTTON_PRESS 11
LRESULT CALLBACK windowprocedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow)
{
WNDCLASSW wc = { 0 };
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hInstance = hInst;
wc.lpszClassName = L"mywindowsclass";
wc.lpfnWndProc = windowprocedure;
// before creating window for a class we need to register that class
if (!RegisterClassW(&wc))
{
return -1; // registration failed
}
HWND hWnd = CreateWindowW(L"mywindowsclass", L"My window", WS_POPUP | WS_VISIBLE/*| WS_EX_TOOLWINDOW*/ /* check for no task bar*/,
100, 100, 170, 500, NULL, NULL, NULL, NULL);
MSG msg{ 0 };
while (GetMessage(&msg, NULL, NULL, NULL))
{
// translate the messahe
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// main window message handling
LRESULT CALLBACK windowprocedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
DWORD dwStyleOfStaticText = 0;
HWND hBmp2 = NULL;
WORD reason = 0;
switch (msg)
{
case WM_CREATE:
dwStyleOfStaticText = SS_NOTIFY | SS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP /*| WS_BORDER */;
hBmp2 = CreateWindowW(L"Button", L"Button1", WS_VISIBLE | WS_CHILD,
5, 5 + 4 * 15, 100, 50,
hwnd, (HMENU)BUTTON_PRESS, NULL, NULL);
SetWindowLong(hwnd, GWL_STYLE, 0);
break;
case WM_ACTIVATE:
reason = LOWORD(wp);
if (reason == WA_ACTIVE)
{
//Activated by some method other than a mouse click (for example, by a call to the SetActiveWindow function or by use of the keyboard interface to select the window).
}
else if (reason == WA_CLICKACTIVE)
{
// activated by mouse click
}
else if (reason == WA_INACTIVE)
{
PostMessage(hwnd, WM_COMMAND, KILLTHEWIN, 0);
}
break;
case WM_COMMAND: // when ever a menu is clicked
// WM_COMMAND has been received for which control handle and then kill it
switch (wp)
{
case KILLTHEWIN:
DestroyWindow(hwnd);
break;
case BUTTON_PRESS:
MessageBoxW(hwnd, L"button pressed", L"button pressed caption ", MB_OK);
break;
}
PostMessage(hwnd, WM_COMMAND, KILLTHEWIN, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);// case other as default window procedure
}
return (LRESULT)0;
}
The WM_ACTIVATE was handled above instead of the kill focus .
The two aims written above are really important . Still on pressing the button the MessageBox does not appear and window is killed .
I put it in debugger
one time the breakpoint hit the button message box but it still did not came . The other time the breakpoint hit the WM_ACTIVATE deactivate reason first .
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 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'm trying to receive a message from a button when clicked.
But instead of receiving it from the main window, I would like to receive it from a tab control, which is a child of the main window. But I'm not sure how to go about this.
This is the creation of the button:
deAll = CreateWindowEx(0, "BUTTON", "Disable All", WS_CHILD | BS_PUSHBUTTON, 135, 49, 95, 26, tabs, (HMENU)204, instance, NULL);
Obviously, tabs is a tab control.
This is the creation of the tab control:
tabs = CreateWindowEx(0, WC_TABCONTROL, 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, rc.right + 2, rc.bottom - 22, hwnd, NULL, instance, NULL);
And hwnd is the main window.
Any ideas? Or do I have to make every control a child of the main window?
This was made using the Win32 API in C++.
The tabs window is the parent window of the button, so the button will send a WM_COMMAND/BN_CLICKED notification to the tabs window when the button is clicked. You will have to subclass the tabs window via either SetWindowLongPtr(GWL_WNDPROC) or SetWindowSubClass() to receive that message. For example:
WNDPROC prevWndProc;
LRESULT CALLBACK myWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if ((uMsg == WM_COMMAND) && (HIWORD(wParam) == BN_CLICKED))
{
// LOWORD(wParam) is the ID, and lParam is the HWND,
// of the button that was clicked. do something ...
}
return CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
}
tabs = CreateWindowEx(0, WC_TABCONTROL, 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, rc.right + 2, rc.bottom - 22, hwnd, NULL, instance, NULL);
prevWndProc = (WNDPROC) SetWindowLongPtr(tabs, GWLP_WNDPROC, (LONG_PTR) &myWndProc);
Or:
LRESULT CALLBACK mySubClassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if ((uMsg == WM_COMMAND) && (HIWORD(wParam) == BN_CLICKED))
{
// LOWORD(wParam) is the ID, and lParam is the HWND,
// of the button that was clicked. do something ...
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
tabs = CreateWindowEx(0, WC_TABCONTROL, 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, rc.right + 2, rc.bottom - 22, hwnd, NULL, instance, NULL);
SetWindowSubclass(tabs, &mySubClassProc, 0, 0);
If a control is a child of a tab (or any other control) then it's the tab that will get notification messages like WM_COMMAND.
It's easiest to make your controls all children of your main window and just fix the z-order to make them appear in front of the tab (you already have WS_CLIPSIBLINGS set on the tab control, which you would also need). If you leave the controls as children of the tab then the only way to get notification messages is to sub-class the tab.
Or, you can do this the way property sheets do it, and use child dialogs (a dialog with the DS_CONTROL style set) to host the tab content. Then you can have a separate dialog procedure that handles messages from the child controls, and it makes it easy to show/hide a whole page of controls rather than handling them all individually. The TCM_ADJUSTRECT message can be used to calculate the size/position that you need to display your child dialog at.
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