原博客地址 http://blog.csdn.net/mousebaby808/article/details/20299571
概述
有人问WNDCLASSEX结构体中cbWndExtra成员到底是做什么用的,在网上也查了一些资料,但说的都不太正确,MSDN上说的也较为含糊,但这个cbWndExtra成员的作用确实是较为重要,首先Windows默认的对话框类会用到它(即窗体类为#32770的对话框),几乎所有的Windows标准控件也会用到它,可以说cbWndExtra类给予了Windows窗体一个可扩展的途径,使得用户可以在HWND句柄中存储额外的数据。微软就是用这种方法在C语言上实施了面向对象“继承”的概念。
使用方法
下面以创建一个自定义组件为例,介绍WNDCLASSEX结构体中cbWndExtra成员的用法。
第一步,注册窗体类
在注册窗体类时,设置cbWndExtra成员的值。
- /// <summary>
- /// 窗体句柄附加数据长度
- /// </summary>
- #define CTRLWINDOWEXTRA 32
- /// <summary>
- /// 声明回调函数的宏
- /// </summary>
- #define DECLARE_WNDPROC(ProcName) \
- LRESULT CALLBACK ProcName(HWND, UINT, WPARAM, LPARAM);
- /// <summary>
- /// 声明钟表组件的消息回调函数
- /// </summary>
- DECLARE_WNDPROC(ClockCtrlProc)
- /// <summary>
- /// 注册窗体类
- /// </summary>
- static
- ATOM _RegistCtrlClass(HINSTANCE hInst, LPCTSTR lpszClsName, WNDPROC pWndProc)
- {
- WNDCLASSEX wcex = {
- sizeof(WNDCLASSEX),
- CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS,
- pWndProc,
- 0, CTRLWINDOWEXTRA, hInst, NULL, NULL,
- (HBRUSH)(COLOR_BTNFACE + 1),
- 0, lpszClsName, NULL
- };
- return RegisterClassEx(&wcex);
- }
- /// <summary>
- /// 初始化用户自定义控件
- /// </summary>
- BOOL InitializeUserControls()
- {
- ATOM atom = _RegistCtrlClass(_INSTANCE, _T("ClockCtrl"), (WNDPROC)ClockCtrlProc);
- _ASSERT(atom);
- if (atom == 0)
- return FALSE;
- return TRUE;
- }
其中,
CTRLWINDOWEXTRA宏就定义了要预留空间的大小,本例中为32字节,可以取值为0~40字节,而且数值应该为4的倍数(或者sizeof(long)的倍数)。
第二步:定义要存储的数据类型
- /// <summary>
- /// 保存绘图对象的结构体
- /// </summary>
- typedef struct tagCLK_GDIOBJ
- {
- HBRUSH brushBackground, // 控件背景画刷
- brushDigitalDark, // 数字表盘暗色画刷
- brushDigitalLight, // 数字表盘亮色画刷
- brushClockBackground; // 表盘背景
- LOGFONT fontDate, // 控件全局字体
- fontClockNumber; // 表盘数字字体
- HPEN penBorder, // 控件边框画笔
- penDigitalBorder, // 数字边框画笔
- penClockBorder, // 表盘边框画笔
- penClockArrow; // 表盘箭头画笔
- } CLK_GDIOBJ, *LPCLK_GDIOBJ;
- /// <summary>
- /// 保存运行时信息的结构体
- /// </summary>
- typedef struct tagCLK_RUNTIME
- {
- SYSTEMTIME stimNow; // 当前时间
- BOOL isDigitalSecondDark; // 数字表盘的秒针是否为灰色
- UINT_PTR timSecond; // 按秒进行的定时器句柄
- } CLK_RUNTIME, *LPCLK_RUNTIME;
- /// <summary>
- /// 表示数据位置的常量
- /// </summary>
- #define CLKP_GDIOBJ 0 // 0~3字节存放CLK_GDIOBJ结构体变量地址
- #define CLKP_RUNTIME (CLKP_GDIOBJ + sizeof(LPCLK_GDIOBJ)) // 4~7字节存放CLK_RUNTIME结构体变量地址
这里定义的两个宏
CLKP_GDIOBJ和
CLKP_RUNTIME分别用于存储上述两个结构体变量,由于存储的只能是指针类型(这两个结构体的大小必然超出了cbWndExtra成员的规定大小),所以两个宏表示的值相差4个字节。
第三步:存储数据
存储数据主要使用
SetWindowLongPtr(或者SetWindowLong,已不再推荐使用)函数,使用起来非常简单。
- /// <summary>
- /// 窗口创建消息
- /// </summary>
- static
- int _OnCreate(HWND hCtrl, LPCREATESTRUCT lpcs)
- {
- LPCLK_GDIOBJ pGdi;
- LPCLK_RUNTIME pRunTm;
- HFONT fontDef;
- if (!_LOCHEAP)
- _LOCHEAP = HeapCreate(0, 0, 0); // 创建本地堆句柄
- fontDef = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
- /// 设置默认的GDI对象
- pGdi = HeapAlloc(_LOCHEAP, HEAP_ZERO_MEMORY, sizeof(CLK_GDIOBJ));
- GetObject(fontDef, sizeof(LOGFONT), &pGdi->fontDate);
- GetObject(fontDef, sizeof(LOGFONT), &pGdi->fontClockNumber);
- pGdi->brushBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
- pGdi->brushClockBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
- pGdi->brushDigitalDark = CreateSolidBrush(RGB(0xEE, 0xEE, 0xEE));
- pGdi->brushDigitalLight = CreateSolidBrush(RGB(0xFF, 0, 0));
- pGdi->penBorder = CreatePen(PS_SOLID, 1, RGB(0x90, 0xC4, 0xE8));
- pGdi->penClockArrow = (HPEN)GetStockObject(BLACK_PEN);
- pGdi->penClockBorder = (HPEN)GetStockObject(BLACK_PEN);
- pGdi->penDigitalBorder = (HPEN)GetStockObject(WHITE_PEN);
- SetWindowLongPtr(hCtrl, CLKP_GDIOBJ, (LONG_PTR)pGdi); // 存储GDI对象结构体指针
- /// 设置默认的运行时状态
- pRunTm = HeapAlloc(_LOCHEAP, HEAP_ZERO_MEMORY, sizeof(CLK_RUNTIME));
- pRunTm->isDigitalSecondDark = FALSE;
- GetLocalTime(&pRunTm->stimNow);
- SetWindowLongPtr(hCtrl, CLKP_RUNTIME, (LONG_PTR)pRunTm); // 存储运行时状态结构体指针
- SendMessage(hCtrl, WM_START, 0, 0); // 发送启动消息
- return 0;
- }
第四步:获取数据
获取数据主要使用GetWindowLongPtr(同理,也可以为GetWindowLong,但不推荐)函数,执行之前代码的反向操作即可。
- /// <summary>
- /// 窗口销毁消息
- /// </summary>
- static
- void _OnDestory(HWND hCtrl)
- {
- LPCLK_GDIOBJ pGdi;
- LPCLK_RUNTIME pRunTm;
- pGdi = (LPCLK_GDIOBJ)GetWindowLongPtr(hCtrl, CLKP_GDIOBJ); // 获取GDI对象结构体
- if (pGdi)
- {
- /// 删除所有的GDI对象
- DeleteObject(pGdi->brushBackground);
- DeleteObject(pGdi->brushClockBackground);
- DeleteObject(pGdi->brushDigitalDark);
- DeleteObject(pGdi->brushDigitalLight);
- DeleteObject(pGdi->penBorder);
- DeleteObject(pGdi->penClockArrow);
- DeleteObject(pGdi->penClockBorder);
- HeapFree(_LOCHEAP, 0, pGdi); // 从内存中删除结构体
- }
- pRunTm = (LPCLK_RUNTIME)GetWindowLongPtr(hCtrl, CLKP_RUNTIME); // 获取运行时状态结构体
- if (pRunTm)
- {
- KillTimer(hCtrl, pRunTm->timSecond); // 停止定时器
- HeapFree(_LOCHEAP, 0, pRunTm); // 从内存中删除结构体
- }
- }
一些说明
首先,WNDCLASSEX结构体中cbWndExtra成员表示
“为每个窗体预留的空间大小”,即使用指定窗体类创建的每个窗体都有这么个空间,但存储的值各不相关。cbWndExtra的值可以在0~40之间,单位是字节,并非固定大小,可由程序员自行掌握。(例如Windows在创建标准对话框类#32770时,cbWndExtra成员值为DLGWINDOWEXTRA,DLGWINDOWEXTRA宏的值为30,表示每个对话框窗口有额外30个字节的空间可以使用)。
其次,设置和获取额外空间内容时,是按照字节数来获取而非数据存放的顺序索引。例如:SetWindowLongPtr(hWnd, 0, 1234L)表示在额外空间从0字节开始的位置设置内容1234,用掉4个字节;而SetWindowLongPtr(hWnd, 8, _T("Hello"))表示在额外空间的第9个字节开始设置内容"Hello"指针,也用掉4个字节。由于SetWindowLongPtr设置的值(以及GetWindowLongPtr获取的值)类型为sizeof(LONG_PTR)类型,所以对于额外空间的存取颗粒度应该总是4字节的。
其次,设置和获取额外空间内容时,是按照字节数来获取而非数据存放的顺序索引。例如:SetWindowLongPtr(hWnd, 0, 1234L)表示在额外空间从0字节开始的位置设置内容1234,用掉4个字节;而SetWindowLongPtr(hWnd, 8, _T("Hello"))表示在额外空间的第9个字节开始设置内容"Hello"指针,也用掉4个字节。由于SetWindowLongPtr设置的值(以及GetWindowLongPtr获取的值)类型为sizeof(LONG_PTR)类型,所以对于额外空间的存取颗粒度应该总是4字节的。
最后,cbWndExtra成员和使用GWLP_USERDATA(或者GWL_USERDATA)设置和获取的值无关,每个HWND句柄都关联了4(或者sizeof(void*))字节的空间,可以随时通过SetWindowLongPtr(hWnd, GWLP_USERDATA, 一个LONG值)设置以及通过GetWindowLongPtr(hWnd, GWLP_USERDATA)获取,但要使用cbWndExtra成员指定的空间,则必须在注册窗体类时,预先预留好指定的大小,否则无法使用。
代码中定义了一个时钟控件,由于代码只完成了数字时钟(还有一个指针时钟,但暂时没需求就先放下了),所以创建控件时一定要加上CLKS_DIGITAL扩展样式(即SetWindowLongPtr(hWnd, GWL_EXSTYLE,
CLKS_DIGITAL | (LONG)GetWindowLongPtr(hCtrl, GWL_EXSTYLE))),也可以设置WS_BORDER普通样式(即SetWindowLongPtr(hCtrl, GWL_STYLE,
WS_BORDER | (LONG)GetWindowLongPtr(hCtrl, GWL_STYLE));),这样会有边框,好看一些。
例子中的控件是放在对话框上的,直接使用了资源编辑器的
Custom Control项,在属性里设置Class为“ClockCtrl”即可,当然也可以通过CreateWindowEx函数通过“ClockCtrl”类名直接创建该控件,执行后的样子大概如下: