Displaying a bitmap on a "BUTTON" class window in WIN32 - c

Edit: I think the WM_CREATE message isn't sent during the creation of child windows (namely my button). So by calling SendMessage during WM_CREATE, I'm sending a message to a window that hasn't been created yet. The solution for now is to call SendMessage() during the WM_SHOWWINDOW message. Do child windows send WM_CREATE messages at creation?
Why isn't the bitmap displaying on the button? The bitmap is 180x180 pixels.
I have a resource file with:
Bit BITMAP bit.bmp
I then create the main window and a child "BUTTON" window with:
HWND b, d;
b = CreateWindow(L"a", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, 0, 0,
hInstance, 0);
d = CreateWindow(L"BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
10, 10, 180, 180, b, 200, hInstance, 0);
Then, in my windows procedure, I send the "BUTTON" window the "BM_SETIMAGE" message with:
HBITMAP hbit;
case WM_CREATE: // It works if I change this to: case WM_SHOWWINDOW
hbit = LoadBitmap(hInstance, L"Bit");
SendMessage(d, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbit);
LoadBitmap() is returning a valid handle because It isn't returning NULL, and I'm able to display the bitmap in the client area using the BitBlt() function. So I'm either not sending the message correctly, or I'm not creating the "BUTTON" window correctly.
What am I doing wrong?
Thanks!

The window procedure for for your window class "a" gets called with WM_CREATE when a window of that class is created. This is during your first call to CreateWindow, which is before you create the child BUTTON window. WM_CREATE means "you are being created" - it doesn't mean "a child is being created".
The solution is to call d = CreateWindow(L"BUTTON"...) in the WM_CREATE handler for class "a":
case WM_CREATE:
d = CreateWindow(L"BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
10, 10, 180, 180, hwnd, 200, hInstance, 0);
hbit = LoadBitmap(hInstance, L"Bit");
SendMessage(d, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbit);

How are you verifying that WM_CREATE isn't getting called? Since BUTTON isn't your window class (but rather defined by the OS) it owns the WndProc for the window, not you - therefore WM_CREATE shouldn't be called for the button in your code, because BUTTON isn't your class.
If you want to receive messages for the button, you'll have to subclass it, and then provide your own WndProc.

Related

How to receive a message in the parent window from a button created by CreateWindow()?

I've created a button with the following code:
HWND hwndButton = CreateWindow(
"BUTTON", // Predefined class;
"Options", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
250, // x position
0, // y position
50, // Button width
30, // Button height
hwnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
NULL);
I'm aware that messages from buttons can be received in the window procedure of the parent window by doing a switch with WM_COMMAND and the name of the control as the case, for example, IDC_BUTTON. However, that's only when I create buttons on dialog boxes graphically using Visual Studio's editor. When I create a button using CreateWindow, as above, there isn't a 'name' for the control to put into a case statement.
What message is sent to the parent window when the button is pressed?
You can specify the button's identifier value (as in the IDC_BUTTON1 control resource ID, for example, in a dialog box) as the hMenu argument in the call to CreateWindow.
Here's a suitably modified version of your code:
HWND hwndButton = CreateWindow(
"BUTTON", // Predefined class;
"Options", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
250, // x position
0, // y position
50, // Button width
30, // Button height
hwnd, // Parent window
(HMENU)(IDC_BUTTON1), // With WS_CHILD, this is an ID, not a menu
(HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
NULL);
From the description of the hMenu parameter in the docs (bold emphasis mine):
A handle to a menu, or specifies a child-window identifier depending
on the window style. For an overlapped or pop-up window, hMenu
identifies the menu to be used with the window; it can be NULL if the
class menu is to be used. For a child window, hMenu specifies the
child-window identifier, an integer value used by a dialog box control
to notify its parent about events. The application determines the
child-window identifier; it must be unique for all child windows with
the same parent window.
Then, when the parent receives the WM_COMMAND message, that identifier value will be part (the low word) of the wParam argument (the high word will be the BN_CLICKED notification code). (docs)
So, assuming you have the IDC_BUTTON1 token defined somewhere, and pass that as the hMenu argument as described above, then your parent window can have something along the following lines as part of the message-handler's switch statement:
//...
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON1 && HIWORD(wParam) == BN_CLICKED) {
MessageBox(NULL, "Clicked My Button!", "Testing...", MB_OK);
// <Place your actual code here ...>
return 0; // If handled, we should return zero.
}
break;
//...

