Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Associating a Class/Structure with a Window

4.92/5 (9 votes)
6 Jan 2013CPOL4 min read 26.4K  
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).

C++
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.

C++
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 :

C++
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 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 :

C++
 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. 

C++
 //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 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)