Related
I am pretty new to C++ and the Windows API.
I cannot figure out how to change the background color of a button using WM_CTLCOLORBTN while simultaneously having a bitmap on the button. When I try to do this, the program crashes.
I have created a button like this:
HINSTANCE hInstance = GetModuleHandle(NULL);
HWND hbutton = CreateWindow(L"BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_BITMAP | BS_PUSHBUTTON, 15, 202.5, 96.5, 72.5, hWnd, (HMENU)301, hInstance, NULL);
In the WndProc function, I have written:
case WM_CTLCOLORBTN:
switch (((LPNMHDR)lParam) -> code)
{
MessageBox(hWnd, L"made it inside of the switch statement", L"Debug", 1);
}
I have also tried:
case WM_CTLCOLORBTN:
LPNMHDR button = (LPNMHDR)lParam;
if(button->idfrom == 301 && button->code == NM_CUSTOMDRAW)
{
}
In addition, I have tried:
case CDDS_PREERASE:
LPNMHDR button = (LPNMHDR)lParam;
if(button->idfrom == 301 && button->code == NM_CUSTOMDRAW)
{
}
Furthermore, I have tried:
case WM_NOTIFY:
MessageBox(hWnd, L"Code in wm_notify got ran", L"Debug", 1);
The code above does not cause a message box to appear, so it is assumed to have never ran.
All of these methods have resulted in the program crashing.
I have seen in some other posts people putting their code inside of WM_NOTIFY, but that results in the code not running at all.
Would anyone happen to know what I am doing wrong?
First, you don't need to specify an HINSTANCE when creating a system-defined window class. So, you can set the hInstance parameter to NULL.
Second, all of the message handlers you have shown are wrong:
The lParam parameter of the WM_CTLCOLORBTN message is an HWND window handle (of the button), not an LPNMHDR struct pointer. NMHDR structs are used only in WM_NOTIFY messages.
CDDS_PREERASE is not a window message. It is the value of a drawing stage used by the NM_CUSTOMDRAW message. CDDS_PREERASE has a numeric value of 3, which is the same value as the WM_MOVE window message, whose lParam contains X/Y coordinates, not an LPNMHDR pointer.
Most button notifications are delivered to a button's parent window using WM_COMMAND messages. There are only 3 button notifications that are delivered to the parent window using WM_NOTIFY messages - BCN_DROPDOWN, BCN_HOTITEMCHANGE, and NM_CUSTOMDRAW. The first 2 don't apply to your button example. The 3rd one applies only if your app has Comctl32.dll v6 enabled, in which case the lParam parameter is an LPNMCUSTOMDRAW struct pointer, not an LPNMHDR pointer.
Now then, the WM_CTLCOLORBTN documentation says:
Parameters
wParam
An HDC that specifies the handle to the display context for the button.
lParam
An HWND that specifies the handle to the button.
Return value
If an application processes this message, it must return a handle to a brush. The system uses the brush to paint the background of the button.
So, try something more like this instead:
HWND hButton = NULL;
HBRUSH hButtonBkg = NULL;
...
case WM_CREATE: {
...
hButton = CreateWindow(L"BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_BITMAP | BS_PUSHBUTTON, 15, 202.5, 96.5, 72.5, hWnd, (HMENU)301, hInstance, NULL);
hButtonBkg = CreateSolidBrush(RGB(...)); // <-- whatever color you want
...
break;
}
case WM_DESTROY: {
DeleteObject(hButtonBkg);
break;
}
case WM_CTLCOLORBTN: {
if (hButton == (HWND)lParam) {
HDC hdc = (HDC) wParam;
// configure hdc as needed...
SetTextColor(hdc, RGB(...)); // <-- whatever color you want
return (LRESULT) hButtonBkg;
}
break;
}
Alternatively:
case WM_CTLCOLORBTN: {
if (hButton == (HWND)lParam) {
HDC hdc = (HDC) wParam;
// configure hdc as needed...
SetTextColor(hdc, RGB(...)); // <-- whatever color you want
SetDCBrushColor(hdc, RGB(...)); // <-- whatever color you want
return (LRESULT) GetStockObject(DC_BRUSH);
}
break;
}
However, the WM_CTLCOLORBTN documentation also says:
However, only owner-drawn buttons respond to the parent window processing this message.
...
By default, the DefWindowProc function selects the default system colors for the button. Buttons with the BS_PUSHBUTTON, BS_DEFPUSHBUTTON, or BS_PUSHLIKE styles do not use the returned brush. Buttons with these styles are always drawn with the default system colors. Drawing push buttons requires several different brushes - face, highlight, and shadow - but the WM_CTLCOLORBTN message allows only one brush to be returned. To provide a custom appearance for push buttons, use an owner-drawn button. For more information, see Creating Owner-Drawn Controls.
So, you should add the BS_OWNERDRAW style to your button, in which case you will need to handle the WM_DRAWITEM message instead to custom-draw the button how you want.
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 am creating a listbox control like this:
HWND hAvailable = CreateWindowEx(WS_EX_CLIENTEDGE, L"Listbox", NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
LBS_DISABLENOSCROLL | LBS_SORT,
0, 0, 0, 0, hWnd, (HMENU)IDC_AVAILABLELIST,
hInst, NULL);
and listening for events in my callback function, under WM_COMMAND like this:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int wmId, wmEvent;
switch (message) {
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId) {
case IDC_AVAILABLELIST:
// We get here, with LBN_SETFOCUS and LBN_KILLFOCUS
if (wmEvent == LBN_SELCHANGE || wmEvent == LBN_DBLCLK) {
// Never gets here!
}
[ ... ]
The issue is that when I change the selection, I can see the new item being selected but I am not receiving the appropriate event.
When I select an item, I get an LBN_SETFOCUS event, followed by an LBN_KILLFOCUS event. Any attempt to get the currently selected index with int idx = SendMessage(hAvailable, LB_GETCURSEL, 0, 0); returns -1 even though an item is in fact selected.
If you read the documentation for LBN_SELCHANGE and LBN_DBLCLK, they both say:
This notification code is sent only by a list box that has the LBS_NOTIFY style.
The documentation for LBS_NOTIFY says:
LBS_NOTIFY
Causes the list box to send a notification code to the parent window whenever the user clicks a list box item (LBN_SELCHANGE), double-clicks an item (LBN_DBLCLK), or cancels the selection (LBN_SELCANCEL).
You are not enabling that style on your ListBox control.
As for LB_GETCURSEL, it returns LB_ERR (-1) when there is no selection.
I had the same problem. This is true. The LBS_NOTIFY needs to be in the style.
What is very peculiar about this issue is that yesterday my code did not need the LBS_NOTIFY but today it does. So weird right! I'm using Visual Studios 2019. Also, adding the LBS_NOTIFY resolved the problem only after a reboot.
Is there a way to set true full focus on a push button (button window class) in WinAPI?
SetFocus() somewhat sets focus (the button gets an inner dotted border), but the button is actually partially focused and still cannot be pressed with the Enter key, only the Spacebar key works. At the same time, if I move focus to a sibling button with the Tab key, then this sibling button (as well as the first button if I then return focus to it using Shift+Tab) gets a true focus (visually, not just inner dotted focus border is added to the really focused button, but its main outer border becomes blue [Windows 7]), and now it reacts to Enter as intended.
How to make a button such fully focused programmatically?
Screenshot of the three button states:
Some background for clarity: there is a window (created with the regular combination of WNDCLASSEX / RegisterClassEx / CreateWindowEx() with WS_OVERLAPPEDWINDOW as its style) with a multiline edit box (edit window class with ES_MULTILINE style) and several push buttons. To implement keyboard navigation using the Tab key, I process the WM_KEYDOWN event in the edit box's procedure (subclassed via SetWindowLong()), otherwise I could navigate between buttons and from buttons to the edit box, but not from the edit box to a button. All the controls have WS_TABSTOP style. The issue with the button focus takes place when I set focus using SetFocus() when the Tab key is pressed on the edit box having focus and caret.
Minimal relevant C++ code:
HWND mainWindow;
WNDPROC defaultEditCallback = NULL;
int WINAPI WinMain(HINSTANCE instance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEXW wc;
wchar_t windowClass[] = L"testcase";
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = 0;
wc.lpfnWndProc = mainWindowCallback;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = instance;
wc.hIcon = NULL;
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = (LPCWSTR)windowClass;
wc.hIconSm = NULL;
RegisterClassExW(&wc);
mainWindow = CreateWindowW(
(LPCWSTR)windowClass, (LPCWSTR)windowClass, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 200, NULL, NULL, instance, NULL
);
HWND edit = CreateWindowExW(
WS_EX_CLIENTEDGE, (LPCWSTR)L"edit", NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | WS_TABSTOP,
0, 0, 0, 0, mainWindow, (HMENU) 10,
(HINSTANCE) GetWindowLongPtrW(mainWindow, GWLP_HINSTANCE),
NULL
);
defaultEditCallback = (WNDPROC)SetWindowLongPtrW(edit, GWLP_WNDPROC, (LONG)editCallback);
HWND firstButton = createButton(mainWindow, 20, L"First", buttonWidth, buttonHeight);
HWND secondButton = createButton(mainWindow, 30, L"Second", buttonWidth, buttonHeight);
HWND thirdButton = createButton(mainWindow, 40, L"Third", buttonWidth, buttonHeight);
// [Skipped] Sizing and positioning controls.
ShowWindow(mainWindow, nCmdShow);
UpdateWindow(mainWindow);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
if (!IsDialogMessage(mainWindow, &msg)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
return (int)msg.wParam;
}
LRESULT CALLBACK mainWindowCallback(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
if (WM_DESTROY == msg) {
PostQuitMessage(0);
}
return DefWindowProcW(window, msg, wParam, lParam);
}
LRESULT CALLBACK editCallback(HWND control, UINT msg, WPARAM wParam, LPARAM lParam) {
if (WM_KEYDOWN == msg && VK_TAB == wParam) {
HWND next = GetNextDlgTabItem(mainWindow, control, (int)(GetKeyState(VK_SHIFT) & 0x8000));
SetFocus(next);
return 0;
}
return CallWindowProc(defaultEditCallback, control, msg, wParam, lParam);
}
HWND createButton(HWND parentWindow, int id, wchar_t* caption, int width, int height) {
return CreateWindowW(
(LPCWSTR)L"button", (LPCWSTR)caption, WS_VISIBLE | WS_CHILD | WS_TABSTOP,
0, 0, width, height, parentWindow, (HMENU)id, NULL, NULL
);
}
Thanks.
The question does not make it clear whether the WS_OVERLAPPEDWINDOW main window is either a dialog, or subclassed to work like a dialog (i.e. based on DefDlgProc). Some hints about TAB navigation and DM_SETDEFID in the OP and following comments appear to indicate that it's a dialog(-styled) window.
For dialogs, the correct way to move the input focus between child controls is by sending the WM_NEXTDLGCTL message, rather than calling SetFocus directly. As noted in the docs:
This message performs additional dialog box management operations beyond those performed by the SetFocus function WM_NEXTDLGCTL updates the default pushbutton border, sets the default control identifier, and automatically selects the text of an edit control (if the target window is an edit control).
More details at How to set focus in a dialog box, including this part:
As the remarks to the DM_SETDEFID function note, messing directly with the default ID carelessly can lead to odd cases like a dialog box with two default buttons. Fortunately, you rarely need to change the default ID for a dialog.
A bigger problem is using SetFocus to shove focus around a dialog. If you do this, you are going directly to the window manager, bypassing the dialog manager. This means that you can create “impossible” situations like having focus on a pushbutton without that button being the default!
To avoid this problem, don’t use SetFocus to change focus on a dialog. Instead, use the WM_NEXTDLGCTL message.
[EDIT] After the OP edit, the main window turns out to be a regular window, not a dialog. The newly posted code, however, does not remember the focused child (nor does it remove/restore the default pushbutton style) after the main window is deactivated and reactivated, relegates keyboard navigation such as VK_TAB to child controls etc. Those introduce inconsistencies with the default dialog-like behaviors.
In order to make the main window behave like a dialog (and do it right), the WNDPROC would need to mimic the relevant parts of DefDlgProc, at least those that pertain to navigation. From Dialog Box Programming Considerations, among the messages such a WNDPROC would need special handling for are DM_GETDEFID, DM_SETDEFID, WM_ACTIVATE, WM_NEXTDLGCTL, WM_SHOWWINDOW, WM_SYSCOMMAND. Once that's done, my original answer still applies.
I am working on windows and i chose the c++ as language. My application is a win32 application and I am not using MFC classes, but only the native API.
My main window contain a Tab control in which there is a Listview control. I tried to Subclass both the control, but it is not working even though i follow the msdn guide.
The function I used are:
SetWindowLong and GetWindowLong
so I changed the child's procedures but it seems the main window catches every messages that controls send ( through WM_NOTIFY ) and every WM messages. I thought that it may concern focus but I do not have any idea on how implement this kind of situation: a child control which is the parent of an another child control.
I have created the Tab control in this way.
code:
hwndTab = CreateWindow(WC_TABCONTROL, "",
WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
0, 0, rcClient.right, rcClient.bottom,
winHandle, NULL, hInst, NULL);
OldUserTabProc = (WNDPROC)GetWindowLong(hwndTab, GWL_WNDPROC);
SetWindowLong(hwndTab, GWL_WNDPROC, (LONG_PTR)UserTabProc);
Here The Tab control procedure
LRESULT CALLBACK UserTabProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch ( message )
{
case WM_LBUTTONDOWN:
break;
case WM_RBUTTONUP:
break;
case TCN_SELCHANGE:
{
userCurrentTab = TabCtrl_GetCurSel( userTab );
break;
}
case TCN_SELCHANGING:
{
break;
}
};
return CallWindowProc( OldUserTabProc, hWnd, message, wParam, lParam );
}
The listview has as parent the Tab control.
Values like TCN_SELCHANGE are not message types and they aren't sent to the control themselves. They are notifications (the N stands for "notification"). Notifications are sent by the control to the parent using a message like WM_NOTIFY or WM_COMMAND. You cannot intercept them by subclassing the control.
If you want to change the way the parent handles those notifications, you need to subclass the parent.