将类/结构与窗口关联






4.92/5 (9投票s)
将类或结构与窗口关联的几种方法及其差异
引言
在使用 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()
分配)。动态内存总是很麻烦,您必须释放它,必须检查它是否正确分配……等等。但使用这种方法,您不必使用动态内存。
延伸阅读
- Catch22 上的文章: http://www.catch22.net/tuts/custom-controls
- 如果您想实现像 MFC 中的类,这篇是最好的: http://www.infernodevelopment.com/c-win32-api-simple-gui-wrapper