65.9K
CodeProject 正在变化。 阅读更多。
Home

将类/结构与窗口关联

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (9投票s)

2013年1月6日

CPOL

4分钟阅读

viewsIcon

27245

将类或结构与窗口关联的几种方法及其差异

引言

在使用 Windows API 创建自定义控件时,挑战之一是将用户定义的数据与控件关联起来。这可能是一个 C++ 类或一个结构。本文讨论了几种将数据与窗口关联的方法。(不仅是自定义控件,自定义控件也是一种窗口。这些方法适用于 Windows 中的任何窗口)。 

1. 使用窗口额外字节。 

2. 使用 GWL_USERDATA。

在这两种情况下,我们都将一个指向类/结构的指针与一个窗口关联起来。

创建示例结构

我们将以下结构与我们的控件(因此它是一个窗口)关联起来。

typedef tagMyControl
{
    TCHAR *  text;
    COLORREF crColor;
    int      width;
    //... and/or more
}MyControl;      

如果您愿意,也可以使用类。这没什么区别。两者都可以(至少在我们当前将一个与窗口关联的情况下)。

1. 使用窗口额外字节

您可以使用窗口额外字节来存储指向您预定义控件的指针。在注册控件的窗口类时,您可以将窗口额外字节的大小设置为指向您预定义结构或类(在本例中为上述结构)的大小。

wc.cbWndExtra     = sizeof( MyControl * );

现在,窗口为您分配了 sizeof( MyControl * ) 字节。但尚未初始化。您可以使用 WM_NCCREATE 消息来初始化您的控件。

我们自定义控件的窗口过程

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

我做了什么?

  • 当消息被发送到 ControlProc() 时,它将首先使用 GetWindowLong() 函数获取指向 MyControl 的指针。使用 GetWindowLong() 的最后一个参数,我们指定我们需要存储在窗口额外字节区域的数据。(此时,数据尚未初始化)。
  • 每个窗口收到的第一条消息是 WM_NCCREATE 消息。因此,我们处理了这条消息来初始化控件。我们使用 malloc() 函数分配了一个新结构(当然,如果您使用 C++,可以使用 new)。
  • 我们为分配的结构设置了一些值。然后,我们使用 SetWindowLong() 更新了额外字节的数据(这意味着我们将新的 myCtrl 存储在窗口额外字节区域)。
  • 如果您使用 malloc()(或 new),则必须有相应的 free()delete。因此,我们处理了每个窗口收到的最后一条消息,即 WM_NCDESTROY 消息,以最终释放分配的结构。
然后呢?

现在我们已经成功地将一个结构与一个窗口关联起来。通过这种方式,您可以创建多个行为相同但具有不同数据的窗口。

2. 使用 GWL_USERDATA。 

每个窗口都有一个称为 GWL_USERDATA 的属性。它是一个初始值为 0 的值。您可以使用它来存储指向我们结构的指针。(我不知道这个数据区域的大小,但它足以存储指向结构的指针)。

现在窗口过程看起来像这样

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

这和之前的代码一样,不是吗?好吧,差不多是这样。区别在于,在所有对 GetWindowLong()SetWindowLong() 的调用中,我都将最后一个参数的 0 替换为了 GWL_USERDATA。  

如果两者都相同,为什么还要使用 GWL_USERDATA?

仅仅是因为您可以使用窗口额外字节用于其他用途。额外字节的大小可以很大(它确实如此,这是您在注册窗口类时选择的),但 GWL_USERDATA 的大小是唯一的。简单来说,我的意思是,指针在字节大小方面并不多,所以如果您还有其他数据要存储在窗口的额外字节区域,您有第二个选择来使用 GWL_USERDATA

一点点不同 

现在我们已经成功地通过两种方式(您可以一次使用其中一种)将一个结构/类与一个窗口关联起来。但在所有这些情况下,我们都是在窗口过程中初始化我们的结构。如果我们想在窗口过程外部进行呢?这是另一个问题!但有办法解决。

当使用 CreateWindow()(或使用 CreateWindowEx())创建窗口时,最后一个参数的值会通过 WM_NCCREATE(和 WM_CREATE)传递给 Windows 过程。它的类型是 LPVOID(这只是 void *)。因此,您可以在窗口过程外部创建并初始化您的结构或类,然后使用 CreateWindow() 将其传递给窗口过程。然后,您可以使用 GWL_USERDATA 区域来存储传递的指针。 

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

这样做有什么好处?

我看到的第一件事是,在使用早期方法时,您必须使用动态内存(它使用 malloc() 分配)。动态内存总是很麻烦,您必须释放它,必须检查它是否正确分配……等等。但使用这种方法,您不必使用动态内存。 

延伸阅读 

© . All rights reserved.