Associating a Class/Structure with a Window






4.92/5 (9 votes)
Several ways of associating a class or a structure with a window and their differences
Introduction
When creating custom controls using the Windows API, one of the challenges is associating user defined data with the control. Which may probably a C++ class or a structure. This article discusses about several ways of associating data with a window. (Not only a custom control, that's a type of a window. These methods are valid for any window in Windows).
1. Using Window Extra bytes.
2. Using GWL_USERDATA.
In both cases we will be associating a pointer to a Class/Struct to a window.
Creating a Sample Structure
We will associate the following structure with our control (hence it's a window).
typedef tagMyControl
{
TCHAR * text;
COLORREF crColor;
int width;
//... and/or more
}MyControl;
You can also use a class instead, if you'll prefer. It doesn't really a matter. Both, the same (only in our case, which is associating a one with a window).
1. Using Window Extra bytes
You can use the Window Extra bytes to store a pointer to your pre-defined control. You can set the window extra bytes' size to the size of a pointer to your pre-defined structure or class (in this case the above structure ) , when registering the window class of your control.
wc.cbWndExtra = sizeof( MyControl * );
Now the window has sizeof( MyControl * )
bytes allocated for you. But not yet initialized. You can use the WM_NCCREATE
message to initialize your control.
The Window Procedure of our custom control :
LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MyControl * myCtrl = (MyControl *) GetWindowLong(hwnd, 0);
switch(msg)
{
case WM_NCCREATE:
//Allocate a new structure
myCtrl = malloc(sizeof(MyControl));
//if not allocated, stop window creation
if(myCtrl == NULL)
return FALSE;
//Initialize the structure
myCtrl->text = TEXT("Just a text");
myCtrl->crColor = RGB(0,20,40);
myCtrl->width = 120;
//attach the new structure to 'hwnd'
SetWindowLong(hwnd, 0, (LONG) myCtrl);
return TRUE;
case WM_DESTROY:
//Free up the structure, if not it'll cause memory leaks
free(myCtrl);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
What Have I Done ?
- When a message is received to
ControlProc()
it'll first get a pointer to aMyControl
using theGetWindowLong()
function. Using the last parameter to GetWindowLong() we specify that we need the data stored in the Window's extra bytes area. (At this point, the data is not initialized). - The First message that every window receives is the
WM_NCCREATE
message. So we've processed this message to initialize the control. We used themalloc()
function to allocate a new structure (Of course, if you're using C++ you can usenew
). - We've set some values to the structure that we allocated. Then, we've updated the extra bytes' data using
SetWindowLong()
, (which means we stored the newmyCtrl
in the window extra bytes area). - If you use
malloc()
(ornew
, there must be a consecutivefree()
ordelete
). So we've processed the last message that every window receives, which is theWM_NCDESTROY
message to free the allocated structure, at last.
Now we have successfully associated a structure with a window. This way you can create multiple windows that act as the same way, but has different data.
2. Using the GWL_USERDATA.
Every window has something called GWL_USERDATA
, It is a value which is initially 0, You can use this to store a pointer to our structure. ( I don't know the size of this data area, but it is enough to store a pointer to a structure).
Now the window procedure looks like :
LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MyControl * myCtrl = (MyControl *) GetWindowLong(hwnd, GWL_USERDATA);
switch(msg)
{
case WM_NCCREATE:
//Allocate a new structure
myCtrl = malloc(sizeof(MyControl));
//if not allocated, stop window creation
if(myCtrl == NULL)
return FALSE;
//Initialize the structure
myCtrl->text = TEXT("Just a text");
myCtrl->crColor = RGB(0,20,40);
myCtrl->width = 120;
//attach the new structure to 'hwnd'
SetWindowLong(hwnd, GWL_USERDATA, (LONG) myCtrl);
return TRUE;
case WM_DESTROY:
//Free up the structure, if not it'll cause memory leaks
free(myCtrl);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
It's the same as the previous code, Isn't it ? Well, almost yes, The difference is that I have replaced the 0
to GWL_USERDATA
in last parameter to all the calls to GetWindowLong()
and SetWindowLong()
.
If both are the same, Why GWL_USERDATA ?
It's just because you can use window extra bytes for other uses. Size of the extra bytes can be big (which it is, your choice when registering the window class), but size of the GWL_USERDATA
is unique. Simply, what I'm saying is that a pointer is not much in bytes of its size, so if you have other data to store in window's extra bytes area, you have a second option to use GWL_USERDATA
.
A little difference
Now we have successfully associated a structure/class with a window in two ways (you can use one of them at once). But In both cases, we initialized our structure inside the Window Procedure. What if we want to do it out side the Window Procedure. Another Problem ! But there's a way.
When creating a window using CreateWindow()
(or using CreateWindowEx()
), the last parameter's value is passed to the Windows procedure through WM_NCCREATE
( and WM_CREATE
). It's type is in LPVOID
(which is simply void *
). So you can create and initialize your structure or class out-side the Window Procedure, and pass it to Window Procedure using CreateWindow().
Then you can use GWL_USERDATA
area to store the pointer which is passed.
//Just a function to call, no exact rule
HWND CreateWindow(HINSTANCE hInstance, HWND hParent)
{
MyControl myCtrl;
myCtrl.text = TEXT("Just a text");
myCtrl.crColor = RGB(0,20,40);
myCtrl.width = 120;
//last parameter is set to (LPVOID) myCtrl.
return CreateWindow(
TEXT("your_control_classname_here"), TEXT("window_text_here"),
WS_CHILD | WS_VISIBLE, 0, 0, 100, 100, hParent, 0, hInstance, (LPVOID) myCtrl);
}
LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MyControl myCtrl *;
//Grab the 'myCtrl' sent from 'CreateWindow()' and assign it to GWL_USERDATA
if(msg == WM_NCCREATE)
{
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG) (( (CREATESTRUCT *) lParam)->lpCreateParams) );
}
//Get the pointer for further use
myCtrl = (MyControl *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
return DefWindowProc(hwnd, msg, wParam, lParam);
}
What is the advantage of doing so ?
The first thing I see is that, when using the earlier methods you had to use Dynamic memory (which was allocated using malloc()
). Dynamic memory is always trouble, you have free it, have to check if allocated properly.... and so on. But using this method, you don't use dynamic memory.
Further Reading
- The article at the Catch22 : http://www.catch22.net/tuts/custom-controls
- If you want to implement a class like the ones in MFC this one is the best : http://www.infernodevelopment.com/c-win32-api-simple-gui-wrapper