作者:vcbear
翻譯說明:以前一直沒有很在意窗口類的使用,我近期在用vc做一些界面時遇到窗口類定義的概念,這次翻MSDN的時候看到了一篇文章,講到窗口類的一些基本概念,匆匆看完,翻譯出來當學習筆記,可能有一些細節上的謬誤,請包涵。放在網上,如果大家覺得值得參考,就看看。
在MFC編程裏,窗口類的概念接觸的已經比較少了,在SDK編程的時候則至少還需要聲明一個WNDCLASS(EX)類RegisterClass(Ex)一下。但是窗口類在windows中是無所不在的,雖然不是很深奧的東西,瞭解一下其中細節,個人覺得對編程應該是有好處的。
<正文>
探索Win32系統之窗口類(Window Classes in Win32)
Kyle Marsh
Microsoft Developer Network Technology Group
MSDN技術組
本文主要介紹win32系統裏窗口類的運做和使用機制,探索一些細節問題,使win32窗口類的信息更加明朗化。
在本文中,"類","窗口類"這兩個術語等同,都不是指C++類,而是指和窗口相關的一組信息的集合。
窗口類的風格決定了窗口的外觀和風格。所有的窗口都會屬於某一個窗口類。在創建一個窗口之前,必須註冊(register)一個相應的窗口類。32位Windows操作系統類可以註冊被系統裏所有的程序所使用的窗口類。
大部分開發人員認爲窗口類是個麻煩的東西,他們頂多就是從例子或其他代碼中拷貝一個RegisterClass函數,修改一下部分參數而已。這彷彿有些輕視了,沒有發揮窗口類的作用。本文將對此進行探索,並且描述窗口類如何的使應用程序得到優化。
我們的將討論的題目包括:
- 什麼是窗口類(Windows Classes)
- 系統全局,應用程序全局,應用程序局部類的區別
- 類裏包含了那些信息
- 這些信息如何影響窗口的表現
- 應用程序如何使用窗口類信息。
一:窗口類的類型
window系統提供了三種類型的窗口類
- 系統全局類(System global classes)
- 應用程序全局類(Application global classes)
- 應用程序局部類(Application local classes)
1.系統全局窗口類(System Global Classes)
windows 本身註冊了幾個系統全局類供全部的應用程序使用,這些類包括了以下的常用標準窗口控件
- Listbox (列表框)
- ComboBox (下拉組合框)
- ScrollBar (滾動條)
- Button (按鈕)
- Static (靜態標籤)
- Edit (編輯框)
以及其他不那麼常用的控件如TabCtrl等.
還有:
- 菜單窗口
- 桌面窗口
- 對話框窗口
- 任務條窗口
- 題頭帶圖標的窗口
- ComboLBox:ComBoBox控件的下拉列表窗口
- MDIClient: MDI風格窗口的子窗口
WindowsNT爲DDEML(Dynamic Data Exchange Management Library)增加了DDEMLEvent類,因爲DDEML功能已經結合到USER裏去了。
Windows 95/98不註冊類 #32772,因爲它不使用題頭帶圖標風格的窗口(由於我用的是win2k操作系統,這一點沒有嘗試)
所有的win32應用程序都可以使用系統全局類,但不能增加或刪除一個這樣的類。
應用程序可以通過“子類化”(SubClassing)這些類來改變系統全局類的屬性。在Win32裏,應用程序子類化某個系統全局類只會影響本進程內窗口的表現,而不會影響另外一個進程或應用程序。比起相應的操作會影響其他窗口的win16時代,這是一個進步。
在Win32裏,Ms鼓勵“子類化”系統類的行爲。因爲這個技術可以非常有效和方便的改變窗口的表現。例如:如果應用程序希望限制edit控件的輸入和編輯行爲,可以通過子類化edit類並設置一個新的窗口過程(WindowProc)來自己處理處理鍵盤操作來實現。子類化以後,此應用程序裏創建的edit控件將使用新的窗口過程,以代替標準的edit控件窗口過程。
系統全局窗口類實現
現在的win32平臺使系統類和各32位進程互不相干,系統類如何實現的並不會直接影響應用程序。本節將描述系統類的實現,當然,跳過此節並不妨礙全文的閱讀和理解。
系統全局類在win9x和win3.1的實現使相當相象的。在系統啓動時,USER模塊創建了系統類。win9x和win3.1不同的是:當發現一個應用程序子類化了某個系統類的時候,win9x將進行如下工作:
- 如果在debug模式下運行,在debug屏幕上顯示一個 warning 信息
- 複製一份被子類化的窗口類的信息 。將複製的新類填加到應用程序的“私有”系統類列表裏。win9x系統裏,系統爲每個進程都保持了這樣的一個列表,以供系統存放系統全局類的克隆信息。
- 強制進程裏所有的子類化過的窗口實例使用這個系統類的拷貝。但這不影響已經存在的窗口,窗口是使用事先已經拷貝到窗口實例數據區的類信息,並非直接使用進程裏保存的類的信息。子類化只更新了進程的窗口類列表裏的類的信息,而沒有更新窗口實例裏的類。
16位應用程序共享相同的進程空間。在win9x裏,16位程序的表現和它在win3.1裏是一樣的。
winNT則有很多的不同。winNT包括了兩個win32子系統:一個服務進程和一個在各win32進程裏運行的動態連接庫(DLL)。以edit類爲例,winNT在各進程空間裏,從DLL裏導出和註冊edit類。這樣,處理EDIT控件的代碼可以存在於DLL裏,也即在各進程空間裏。不需要系統分配局部過程調用來處理Edit控件,應用程序對控件的頻繁調用所導致的系統開銷也被避免了。因爲EDIT 控件實例僅僅在各進程空間裏操作自身數據,所以對系統魯棒性的衝擊就降低了。
服務進程管理每個win32應用程序的信息,包括應用程序的公有和私有窗口類。當創建一個win32線程的過程開始(即某個線程調用USER模塊或GDI模塊的函數時),USER模塊檢查該線程是否該進程的第一個線程,如果是(一般是主線程),USER模塊爲該進程註冊系統類。當爲了任何一個進程而註冊一個類(服務模塊進程除外),該類就會添加到該進程的公有或私有列表裏。爲了提高效率,windows爲每個進程都註冊系統類,並且把類信息的拷貝儲存在應用程序的空間裏。這增加了魯棒性,但是比起Windows95,增加了需要使用的內存。windowsNT也由此獲得了更高的性能,因爲當子類化一個系統類的時候,winNT不需要象window95那樣重新分配內存和拷貝類信息。
在winNT裏,16位的應用程序依然共享同一進程,也共享所有的系統全局類。16位程序總是不穩定因素的起源。
2.應用程序全局類。
應用程序全局類是註冊的時候指定了CS_GLOBALCLASS標誌的類(該標誌還有後續敘述)。
16位系統比如win3.1的應用程序“全局”類是真正意義的“全局”的,一個DLL或應用程序註冊的應用程序全局類,系統內所有的DLL和應用程序都可以使用。一個應用程序全局類在“全局”的意義上和系統全局類一致,只是它是由應用程序創建的而不是系統創建的而已。
Win32的應用系統全局類本質的不同是:應用程序全局類只是在進程內部的“全局”而已。這是什麼意思呢?一個DLL或.EXE可以註冊一個類,這個類可以讓在相同的進程空間裏其他.EXE和DLL使用。如果一個DLL註冊了一個非應用程序全局類的窗口類,那麼,只有該DLL可以使用該類,同樣的,.EXE裏註冊的非應用程序全局類也適用這個規則,即該類只在該.EXE裏有效。
作爲這個特性的擴展,win32有一項技術,允許一個第三方窗口控件在DLL裏實現,然後把這個DLL載入和初始化到每個Win32進程空間裏。這項技術的細節是,把DLL的名字寫入註冊表的指定鍵值裏:
HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/APPINIT_DLLS
這樣當任意一個win32應用程序加載的時候,系統也同時將該dll加載到進程空間裏(這可能有點過於奢侈,因爲很多win32程序不一定會使用該控件)。DLL在初始化的時候註冊應用程序全局類,這樣的窗口類就可以在每個進程空間的.EXE或DLL裏使用了。這個技術基於win32系統的這個特性:允許在每個進程空間裏自動的(也是強制的)加載特定的DLL(事實上,這也是打破進程邊界,把你的代碼切入到其他進程裏的一種辦法)。
3. 應用程序局部類
WIN32應用程序局部類是使用最頻繁的類(絕大部分的應用程序爲主窗口註冊的類都是應用程序局部類),僅僅在聲明和註冊該類的應用程序模塊或DLL自身裏使用。註冊一個應用程序局部類和應用程序全局類的區別是,局部類不包括CS_GLOBAL CLASS標誌。
二:窗口類包含的信息和作用
窗口類都包含些什麼信息呢?讓我們看以下窗口類結構體。
WNDCLASS結構包含的是一般的窗口類的信息
typedef struct tagWNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName;} WNDCLASS;
成員
|
描述
|
style
|
一組標誌位的組合。定義比如窗口位置,設備上下文(DC)分配,雙擊的處理等特徵。
|
lpfnWndProc
|
指向窗口過程的地址,該窗口過程負責處理窗口類相應的窗口消息
|
cbClsExtra
|
指明需要額外分配的內存數量,單位爲byte,系統爲該類分配保留指定數量的額外內存
|
cbWndExtra
|
指明需要額外分配的內存數量,單位爲byte,系統爲每個該類所對應的窗口實例分配保留指定數量的額外內存
|
hInstance
|
標識註冊該類的DLL或應用程序實例
|
hIcon
|
當一個屬於該類的窗口被最小化的時候,顯示的圖標.
|
hCursor
|
屬於鼠標該類的窗口裏顯示的指針
|
hbrBackground
|
定義當程序打開或重畫某個屬於該類的窗口是,填充窗口客戶區的顏色和樣式
|
lpszMenuName
|
如果沒有顯性定義菜單時,窗口的默認菜單
|
lpszClassName
|
字符串的類名
|
以下詳細討論各成員的具體意義:
Style成員決定了從該類創建出來的窗口的風格,可以使用下列值的一個或幾個的組合。
如果使用這兩個標誌,窗口的的客戶區或整個窗口都在“字節邊界”上對齊,也就是說,系統調整窗口的水平位置,客戶區或整個窗口的左邊座標是8的倍數。win32 SDk的文檔說這兩個標誌影響窗口的寬度,但是實際上筆者沒有發現這個現象,此標誌隻影響窗口的水平位置(左邊)。
看看系統如何擺放一個邊框寬度爲4的窗口:
Original window location
|
Placement with CS_BYTEALIGNWINDOW
|
Placement with CS_BYTEALIGNCLIENT
|
0,y
|
0,y
|
4,y
|
1,y
|
0,y
|
4,y
|
2,y
|
0,y
|
4,y
|
3,y
|
0,y
|
4,y
|
4,y
|
8,y
|
4,y
|
5,y
|
8,y
|
4,y
|
6,y
|
8,y
|
4,y
|
7,y
|
8,y
|
4,y
|
8,y
|
8,y
|
12,y
|
9,y
|
8,y
|
12,y
|
10,y
|
8,y
|
12,y
|
11,y
|
8,y
|
12,y
|
12,y
|
16,y
|
12,y
|
13,y
|
16,y
|
12,y
|
14,y
|
16,y
|
12,y
|
15,y
|
16,y
|
12,y
|
16,y
|
16,y
|
20,y
|
這兩個標誌在以下兩個情況中無效:
- 如果顯示設備對每個象素使用8個或更多的位數,字節對齊並不會帶來什麼好處。這種情況下,系統在創建和移動的時候忽略了CS_BYTEALIGNCLIENT和CS_BYTEALIGNWINDOW這兩個標誌。
- 如果使用SetWindowPos函數改變窗口的位置,此函數忽略窗口的CS_BYTEALIGNCLIENT和CS_BYTEALGNWINDOW標誌指定的位置限定。在Win32 SDK文檔關於WM_WINDOWPOSCHANGEING的描述讓人費解,它說:“對於一個有WS_OVERLAPPED和WS_THICKFRAME風格的窗口來說,DefWindowProc函數響應WM_WINDOWPOSCHANGING消息,並向窗口發送一個WM_GETMINMAXINFO消息,此消息的處理是驗證窗口的新位置和尺寸,迫使窗口接受CS_BYTEALIGNCLIENT和CS_BYTEALIGNWINDOW限定”
其實當DefWindowProc接受到WM_WINDOWPOSCHANGING消息後,確實發送一條WM_GETMINMAXINFO消息,但是並不迫使窗口接受字節對齊的風格,爲了確保字節對齊風格,應用程序必須通過計算再改變其水平位置
CS_BYTEALIGNCLIENT 和CS_BYTEALIGNWINDOW的存在是爲了優化程序的性能,尤其在是3.0版本的windows系統以前。那時侯,所有的系統字體寬度都是固定的,系統可以通過使窗口字節對齊優化字體的顯示。在3.0以後的window裏,這一點已經被忽略了。
如果程序使用BitBlt函數從一個窗口向另一個窗口,或者從窗口的某個矩形區域向另一個矩形區域拷貝象素,還是可以通過設置CS_BYTEALIGNCLIENT標誌來獲得更好的性能。如果客戶區是字節對齊的,程序也可以儘量保證BitBlt操作發生在字節對齊的矩形裏, BitBlt操作將會比發生於非字節對齊的矩形裏的操作更快。當然,字節對齊僅僅是對窗口的左邊界而言的,如果要進一步的提高性能,應該連寬度都進行字節對齊。
其實對於可以顯示256及以上顏色的視頻卡或顯示器,對字節對齊的需求已經微乎其微了。256色顯示器已經自己實現了字節對齊,實際上,一些16色的顯示器也是字節對齊的。在大部分顯示器上根本看不出來字節對齊限定對窗口位置的影響。一句話,字節對齊標誌已經沒有什麼重要的作用了。
使用CS_BYTEALIGNWINDOW的時候也等同於包含了CS_BYTEALIGNCLIENT;對話框類本身已經默認包含了CS_BYTEALIGNWINDOW標誌
這幾個標誌決定窗口的默認DC
- 如果使用CS_OWNDC標誌,屬於此窗口類的窗口實例都有自己的DC(稱爲私有DC),私有DC僅屬於該窗口實例,所以程序只需要調用一次GetDC或BeginPaint獲取DC,系統就爲窗口初始化一個DC,並且保存程序對其進行的改變。ReleaseDC和EndPaint函數不再需要了,因爲其他程序無法訪問和改變私有DC。當選擇了CS_OWNDC,程序改變影射模式(Mapping Mode)的時候必須小心,當由系統擦除窗口的背景時,系統假定和默認其影射模式是MM_TEXT。如果私有DC的影射模式不一樣,窗口被擦除的地方將不再可見。CS_OWNDC標誌在WinNT和Win9x的作用也是有差別的。在WinNT,win32子系統和其他NT進程有相同的地址空間(4GB),應用程序使用此地址空間裏的2GB,每個有CS_OWNDC標誌窗口實例佔用800個字節,在NT下,這裏面沒有什麼問題。而Win9x爲GDI保留了64K的局部堆,DC進入這個堆之後,即使其他GDI對象數據被釋放,DC依然在。這意味着每個CS_OWNDC的窗口都在這個寶貴的內存空間裏佔用800個字節。所以,爲win9x寫的程序最好儘量少的使用CS_OWNDC這個標誌。win9x的修正版打算解決這個問題,但效果不明顯(而且win9x已經開始式微了)如果使用CS_CLASSDC標誌,所有屬於該類的窗口實例共享相同的DC(稱爲類DC).類DC有一些私有DC的優點,而更加節約內存(因爲不需要爲每個窗口實例都分配800字節的DC空間了)。每個窗口實例都通過GetDC或BeginPaintde得到設備上下文(DC)句柄,如果沒有別的窗口需要該DC,不需要調用ReleaseDC或EndPaint釋放DC。在一個窗口實例上通過GetWindowDC,GetDC,GetDCEx,BeginPaint獲得 DC,並對其中的一些參數進行更改的話,所進行的更改除了剪切區域和設備本身屬性(Device origin)之外對所有其他窗口實例都是有效的。和CS_OWNDC相同的是,必須確保影射模式也是MM_TEXT,否則,被系統擦除的背景將不再可見。爲NT編寫的程序最好不要使用這個標誌,因爲“節約內存”的好處根本不明顯。對於win9x來說,卻是有用的,因爲對於win9x的64K的GDI局部堆來說,節約的空間意義更重大一些。
- 如果使用了CS_PARENTDC標誌,屬於這個類的窗口都使用它的父窗口的句柄。和CS_CLASSDC相似的是,多個窗口共享一個DC,不同的是,這多個窗口(雖然有父子關係並且共享DC)並不要求都屬於同一個窗口類。WIN9x下,所有的標準窗口控件都有CS_PARENTDC標誌。WinNT下,除了ComBoBox之外的窗口控件都有此標誌。因此,比如Edit控件和ListBox控件都共享他們的父窗口(比如對話框)的DC(注:這一段是我根據原文再加上自己的理解,不一定完全正確:) CS_PARENTDC帶來的好處就一個字:速度。Win9x系統爲每個線程預留了5個DC緩衝區,如果一個窗口(比如一個對話框)有多於5個的字窗口(比如有6個或以上的編輯框),而每個子窗口都有自己的DC的話,DC緩衝區就失去了效力,系統得爲每個子窗口根據剪切邊界和設備屬性重新初始化一個DC,這多於5個的DC在DC緩衝裏不停交換,不能確保在緩衝裏被訪問。而如果每個子窗口都和父窗口共享一個DC,在頻繁訪問該DC時,該DC在於DC 緩衝裏被找到的機率顯然相當的高,從而可以被高速的訪問,顯著的提高了速度,所以一般來說標準窗口控件都和父窗口共享DC。WinNT可以擁有多於5個的DC緩衝,所以它可能可以提供足夠的DC緩衝 -- 但是不保證時時如此。使用CS_PARENTDC的另一個效果是,子窗口可以在父窗口的客戶區隨意做畫,就象畫在自己的客戶區一樣. 負責表現Edit控件和ListBox控件周圍的3D效果的CTL3D庫就是利用了這個特性。注意如果程序需要改變各子窗口的影射模式,那麼最好不要用CS_PARENTDC標誌,否則將很容易引起各子窗口影射模式的混亂,因爲所有的子窗口都使用同一個DC。
- 如果不指定CS_OWNDC,CS_CLASSDC或CS_PARENTDC這幾個標誌,此類的窗口使用一個通用DC,並置於DC緩衝裏以供使用。通用DC在使用前獲取,使用後釋放,在DC獲取的時候,DC裏的上下文按默認值初始化,除非當時該DC已經在窗口的DC緩衝裏(比如沒有調用ReleaseDC或EndPaint釋放DC),這樣的話DC的剪切邊界和設備屬性都不需要被重新初試化,可以節約一些時間。在WinNT裏,DC緩衝沒有確定的數量。如果所有的DC緩衝都在使用中,而程序調用了GetDC和BeginPaint,NT則再分配一個緩衝。出於對win9x的兼容考慮,win32程序最好把 DC的使用限制在5個以下,並且儘可能快的釋放DC.如果要忽略類創建時由標誌位決定的窗口的默認DC,程序可以使用GetDCEx函數,指定DCX_CACHE標誌,則完全忽略CS_OWNDC和CS_CLASSDC標誌,並從緩衝裏返回一個通用DC.ScrollWindow和ScrollWindowEx函數處理DC的方法則有所不同:
- ScrollWindow使用窗口默認的DC。因此,如果窗口使用CS_OWNDC或CS_CLASSDC標誌,ScrollWindow使用相應的DC(窗口私有DC或類DC,其影射模式有可能被程序改變,不爲MM_TEXT).傳遞給ScrollWindow的座標值必須和DC的影射模式相一致。
- ScrollWindowEx使用系統通用DC(這種DC使用MM_TEXT的影射模式),而忽略窗口的標誌。傳遞給ScrollWindowEx的座標值必須是MM_TEXT影射模式下的客戶區座標值。
CS_DBLCLKS標誌使窗口可以檢測到雙擊事件。窗口響應雙擊的細節如下:
- 如果窗口沒有CS_DBLCLKS標誌,系統向窗口依次發送如下消息:
WM_LBUTTONDOWN
- WM_LBUTTONUP
- WM_LBUTTONDOWN
- WM_LBUTTONUP.
其實相當於兩個單擊。
- 如果窗口有CS_DBLCLKS標誌,則系統向窗口依次發送如下消息:
WM_LBUTTONDOWN
- WM_LBUTTONUP
- WM_LBUTTONDBLCLK
- WM_LBUTTONUP.
第一種情況中的第二個WM_LBUTTONDOWN被WM_LBUTTONDBLCLK代替了。
注意,在上述序列中間可能會插入其他的一條或一些消息,所以這兩個消息序列不一定是完全連續的。
其實,在沒有指定CS_DBLCLKS標誌時,程序本身也可以檢測到雙擊事件的。參見MSDN裏Dr.GUT的"Simulating Mouse Button Clicks"文章,不過要有一些技巧.一般的情況下,如果沒有指定CS_DBLCLKS,在窗口的消息循環裏將不會得到WM_LBUTTONDBLCLK消息。
所有的標準窗口控件,對話框,桌面窗口類都默認擁有CS_DBLCLKS標誌。第三方控件最好也加上此風格,以使其在對話框編輯器裏可以正常工作。
CS_GLOBALCLASS是唯一一個針對類本身起作用而不是對單個窗口起作用的標誌.系統將包含這種標誌的窗口類作爲應用程序全局類保存,這樣類可以應用於註冊該類的進程內的所有EXE和DLL,當EXE或DLL退出或卸載,或對該類調用UnregisterClass後,該類則被銷燬。在註冊該窗口的程序退出前,所有從該類創建的窗口都必須先關閉(對於DLL來說,這是自動進行的)。
CS_HREDRAW標誌表示當窗口的水平尺寸(寬度)改變的時候,重畫整個窗口。CS_VREDRAW則是在窗口垂直尺寸(高度)改變時重畫整個窗口。按鈕和滾動條都有這兩種風格。
如果指定了CS_NOCLOSE標誌,則窗口上的關閉按鈕和系統菜單上的關閉命令失效。
菜單,對話框,下拉框都擁有CS_SAVEBITS標誌。當窗口使用這個標誌時,系統用位圖形式保存一份被窗口遮蓋(或灰隱)的屏幕圖象。首先,系統要求顯示驅動保存圖象位數據,如果顯示驅動本身的存儲空間足夠,保存操作成功,window系統也可以使用這些保存的位數據。如果不夠,系統將位數據在全局內存裏以位圖的方式保存,並且在USE模塊局部堆裏爲每個窗口分配空間,保存一些事務數據(比如位圖數據緩衝的大小)的結構。當程序使遮蓋屏幕的窗口消失時,系統可以很快的從內存裏恢復屏幕圖象。
CS_SAVEBITS的效率本身是很難度量的。CS_SAVEBITS提高了“臨時”窗口比如菜單,對話框,下拉框的性能。但是,存貯位信息的開銷也是很明顯的,尤其由系統代替顯示驅動存儲位信息的時候,系統承擔了速度和存儲開銷。使用CS_SAVEBITS的好處其實依賴於窗口遮蓋的區域發生了什麼事情,如果該區域相當複雜,需要重畫很多的效果,那麼,存儲該區域可能比重畫該區域要來的輕鬆,如果反之,該區域可以相當快速的重畫,或者在被遮蓋的時候還經常發生變化並且變化很顯著,保存的方案反而影響了整體性能。
以上都是style成員的可選標誌。
WNDCLASS結構裏的lpfnWndProc成員保存了該窗口類的窗口過程地址。該窗口類的所有窗口都使用該過程地址,對於從該類創建的窗口,系統將所有相關的消息交給此窗口過程來處理。窗口過程實現窗口的功能,程序可以使用SetClassLong函數來改變窗口類的窗口過程。這個操作叫“子類化”(Subclassing)。當程序改變了該過程的地址,在改變前已經創建的窗口還是使用原來的地址,而以後創建的窗口才使用新的過程地址。
當一個程序或DLL子類化一個窗口或設置窗口過程函數,必須在模塊定義文件裏輸出該新窗口過程。
- Extra Class and Window Bytes (cbClsExtra and cbWndExtra)
cbClsExtra成員指明爲每個窗口類多分配的額外數據量,cbWndExtra成員則是指明爲每個窗口實例分配的額外數據量。如果程序不需要分配額外的數據,這兩個成員的值都應該設置爲 0,以免產生不確定的數(比如一個非常大的數)而使系統進行錯誤分配,如果是負數,則該窗口類將不會被註冊。
在Win9x和NT,分配40個及以下字節是沒有多大意義的,當然,開發人員可以根據需要分配額外數據大小。
如果用類聲明並註冊一個對話框類型的窗口,cbWndExtra的值必須設置爲DLGWINDOWEXTRA,系統對話框管理器需要這麼多的額外數據對對話框進行管理。
- Instance Handle (hInstance)
WNDCLASS的hInstance成員標識類所在的模塊。此成員可以爲進程的hInstance,或DLL的hInstance,但不可以爲NULL.
WNDCLASS的hIcon成員標識此窗口類的圖標。程序一般使用LoadIcon,從系統標準圖標庫(如IDI_APPLICATION)或用戶指定的圖標資源中來獲取一個圖標句柄。如果hIcon的值爲NULL,當系統給程序發送WM_ICONERASEBKGND消息的時候,程序給窗口畫上程序的主圖標.
WNDCLASS裏的hCursor成員表示屬於該類窗口的默認鼠標指針。當設置了該值,當鼠標移入窗口區域時,系統將指針由系統默認形狀變成所設置的指針形狀。程序可以使用LoadCursor函數從標準系統指針庫(比如IDC_ARROW)或用戶指定指針資源中獲取指針句柄。程序可以通過SetCursor函數隨時改變指針。如果hCursor的值未設置(設置爲NULL),程序必須在鼠標指針移入窗口時進行設置,否則將使用系統默認的鼠標指針形狀。
- Class Background Brush (hbrBackground)
WNDCLASS裏的hbrBackground成員變量表示背景顏色的,類型爲HBRUSH,即GDI刷子句柄。可以對其賦值爲一個刷子句柄(比如用GetStockObject獲取系統內置刷子對象句柄,或者自己創建指定顏色和風格的刷子的句柄)或者顏色值。如果是選擇顏色值的話,必須使用下列系統標準顏色之一。
COLOR_ACTIVEBORDER
|
COLOR_HIGHLIGHTTEXT
|
COLOR_ACTIVECAPTION
|
COLOR_INACTIVEBORDER
|
COLOR_APPWORKSPACE
|
COLOR_INACTIVECAPTION
|
COLOR_BACKGROUND
|
COLOR_INACTIVECAPTIONTEXT
|
COLOR_BTNFACE
|
COLOR_MENU
|
COLOR_BTNSHADOW
|
COLOR_MENUTEXT
|
COLOR_BTNTEXT
|
COLOR_SCROLLBAR
|
COLOR_CAPTIONTEXT
|
COLOR_WINDOW
|
COLOR_GRAYTEXT
|
COLOR_WINDOWFRAME
|
COLOR_HIGHLIGHT
|
COLOR_WINDOWTEXT
|
用顏色值賦值時,必須強制類型轉換爲HBRUSH類型:
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
如果hbrBackground成員設置爲NULL,程序必須在響應WM_PAINT的時候負責畫背景。程序也可以響應WM_ERASEBKGND消息,或根據調用BeginPaint函數時填充的PAINTSTRUCT結構裏的成員fErase的值類判斷是否需要重畫背景。
- Class Menu (lpszMenuName)
WNDCLASS裏的lpszMenuName成員表示默認主菜單。可以使用MAKEINTRESOURCE宏把資源裏菜單項的ID號轉變成連續的字符串值賦給該成員。如果使用CreateWindow或CreateWindowEx函數從該類創建窗口時沒有在函數參數裏指定別的菜單資源,那麼出現在各窗口上的主菜單就是lpszMenuName指定的菜單。如果lpszMenuName爲NULL,窗口則沒有默認的主菜單。
- Class Name (lpszClassName)
WNDCLASS裏的lpszClassName結構表示類的名字。局部窗口類名在進程範圍必須爲唯一的,由於僅要求“在進程內唯一”,對於兩個不同的程序來說,其中的窗口類名有可能相同,比如,兩個程序都可能擁有各自的"Main Wnd"窗口。全局窗口類的名字必須在全局窗口類和系統窗類口範圍裏唯一,比如,程序可以註冊一個名字爲"Edit"的局部窗口類,但是無法註冊同樣名字的一個全局窗口類。
三 系統如何定位窗口類
當程序需要根據一個類創建一個窗口,系統通過以下步驟來定位該類
1.系統在本進程的局部窗口類列表裏尋找有相同名字的類。如果找到,由於局部窗口類是屬於某一個.EXE模塊或DLL的,所以所找到的類的進程實例句柄(hInstance)和本模塊的實例句柄應該保持一致。這樣的規則是爲了防止進程內一個模塊或DLL裏註冊的局部類被進程其他及其的模塊或DLL發現。
2.如果系統無法發現一個同名的局部類,那麼,就繼續搜索進程的全局類列表,這次的搜索不比較實例句柄。
在win9x,如果在程序全局類空間裏無法發現同名類,則繼續查詢進程內的系統窗口類列表。
3. 如果系統還是無法發現同名窗口類,則繼續搜索全局系統類列表。
winNT將此過程應用於所有由程序創建的窗口,包括從主窗口再創建出的其他窗口,比如對話框和消息框。
win9x 同樣將此過程應用於所有的窗口,除了消息框以外。當程序彈出一個消息框,Win9x不對局部類列表進行搜索,而是直接進行後續步驟。
四 應用程序的如何使用窗口類信息。
一旦註冊了一個類,一般來說除了使用該類創建窗口之外就沒有什麼需要作的事情了。當然,如果需要訪問該類信息,子類化,或者超類化該類,介紹一些方法就是有用的。
如果需要獲取和改變類的信息,可以使用以下函數:
- GetClassLong, 從類信息讀回來一個Long類型的值(比如,窗口類的窗口過程地址)
- SetClassLong 對該類的某成員寫入一個long類型的值。比如,寫入一個新的窗口過程的地址。
- GetClassWord 從類信息讀回來一個word類型的值。比如以下調用得到類的額外數據量:
nClassExtra = GetClassWord(hwnd,GCW_CBCLSEXTRA);
SetClassWord 向類信息寫入一個word類型的值。比如改變窗口類圖標。
- GetClassName 獲取窗口類的名字。
- GetClassInfo 獲取除類名和菜單以外的全部類信息。
具體的調用參數以及其他相關的一些類函數可以參見MSDN裏windows classs Functions 一節。
術語“子類化”(subclassing)描述的是用一個新的窗口過程代替原窗口過程。術語“實例子類化”(即子類化單個窗口)是指使用SetWindowLong函數改變某一個窗口實例的窗口過程。“全局子類化”(子類化整個窗口類)則是指使用SetClassLong改變整個類的默認窗口過程函數。
在32位windows 系統裏,可能難於子類化另一個進程裏的窗口或窗口類,一般來說,子類化都是發生於同一個進程裏的(“打破進程邊界”的相關的主題本文沒有涉及)。
術語"超類化"(Superclassing)指創建一個新的類,該類使用某個現存類的窗口過程,繼承該類的基本功能,並可以在此基礎上進行擴展。
關於子類化和超類化的具體描述,請參閱MSDN裏"safe subclasing in Win32"一文
結束語: win32的窗口類給開發人員帶來了很多有用的信息,在win32平臺上開發程序,最好是注意使用兼容winNT和win9x的一些特性以保證程序的兼容性。