GetWindowTextLength() returns 0, supposedly because of invalid window handle

I'm trying to build a simple graph plotting application with C and Windows API for studying purposes. It is supposed to take user's input containing a math function of one real variable and then to plot it. I tried to implement user's input capabilities by creating textbox and submit button through calling CreateWindow() from the callback window function. However, when I attempted to test the textbox by evaluating input text size, I received 0 under any conditions (I also made sure that it is not an ASCII/UNICODE issue). GetLastError() got 1400 error code, i.e. invalid window handle. What could be possible reasons for this error and why respective window handle is considered invalid?
I'm using Bloodshed Dev-C++ 5.11 on Windows 10 x64 with 64-bit TDM-GCC 4.9.2.
Please find below the piece of callback function that is causing problems:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
int len;
(...other declarations go here...)
HWND text_box;
HWND plot_button;
switch (message){
case WM_CREATE:
text_box = CreateWindow("EDIT",
"",
WS_BORDER | WS_CHILD | WS_VISIBLE,
10, 5, 390, 20,
hwnd, (HMENU) 0, NULL, NULL);
plot_button = CreateWindow("BUTTON",
"Plot",
WS_VISIBLE | WS_CHILD | WS_BORDER,
420, 5, 165, 20,
hwnd, (HMENU) 1, NULL, NULL);
break;
case WM_COMMAND:
switch(LOWORD(wParam)){
case 1:
len = GetWindowTextLength(text_box);
printf("%d", len); // a simple console output for testing purposes only, returns 0 disregarding the text length
(...other irrelevant code...)
break;
}
break;
(...other irrelevant code...)
}
Based on a comment by rafix07:
You need to make text_box static. Every time the window procedure is called this variable is created, so when the WM_COMMAND message is processed this variable is uninitialized.
To make text_box static, do this:
static HWND text_box;

C child window procedure

In C language, this is a child button window that I have put inside a window made with CreateWindowEx(). I am wondering if there's a way give this button window and ID so I can callback a procedure and make the button interactive for user experience.
Maybe implement it inside WM_COMMAND -> switch(LOWORD(wParam)){ case: THEIDOF_BUTTON}
This code runs under LRESULT CALLBACK window procedure of parent windows as you can see with WM_CREATE
HWND buttonBox;
case WM_CREATE:
(HWND)buttonBox = CreateWindow(WC_BUTTON, TEXT("ABUTTON"), WS_CHILD | WS_VISIBLE | WS_SIZEBOX, 500, 400, 300, 300, parentWindow, NULL, hInstance, NULL);
read about hMenu parameter in CreateWindowEx
A handle to a menu, or specifies a child-window identifier,
depending on the window style .. For a child window, hMenu specifies
the child-window identifier, an integer value used by a dialog box
control to notify its parent about events.
and from GetDlgCtrlID function documentation:
An application sets the identifier for a child window when it creates
the window by assigning the identifier value to the hmenu parameter
when calling the CreateWindow or CreateWindowEx function.
so you need next code for create child:
buttonBox = CreateWindow(WC_BUTTON, TEXT("ABUTTON"), WS_CHILD | WS_VISIBLE | WS_SIZEBOX,
500, 400, 300, 300, parentWindow, (HMENU)ID_BUTTON_BOX, hInstance, NULL);
where ID_BUTTON_BOX some integer value. and you get in back in WM_COMMAND as wParam (low word) or in WM_NOTIFY
here exist thin point - the CreateWindow[Ex] accept the LONG_PTR in place hMenu as child-window identifier. so 64-bit value on x64 system. the same result will be if call SetWindowLongPtr with GWLP_ID. we can call GetWindowLongPtr(buttonBox, GWLP_ID) after create and check that it return exactly ID_BUTTON_BOX. but if use GetDlgCtrlID function - it return (int)ID_BUTTON_BOX - truncated to 32-bit id.
in case WM_NOTIFY despite idFrom from NMHDR structure declared as UINT_PTR here really only truncated to 32-bit id because the GetDlgCtrlID used for initialize it.
the WM_COMMAND at all truncate id to low 16 bit in wParam.
so for example if we define ID_BUTTON_BOX as 0x9012345678 when calling the CreateWindow or CreateWindowEx function - we got back exactly 0x9012345678 if call GetWindowLongPtr(buttonBox, GWLP_ID). but GetDlgCtrlID(buttonBox) return already 0x12345678 only. also the 0x12345678 will be in wParam and idFrom when we handle WM_NOTIFY and on WM_COMMAND we got only 0x5678 as control id.
so despite we can set full 64 bit value for child window identifier (say pointer to some structure casted to ULONG_PTR) and get it back as is in call GetWindowLongPtr(buttonBox, GWLP_ID) - in WM_NOTIFY and WM_COMMAND we got back only low 32 or 16 bit of identifier. because this usually used only 16 bit values for child id
Once you have the HWND for your button, you must call SetWindowLongPtr to set the button ID:
HWND buttonBox;
#define ID_BUTTON_BOX (100) // or whatever you like, should be unique for the parent window.
//..
case WM_CREATE:
buttonBox = CreateWindow(WC_BUTTON, TEXT("ABUTTON"), WS_CHILD | WS_VISIBLE | WS_SIZEBOX, 500, 400, 300, 300, parentWindow, NULL, hInstance, NULL);
SetWindowLongPtr(buttonBox, GWL_ID, (LONG_PTR)ID_BUTTON_BOX);
Here's a link to the MSDN doc: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx

