1. 程式人生 > >關於WNDCLASSEX結構體中cbWndExtra成員的作用

關於WNDCLASSEX結構體中cbWndExtra成員的作用

概述

  有人問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結構體變數地址
  這裡定義了兩個結構體,一個用於儲存GDI物件,一個用於儲存執行時狀態,這兩個結構體的變數都應該和對應的視窗(HWND)控制代碼進行繫結,這樣才能做到令窗體類為“ClockCtrl”的不同窗體都能取到正確的,屬於自己的資料。   這裡定義的兩個巨集CLKP_GDIOBJCLKP_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;
}
  可以看到,上述程式碼使用SetWindowLongPtr函式,在0(CLKP_GDIOBJ)位置儲存了CLK_GDIOBJ結構體的指標,在4(CLKP_RUNTIME)位置儲存了CLK_RUNTIME結構體的指標,這樣就相當於我們擴充套件了HWND控制代碼,在其中儲存了我們所需的資料。

 第四步:獲取資料

  獲取資料主要使用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);	// 從記憶體中刪除結構體
	}
}
  可以看到,上述程式碼使用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”類名直接建立該控制元件,執行後的樣子大概如下:

相關推薦

關於WNDCLASSEX結構cbWndExtra成員作用

概述   有人問WNDCLASSEX結構體中cbWndExtra成員到底是做什麼用的,在網上也查了一些資料,但說的都不太正確,MSDN上說的也較為含糊,但這個cbWndExtra成員的作用確實是較為重要,首先Windows預設的對話方塊類會用到它(即窗體類為#32770

關於類和結構靜態成員的使用

類中靜態成員的使用 靜態成員資料 1.c++靜態成員是類的所有物件共享的成員,只能被定義一次。靜態資料成員所佔的空間不會隨著物件的產生而分配,也不會隨著物件的消失而回收。定義為私有的靜態資料成員不能被外界所訪問。靜態資料成員可由任意訪問許可權許可的函式所訪問。 2.由於靜態

輸出結構所有成員變數的值到檔案

輸出結構體中,所有成員變數的值到檔案之中的小例子  #include <stdio.h> #include <string.h> #include <sys/types

結構最後成員為一個數組(長度為零)與一個指標

1. 結構體中最後一個數組長度為零 typedef struct _ex_mng { unsigned int type; unsigned int oper; char data[0]; }ex_mng_t; 最近在專案常用到這樣子的一個結

在C語言結構添加成員函數

我們 pau 打印 log print class 控制 stdio.h 語言   我們在使用C語言的結構體時,經常都是只定義幾個成員變量,而學過面向對象的人應該知道,我們定義類時,不只是定義了成員變量,還定義了成員方法,而類的結構和結構體非常的相似,所以,為什麽不想想如何

結構有指標成員

先上程式碼: #include <iostream> using namespace std; struct example { int iNum; char*

c結構char[0]的作用

struct MyData {    int nLen;    char data[0];};          開始沒有理解紅色部分的內容,上網搜尋下,發現用處很大,記錄下來。 在結構中,data

結構存在string型別成員

#include <iostream> #include <string> #include <cstdio> using namespace std; typedef struct node{ string str; }N

結構的陣列成員的賦值問題

#include <iostream> using namespace std; struct student {   char name[20];   int age; }; int main( ) {  student s;  s.name="gyy";

C# 8: 可變結構的只讀例項成員

在之前的文章中我們介紹了 C# 中的 [只讀結構體(readonly struct)](https://mp.weixin.qq.com/s/wwVZbdY7m7da1nmIKb2jCA)[^1] 和與其緊密相關的 [`in` 引數](https://ittranslator.cn/dotnet/csharp

c語言的特殊符號(結構

結合 特殊 value -s height 間接尋址 出現 span size  在“結構”中出現的->運算符成為“右箭頭選擇”,可以用new_node->value = 10;來代替(*new_code).value = 10;即運算符->是運算符*和運

golang 結構的匿名接口

imp -i pil win CA git Go mean others golang 結構體中的匿名接口 代碼示例 golang 中,可以給結構體增加匿名field,可參考 unknwon 大神的書。 匿名字段和內嵌結構體 但,golang同時也可以給結構體定義一個匿名i

C語言中free()函數釋放struct結構的規律

void poi inf clu main 圖片 刪除 動態分配 不同 並不是什麽新鮮的事情,不過值得註意。首先我們知道,在使用struct來定義並聲明一個變量時,將會自動劃分出一個連續的儲存空間(雖然根據某些對齊原則會出現內存間隙,但是大體上來說還是連續的)這一塊連續空間

OpencvMat結構元素的獲取與賦值

【OpenCV3影象處理】Mat中元素的獲取與賦值 ( 對比.at<>()函式 和 .ptr<>()函式) 2017年04月12日 10:08:55 閱讀數:7542 標籤: opencvopencv3 更多 個人分類:&nbs

C語言結構冒號(位域)用法

位域出現的原因是由於某些資訊的儲存表示只需要幾個bit位就可以表示而不需要一個完整的位元組,同時也是為了節省儲存空間和方便處理。   typedef struct  bit_struct {     int &n

結構運算子的過載

C++中,結構體是無法進行==,>,<,>=,<=,!=這些操作的,這也帶來了很多不方便的地方,尤其是在使用STL容器的時候,如果我們可以往語句中傳入結構體,一些事情將會變得很簡單。 比如二分查詢,binary_crearch只能對陣列進行查詢,如果是結構

C++組合(聚合)與C結構包含函式

C++組合(聚合)與C結構體中包含函式 今天突然想到C++的聚合,以前一直沒有注意,今天想到就寫下來,做個筆記; C++的類與我們的C語言中的結構體特別像,但是有有些不太一樣,這裡不多累贅了不能,大家學過的都知道。 C++組合(聚合) 我們知道的都是C++的類的物件,

結構的指標,用malloc初始化時,沒有分配足夠的記憶體空間,造成下述錯誤

      對結構體中的指標,初始化和釋放,遇到堆損壞問題(附連結點選開啟連結)點選開啟連結) out_defect.texturing = (TEXTURING *)malloc(sizeof(TEXTURING

結構使用函式指標

 struct kobj _ type  {  void (*release)(struct kobject *);  struct sysfs _ ops * sysfs _ ops;  struct attribute ** default _

C++:對結構字元陣列賦值時,出現表示式必須是可修改的左值的問題

問題描述:         在C++中為結構體中的字元陣列賦值時,出現”表示式必須是可修改的左值“的錯誤提醒,編譯報錯“不可指定資料型別”。           &n