C child window procedure - c

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

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;
//...

win api - how to implement a separate events procedure for tree view

I have created a main window (CreateWindowEx) with events procedure set using WNDCLASSEXW:
wcex.lpfnWndProc = WndProc;.
Next I have created a child window which is used as tree view:
HWND hwndTRV = CreateWindowEx(
WS_EX_CLIENTEDGE,
WC_TREEVIEW,
TEXT("Tree View"),
WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES | TVS_EDITLABELS,
0,
0,
rcClient.right,
rcClient.bottom,
hwndParent,
NULL,
hInstance,
NULL);
I would like to handle a tree view specific events in the separate function. I use the following code:
SetWindowLongPtr(hwndTRV, GWLP_WNDPROC, (LONG_PTR)TRVProc);
How should I implement the TRVProc ? Here is an example code, TVN_BEGINLABELEDIT and TVN_ENDLABELEDIT events are not visible in TRVProc:
LRESULT CALLBACK TRVProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_NOTIFY:
{
LPNMHDR l = (LPNMHDR)lParam;
switch (l->code)
{
case TVN_BEGINLABELEDIT:
{
return 0;
}
case TVN_ENDLABELEDIT:
{
return 1;
}
}
}
}
return CallWindowProc(WndProc, hWnd, message, wParam, lParam);
}
A message loop looks this way:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Implement a separate events procedure for tree view is possible based on the document:
Calling SetWindowLongPtr with the GWLP_WNDPROC index creates a
subclass of the window class used to create the window. An application
must pass any messages not processed by the new window procedure to
the previous window procedure by calling CallWindowProc. This allows
the application to create a chain of window procedures.
You want to replace the default window procedure of parent window (hwndParent) instead of tree view window (hwndTRV). So the related code line will like this:
SetWindowLongPtr(hwndParent, GWLP_WNDPROC, (LONG_PTR)TRVProc);
Additionally, to receive TVN_BEGINLABELEDIT and TVN_ENDLABELEDIT notification code you may have add some items to the tree view first. Then when you edit item's label the TRVProc will receive above notification code. For an example of how to add some items you can refer to this official document.

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;

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;
}

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

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.

Resources