Draw a line next to word in WinAPI

How do I draw a line like this that is right next to a word like "Counts", in WinAPI with C?
Using Dialog Resources
Create a static text control with no text that is 1 or 2 pixels in height, turn on the border (WS_BORDER), and set its style to Static Edge (WS_EX_STATICEDGE). Then create a static text control with the word "Counts" in it on top of that. Then use CreateDialog() or DialogBox() to show the dialog box.
IDD_DIALOG1 DIALOGEX 0, 0, 172, 63
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
LTEXT "",IDC_STATIC,6,12,156,1,WS_BORDER,WS_EX_STATICEDGE
LTEXT "Counts ",IDC_STATIC,6,8,26,8
END
Note: This is verbatim what Visual Studio generated using the dialog designer.
Creating Static Controls Using CreateWindow() (as suggested by Jonathan Potter)
LRESULT OnCreate( HWND hWnd, LPCREATESTRUCT lpCreateStruct )
{
// Get default gui font
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, NULL);
HFONT hFont = CreateFontIndirect(&metrics.lfMessageFont);
// Create the line
CreateWindowEx(WS_EX_STATICEDGE, _T("STATIC"), NULL, WS_CHILD|WS_VISIBLE|WS_BORDER,
10, 17, 280, 1, hWnd, NULL, lpCreateStruct->hInstance, NULL);
// Create the Counts label
HWND hwndCounts = CreateWindow(_T("STATIC"), _T("Counts "), WS_CHILD|WS_VISIBLE,
10, 10, 50, 26, hWnd, NULL, lpCreateStruct->hInstance, NULL);
// Apply the default gui font
SendMessage(hwndCounts, WM_SETFONT, (WPARAM)hFont, TRUE);
// Cleanup the font object
DeleteObject(hFont);
}
Drawing manually on the WM_PAINT event
void OnPaint( HWND hWnd )
{
// Get the default font
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, NULL);
HFONT hFont = CreateFontIndirect(&metrics.lfMessageFont);
// Setup HDC
RECT rect;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// Select the default font
SelectObject(hdc, hFont);
// Draw the line using the button shadow
SelectObject(hdc, GetStockObject(DC_PEN));
SetDCPenColor(hdc, GetSysColor(COLOR_BTNSHADOW));
MoveToEx(hdc, 10, 17, NULL);
LineTo(hdc, 280, 17);
// Draw the word Counts overtop of the line
SetRect(&rect, 10, 10, 280, 22);
SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
DrawText(hdc, TEXT("Counts "), -1, &rect, DT_NOCLIP);
// Cleanup the font object
DeleteObject(hFont);
// Quit painting
EndPaint(hWnd, &ps);
}
Note: Something I did not account for in this example is the height of the default font. You will want to adjust the code for that.
Here is a screenshot of the output of this method.
In your example, it looked like a single one pixel line, so that's what I drew, but if you'd like to make the line look more like a 'Fixed 3D' or 'lowered bevel line' (which is what the group box tends to draw for it's border line), then you can draw another line below it with the button highlight color.
SetDCPenColor(hdc, GetSysColor(COLOR_BTNHIGHLIGHT));
MoveToEx(hdc, 10, 18, NULL);
LineTo(hdc, 280, 18);
As pointed out by Ben Voigt, it might be better to do this with DrawEdge though.
RECT line;
SetRect(&line, 10, 17, 280,17);
DrawEdge(hdc, &line, EDGE_ETCHED, BF_TOP );
Creating a Group Box Control (suggested by Hans Passant)
Hans Passant's suggestion of doing this with a Group Box did work when I tested it. It still drew a rectangle, and when you enabled visual styles it was very difficult to see. Nevertheless, this should get you started if you want to give it a go.
HWND hwndGroup = CreateWindow(_T("Button"), _T("Counts "),
WS_CHILD|WS_VISIBLE|BS_GROUPBOX, 10, 10, 280, 2, hWnd, NULL,
lpCreateStruct->hInstance, NULL);
SendMessage(hwndGroup, WM_SETFONT, (WPARAM)hFont, TRUE);
Additional Note
Something else I would like to suggest is that you use can use Spy++ which comes with Visual Studio to analyze the window you are looking at. This will tell you at the very least if it's a child control, or whether they are painting it manually. If it's a child control you will also be able to see the rectangle and styles that are applied to it, as well lots of additional information.

plain winapi c GUI changing static text background

I am using plain winapi c to create a GUI, I am new to this language and am struggling with something many might think is basic. Could someone please explain to me how I change the background colour for static text because currently is transparent. The code I am using for the text is:
hwndStatic = CreateWindow(TEXT("static"), TEXT(""),
WS_CHILD | WS_VISIBLE,
10, 70, 90, 25, hwnd, NULL, g_hinst, NULL);
In general, you change the drawing of static text controls by handling WM_GETCTLCOLORSTATIC.
In that handler, you can change things about the DC, like the text color, background mode, background color, even the font that's selected.
You can also return a handle to a GDI brush (using a cast to get it by the type system). The control will erase itself first with the brush and then draw the text.
The callback will happen for all static controls that are children of the current window, so you first test to see if it's the child you care about.
For example:
case WM_CTLCOLORSTATIC:
HWND hwnd = (HWND) lParam;
if (hwnd == hwndStatic) {
HDC hdc = (HDC) wParam;
::SetTextColor(hdc, RGB(0xFF, 0, 0)); // set the text to red
::SetBkMode(hdc, OPAQUE);
::SetBkColor(hdc, RGB(0x00, 0xFF, 0x00)); // set background to green
HBRUSH hbrBackground = ::GetSysColorBrush(COLOR_WINDOW);
return (INT_PTR) hbrBackground;
}
return 0;
This shows several things you can do. You probably don't want to do all of them, but it can be educational to see them all in action.
Note that if you create a brush to return, you have to keep track of it and delete it later. I've avoided this issue by relying on GetSysColorBrush. The system owns those, so you shouldn't delete them. You can also use GetStockObject for system GDI objects that you don't have to manage. But if you need a custom color, you'll have to use CreateSolidBrush and then clean it up.
Respond to the WM_CTLCOLORSTATIC message in your program and have it return a brush object of the proper color.
I've slightly modified the example from the link:
case WM_CTLCOLORSTATIC:
{
HWND hWnd = (HWND) lParam;
if (hWnd == hMyStatic)
{
HBRUSH hbrBkgnd = CreateSolidBrush(RGB(0,0,0));
return (INT_PTR)hbrBkgnd;
}
return 0;
}

Resources