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;
}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:
myCtrl = malloc(sizeof(MyControl));
if(myCtrl == NULL)
return FALSE;
myCtrl->text = TEXT("Just a text");
myCtrl->crColor = RGB(0,20,40);
myCtrl->width = 120;
SetWindowLong(hwnd, 0, (LONG) myCtrl);
return TRUE;
case WM_DESTROY:
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 a MyControl
using the GetWindowLong()
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 the malloc()
function to allocate a new structure (Of course, if you're using C++ you can use new
). - 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 new myCtrl
in the window extra bytes area). - If you use
malloc()
(or new
, there must be a consecutive free()
or delete
). So we've processed the last message that every window receives, which is the WM_NCDESTROY
message to free the allocated structure, at last.
Now What ?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:
myCtrl = malloc(sizeof(MyControl));
if(myCtrl == NULL)
return FALSE;
myCtrl->text = TEXT("Just a text");
myCtrl->crColor = RGB(0,20,40);
myCtrl->width = 120;
SetWindowLong(hwnd, GWL_USERDATA, (LONG) myCtrl);
return TRUE;
case WM_DESTROY:
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.
HWND CreateWindow(HINSTANCE hInstance, HWND hParent)
{
MyControl myCtrl;
myCtrl.text = TEXT("Just a text");
myCtrl.crColor = RGB(0,20,40);
myCtrl.width = 120;
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 *;
if(msg == WM_NCCREATE)
{
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG) (( (CREATESTRUCT *) lParam)->lpCreateParams) );
}
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