WNDCLASS结构中cbWndExtra数据成员的作用

原博客地址 http://blog.csdn.net/mousebaby808/article/details/20299571


概述


  有人问WNDCLASSEX结构体中cbWndExtra成员到底是做什么用的,在网上也查了一些资料,但说的都不太正确,MSDN上说的也较为含糊,但这个cbWndExtra成员的作用确实是较为重要,首先Windows默认的对话框类会用到它(即窗体类为#32770的对话框),几乎所有的Windows标准控件也会用到它,可以说cbWndExtra类给予了Windows窗体一个可扩展的途径,使得用户可以在HWND句柄中存储额外的数据。微软就是用这种方法在C语言上实施了面向对象“继承”的概念。


使用方法


  下面以创建一个自定义组件为例,介绍WNDCLASSEX结构体中cbWndExtra成员的用法。


 第一步,注册窗体类


  在注册窗体类时,设置cbWndExtra成员的值。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /// <summary>  
  2. /// 窗体句柄附加数据长度  
  3. /// </summary>  
  4. #define CTRLWINDOWEXTRA 32  
  5.   
  6.   
  7. /// <summary>  
  8. /// 声明回调函数的宏  
  9. /// </summary>  
  10. #define DECLARE_WNDPROC(ProcName)   \  
  11.     LRESULT CALLBACK ProcName(HWNDUINTWPARAMLPARAM);  
  12.   
  13.   
  14. /// <summary>  
  15. /// 声明钟表组件的消息回调函数  
  16. /// </summary>  
  17. DECLARE_WNDPROC(ClockCtrlProc)  
  18.   
  19.   
  20. /// <summary>  
  21. /// 注册窗体类  
  22. /// </summary>  
  23. static  
  24. ATOM _RegistCtrlClass(HINSTANCE hInst, LPCTSTR lpszClsName, WNDPROC pWndProc)  
  25. {  
  26.     WNDCLASSEX wcex = {  
  27.         sizeof(WNDCLASSEX),  
  28.         CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS,  
  29.         pWndProc,  
  30.         0, CTRLWINDOWEXTRA, hInst, NULL, NULL,  
  31.         (HBRUSH)(COLOR_BTNFACE + 1),  
  32.         0, lpszClsName, NULL  
  33.     };  
  34.     return RegisterClassEx(&wcex);  
  35. }  
  36.   
  37.   
  38. /// <summary>  
  39. /// 初始化用户自定义控件  
  40. /// </summary>  
  41. BOOL InitializeUserControls()  
  42. {  
  43.     ATOM atom = _RegistCtrlClass(_INSTANCE, _T("ClockCtrl"), (WNDPROC)ClockCtrlProc);  
  44.     _ASSERT(atom);  
  45.     if (atom == 0)  
  46.         return FALSE;  
  47.     return TRUE;  
  48. }  
  其中, CTRLWINDOWEXTRA宏就定义了要预留空间的大小,本例中为32字节,可以取值为0~40字节,而且数值应该为4的倍数(或者sizeof(long)的倍数)。


 第二步:定义要存储的数据类型


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /// <summary>  
  2. /// 保存绘图对象的结构体  
  3. /// </summary>  
  4. typedef struct tagCLK_GDIOBJ  
  5. {  
  6.     HBRUSH brushBackground,     // 控件背景画刷  
  7.         brushDigitalDark,       // 数字表盘暗色画刷  
  8.         brushDigitalLight,      // 数字表盘亮色画刷  
  9.         brushClockBackground;   // 表盘背景  
  10.     LOGFONT fontDate,       // 控件全局字体  
  11.         fontClockNumber;    // 表盘数字字体  
  12.     HPEN penBorder,         // 控件边框画笔  
  13.         penDigitalBorder,   // 数字边框画笔  
  14.         penClockBorder,     // 表盘边框画笔  
  15.         penClockArrow;      // 表盘箭头画笔  
  16. } CLK_GDIOBJ, *LPCLK_GDIOBJ;  
  17.   
  18.   
  19. /// <summary>  
  20. /// 保存运行时信息的结构体  
  21. /// </summary>  
  22. typedef struct tagCLK_RUNTIME  
  23. {  
  24.     SYSTEMTIME stimNow; // 当前时间  
  25.     BOOL isDigitalSecondDark;   // 数字表盘的秒针是否为灰色  
  26.     UINT_PTR timSecond; // 按秒进行的定时器句柄  
  27. } CLK_RUNTIME, *LPCLK_RUNTIME;  
  28.   
  29.   
  30. /// <summary>  
  31. /// 表示数据位置的常量  
  32. /// </summary>  
  33. #define CLKP_GDIOBJ 0   // 0~3字节存放CLK_GDIOBJ结构体变量地址  
  34. #define CLKP_RUNTIME    (CLKP_GDIOBJ + sizeof(LPCLK_GDIOBJ))    // 4~7字节存放CLK_RUNTIME结构体变量地址  
  这里定义了两个结构体,一个用于存储GDI对象,一个用于存储运行时状态,这两个结构体的变量都应该和对应的窗口(HWND)句柄进行绑定,这样才能做到令窗体类为“ClockCtrl”的不同窗体都能取到正确的,属于自己的数据。
  这里定义的两个宏 CLKP_GDIOBJCLKP_RUNTIME分别用于存储上述两个结构体变量,由于存储的只能是指针类型(这两个结构体的大小必然超出了cbWndExtra成员的规定大小),所以两个宏表示的值相差4个字节。


 第三步:存储数据


  存储数据主要使用 SetWindowLongPtr(或者SetWindowLong,已不再推荐使用)函数,使用起来非常简单。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /// <summary>  
  2. /// 窗口创建消息  
  3. /// </summary>  
  4. static  
  5. int _OnCreate(HWND hCtrl, LPCREATESTRUCT lpcs)  
  6. {  
  7.     LPCLK_GDIOBJ pGdi;  
  8.     LPCLK_RUNTIME pRunTm;  
  9.     HFONT fontDef;  
  10.   
  11.     if (!_LOCHEAP)  
  12.         _LOCHEAP = HeapCreate(0, 0, 0);     // 创建本地堆句柄  
  13.   
  14.     fontDef = (HFONT)GetStockObject(DEFAULT_GUI_FONT);  
  15.   
  16.     /// 设置默认的GDI对象  
  17.     pGdi = HeapAlloc(_LOCHEAP, HEAP_ZERO_MEMORY, sizeof(CLK_GDIOBJ));  
  18.     GetObject(fontDef, sizeof(LOGFONT), &pGdi->fontDate);  
  19.     GetObject(fontDef, sizeof(LOGFONT), &pGdi->fontClockNumber);  
  20.     pGdi->brushBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);  
  21.     pGdi->brushClockBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);  
  22.     pGdi->brushDigitalDark = CreateSolidBrush(RGB(0xEE, 0xEE, 0xEE));  
  23.     pGdi->brushDigitalLight = CreateSolidBrush(RGB(0xFF, 0, 0));  
  24.     pGdi->penBorder = CreatePen(PS_SOLID, 1, RGB(0x90, 0xC4, 0xE8));  
  25.     pGdi->penClockArrow = (HPEN)GetStockObject(BLACK_PEN);  
  26.     pGdi->penClockBorder = (HPEN)GetStockObject(BLACK_PEN);  
  27.     pGdi->penDigitalBorder = (HPEN)GetStockObject(WHITE_PEN);  
  28.     SetWindowLongPtr(hCtrl, CLKP_GDIOBJ, (LONG_PTR)pGdi);   // 存储GDI对象结构体指针  
  29.   
  30.     /// 设置默认的运行时状态  
  31.     pRunTm = HeapAlloc(_LOCHEAP, HEAP_ZERO_MEMORY, sizeof(CLK_RUNTIME));  
  32.     pRunTm->isDigitalSecondDark = FALSE;  
  33.     GetLocalTime(&pRunTm->stimNow);  
  34.     SetWindowLongPtr(hCtrl, CLKP_RUNTIME, (LONG_PTR)pRunTm);    // 存储运行时状态结构体指针  
  35.   
  36.     SendMessage(hCtrl, WM_START, 0, 0); // 发送启动消息  
  37.     return 0;  
  38. }  
  可以看到,上述代码使用 SetWindowLongPtr函数,在0(CLKP_GDIOBJ)位置存储了CLK_GDIOBJ结构体的指针,在4(CLKP_RUNTIME)位置存储了CLK_RUNTIME结构体的指针,这样就相当于我们扩展了HWND句柄,在其中存储了我们所需的数据。


 第四步:获取数据


  获取数据主要使用GetWindowLongPtr(同理,也可以为GetWindowLong,但不推荐)函数,执行之前代码的反向操作即可。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /// <summary>  
  2. /// 窗口销毁消息  
  3. /// </summary>  
  4. static  
  5. void _OnDestory(HWND hCtrl)  
  6. {  
  7.     LPCLK_GDIOBJ pGdi;  
  8.     LPCLK_RUNTIME pRunTm;  
  9.   
  10.     pGdi = (LPCLK_GDIOBJ)GetWindowLongPtr(hCtrl, CLKP_GDIOBJ);  // 获取GDI对象结构体  
  11.     if (pGdi)  
  12.     {  
  13.         /// 删除所有的GDI对象  
  14.         DeleteObject(pGdi->brushBackground);  
  15.         DeleteObject(pGdi->brushClockBackground);  
  16.         DeleteObject(pGdi->brushDigitalDark);  
  17.         DeleteObject(pGdi->brushDigitalLight);  
  18.         DeleteObject(pGdi->penBorder);  
  19.         DeleteObject(pGdi->penClockArrow);  
  20.         DeleteObject(pGdi->penClockBorder);  
  21.   
  22.         HeapFree(_LOCHEAP, 0, pGdi);    // 从内存中删除结构体  
  23.     }  
  24.   
  25.     pRunTm = (LPCLK_RUNTIME)GetWindowLongPtr(hCtrl, CLKP_RUNTIME);  // 获取运行时状态结构体  
  26.     if (pRunTm)  
  27.     {  
  28.         KillTimer(hCtrl, pRunTm->timSecond); // 停止定时器  
  29.         HeapFree(_LOCHEAP, 0, pRunTm);  // 从内存中删除结构体  
  30.     }  
  31. }  
  可以看到,上述代码使用 GetWindowLongPtr函数,在0(CLKP_GDIOBJ)位置获取了CLK_GDIOBJ结构体的指针,在4(CLKP_RUNTIME)位置获取了CLK_RUNTIME结构体的指针,这样就取到了我们之前存储的数据。


一些说明


  首先,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字节的。
  最后,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”类名直接创建该控件,执行后的样子大概如下:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值