關於視窗類別
每個視窗類別都有一個相關聯的視窗程式,由相同類別的所有視窗共用。 視窗程式會處理該類別之所有視窗的訊息,因此會控制其行為和外觀。 如需詳細資訊,請參閱 Window 程序。
進程必須先註冊視窗類別,才能建立該類別的視窗。 註冊視窗類別會將視窗程式、類別樣式和其他類別屬性與類別名稱產生關聯。 當進程在 CreateWindow 或 CreateWindowEx 函式中指定類別名稱時,系統會使用與該類別名稱相關聯的視窗程式、樣式和其他屬性來建立視窗。
本節討論下列主題。
視窗類別的類型
視窗類別有三種類型:
這些類型在註冊和終結的範圍和時機和方式不同。
系統類別
系統類別是由系統註冊的視窗類別。 許多系統類別都可供所有進程使用,而其他類別則僅供系統內部使用。 因為系統會註冊這些類別,所以進程無法終結它們。
系統在第一次呼叫 User 或 Windows Graphics Device Interface (GDI) 函式時,註冊進程的系統類別。
每個應用程式都會接收自己的系統類別複本。 相同 VDM 共用系統類別中的所有 16 位 Windows 型應用程式,就像在 16 位 Windows 上所做的一樣。
下表描述可供所有進程使用的系統類別。
類別 | 描述 |
---|---|
按鈕 | 按鈕的 類別。 |
ComboBox | 下拉式方塊的 類別。 |
編輯 | 編輯控制項的 類別。 |
ListBox | 清單方塊的 類別。 |
MDIClient | MDI 用戶端視窗的 類別。 |
ScrollBar | 捲軸的 類別。 |
靜態 | 靜態控制項的 類別。 |
下表描述僅供系統使用的系統類別。 為了完整起見,這裡會列出它們。
類別 | 描述 |
---|---|
ComboLBox | 下拉式方塊中包含的清單方塊類別。 |
DDEMLEvent | 動態資料交換管理程式庫的 類別 (DDEML) 事件。 |
訊息 | 僅限訊息視窗的 類別。 |
#32768 | 功能表的 類別。 |
#32769 | 桌面視窗的 類別。 |
#32770 | 對話方塊的 類別。 |
#32771 | 工作切換視窗的 類別。 |
#32772 | 圖示標題的類別。 |
應用程式全域類別
應用程式全域類別是由可執行檔或 DLL 所註冊的視窗類別,可供進程中的所有其他模組使用。 例如,您的.dll可以呼叫 RegisterClassEx 函式來註冊將自訂控制項定義為應用程式全域類別的視窗類別,讓載入.dll的程式可以建立自訂控制項的實例。
若要建立可用於每個進程的類別,請在.dll中建立視窗類別,並在每個進程中載入.dll。 若要在每個進程中載入.dll,請在下列登錄機碼中將其名稱新增至 AppInit_DLLs 值:
\ HKEY_LOCAL_MACHINE軟體\微軟\\ Windows NT CurrentVersion\Windows
每當進程啟動時,系統會在新啟動的進程內容中載入指定的.dll,然後再呼叫其進入點函式。 .dll必須在初始化程式期間註冊 類別,而且必須指定 CS_GLOBALCLASS 樣式。 如需詳細資訊,請參閱 類別樣式。
若要移除應用程式全域類別並釋放與其相關聯的儲存體,請使用 UnregisterClass 函式。
應用程式本機類別
應用程式本機類別是可執行檔或.dll註冊其獨佔用途的任何視窗類別。 雖然您可以註冊任意數目的本機類別,但通常只註冊一個。 這個視窗類別支援應用程式主視窗的視窗程式。
當註冊它的模組關閉時,系統會終結本機類別。 應用程式也可以使用 UnregisterClass 函式來移除本機類別,並釋放與其相關聯的儲存體。
系統如何找出視窗類別
系統會針對這三種視窗類別的每個類型維護結構清單。 當應用程式呼叫 CreateWindow 或 CreateWindowEx 函式來建立具有指定類別的視窗時,系統會使用下列程式來找出 類別。
- 使用實例控制碼符合模組實例控制碼的指定名稱,搜尋應用程式本機類別的清單。 (數個模組可以使用相同的名稱,在相同的進程中註冊本機類別。)
- 如果名稱不在應用程式本機類別清單中,請搜尋應用程式全域類別的清單。
- 如果名稱不在應用程式全域類別清單中,請搜尋系統類別的清單。
應用程式建立的所有視窗都會使用此程式,包括由系統代表應用程式建立的視窗,例如對話方塊。 可以覆寫系統類別,而不會影響其他應用程式。 也就是說,應用程式可以註冊名稱與系統類別相同的應用程式本機類別。 這會取代應用程式內容中的系統類別,但不會防止其他應用程式使用系統類別。
註冊視窗類別
視窗類別會定義視窗的屬性,例如其樣式、圖示、游標、功能表和視窗程式。 註冊視窗類別的第一個步驟是使用視窗類別資訊填入 WNDCLASSEX 結構。 如需詳細資訊,請參閱 Window 類別的元素。 接下來,將 結構傳遞至 RegisterClassEx 函式。 如需詳細資訊,請參閱 使用視窗類別。
若要註冊應用程式全域類別,請在WNDCLASSEX結構的樣式成員中指定CS_GLOBALCLASS樣式。 註冊應用程式本機類別時,請勿指定 CS_GLOBALCLASS 樣式。
如果您使用RegisterClassEx、RegisterClassExA的 ANSI 版本註冊視窗類別,應用程式會要求系統使用 ANSI 字元集將訊息的文字參數傳遞至所建立類別的視窗;如果您使用Unicode 版本的 RegisterClassEx、RegisterClassExW註冊類別,應用程式會要求系統使用 Unicode 字元集將訊息的文字參數傳遞至所建立類別的視窗。 IsWindowUnicode函式可讓應用程式查詢每個視窗的本質。 如需 ANSI 和 Unicode 函式的詳細資訊,請參閱 函式原型的慣例。
註冊類別的可執行檔或 DLL 是 類別的擁有者。 系統會從註冊類別時傳遞至RegisterClassEx函式之WNDCLASSEX結構的hInstance成員判斷類別擁有權。 針對 DLL, hInstance 成員必須是.dll實例的控制碼。
卸載擁有類別的.dll時,不會終結類別。 因此,如果系統針對該類別的視窗呼叫視窗程式,就會造成存取違規,因為包含視窗程式的.dll不再位於記憶體中。 在卸載.dll並呼叫 UnregisterClass 函式之前,進程必須使用 類別終結所有視窗。
Window 類別的專案
視窗類別的元素會定義屬於 類別之視窗的預設行為。 註冊視窗類別的應用程式會藉由在 WNDCLASSEX 結構中設定適當的成員,並將結構傳遞至 RegisterClassEx 函式,將元素指派給 類別。 GetClassInfoEx和GetClassLong函式會擷取指定視窗類別的相關資訊。 SetClassLong函式會變更應用程式已註冊之本機或全域類別的專案。
雖然完整的視窗類別包含許多元素,但系統只需要應用程式提供類別名稱、視窗過程位址和實例控制碼。 使用其他元素來定義 類別視窗的預設屬性,例如游標的形狀,以及視窗功能表的內容。 您必須將 WNDCLASSEX 結構的任何未使用成員初始化為零或 Null。 視窗類別專案如下表所示。
元素 | 目的 |
---|---|
類別名稱 | 區分 類別與其他已註冊的類別。 |
視窗程式位址 | 函式的指標,該函式會處理傳送至 類別中視窗的所有訊息,並定義視窗的行為。 |
[執行個體控制代碼] | 識別註冊 類別的應用程式或.dll。 |
類別資料指標 | 定義系統針對 類別視窗顯示的滑鼠游標。 |
類別圖示 | 定義大型圖示和小型圖示。 |
類別背景筆刷 | 定義開啟或繪製視窗時填滿工作區的色彩和圖樣。 |
類別功能表 | 指定未明確定義功能表之視窗的預設功能表。 |
[類別樣式] | 定義如何在移動或調整視窗大小之後更新視窗、如何處理滑鼠按兩下、如何配置裝置內容的空間,以及視窗的其他層面。 |
額外的類別記憶體 | 指定系統應該為 類別保留的額外記憶體數量,以位元組為單位。 類別中的所有視窗都會共用額外的記憶體,並可用於任何應用程式定義的用途。 系統會將此記憶體初始化為零。 |
額外的視窗記憶體 | 指定系統應該針對屬於 類別的每個視窗保留的額外記憶體數量,以位元組為單位。 額外的記憶體可用於任何應用程式定義的用途。 系統會將此記憶體初始化為零。 |
類別名稱
每個視窗類別都需要 類別名稱 ,才能區分一個類別與另一個類別。 將WNDCLASSEX結構的lpszClassName成員設定為指定名稱之 Null 終止字串的位址,以指派類別名稱。 因為視窗類別是特定進程,所以視窗類別名稱只能在相同的進程中是唯一的。 此外,因為類別名稱佔用系統私用 Atom 資料表中的空間,所以您應該盡可能保留類別名稱字串。
GetClassName函式會擷取指定視窗所屬類別的名稱。
視窗程式位址
每個類別都需要視窗程式位址,以定義用來處理類別中視窗之所有訊息之視窗程式的進入點。 當系統需要視窗執行工作時,系統會將訊息傳遞至程式,例如繪製其工作區或回應使用者的輸入。 進程會將視窗程式複製到類別,方法是將其位址複製到WNDCLASSEX結構的lpfnWndProc成員。 如需詳細資訊,請參閱 Window 程序。
[執行個體控制代碼]
每個視窗類別都需要實例控制碼來識別註冊類別的應用程式或.dll。 系統需要實例控制碼,才能追蹤所有模組。 系統會將控制碼指派給執行中可執行檔或.dll的每個複本。
系統會將實例控制碼傳遞至每個可執行檔的進入點函式, (請參閱 WinMain) ,並.dll (請參閱 DllMain) 。 可執行檔或.dll將這個實例控制碼複製到WNDCLASSEX結構的hInstance成員,以指派給 類別。
類別資料指標
類別資料指標定義資料指標在類別中視窗的工作區時,游標的形狀。 當游標進入視窗的工作區時,系統會自動將游標設定為指定的圖形,並確保該圖形保留在工作區中。 若要將游標圖形指派給視窗類別,請使用LoadCursor函式載入預先定義的游標圖形,然後將傳回的資料指標控制碼指派給WNDCLASSEX結構的hCursor成員。 或者,提供自訂資料指標資源,並使用 LoadCursor 函式從應用程式的資源載入它。
系統不需要類別資料指標。 如果應用程式將WNDCLASSEX結構的hCursor成員設定為Null,則不會定義類別資料指標。 系統假設視窗會在每次游標移至視窗時設定游標圖形。 每當視窗收到WM_MOUSEMOVE訊息時,視窗就可以呼叫SetCursor函式來設定游標圖形。 如需資料指標的詳細資訊,請參閱 資料指標。
類別圖示
類別圖示是系統用來代表特定類別視窗的圖片。 應用程式可以有兩個類別圖示,一個大型和一個小圖示。 系統會在使用者按下 ALT+TAB 時,在工作切換視窗中顯示視窗 的大型類別圖示 ,並在工作列和總管的大型圖示檢視中顯示。 小型類別圖示會出現在視窗的標題列中,以及工作欄和總管的小型圖示檢視中。
若要將大型和小型圖示指派給視窗類別,請在WNDCLASSEX結構的hIcon 和hIconSm成員中指定圖示的控制碼。 圖示維度必須符合大型和小型類別圖示的必要維度。 對於大型類別圖示,您可以在GetSystemMetrics函式呼叫中指定SM_CXICON和SM_CYICON值,以判斷所需的維度。 針對小型類別圖示,指定 SM_CXSMICON 和 SM_CYSMICON 值。 如需詳細資訊,請參閱 圖示。
如果應用程式將WNDCLASSEX結構的hIcon 和 hIconSm成員設定為Null,則系統會使用預設應用程式圖示作為視窗類別的大型和小型類別圖示。 如果您指定大型類別圖示,但不是小型圖示,系統會根據大型圖示建立小型類別圖示。 不過,如果您指定小型類別圖示,但不是大型圖示,則系統會使用預設應用程式圖示做為大型類別圖示,並將指定的圖示當做小類別圖示。
您可以使用 WM_SETICON 訊息覆寫特定視窗的大型或小型類別圖示。 您可以使用 WM_GETICON 訊息來擷取目前的大型或小型類別圖示。
類別背景筆刷
類別背景筆刷會準備視窗的工作區,以供應用程式後續繪製。 系統會使用筆刷以純色或圖樣填滿工作區,藉此移除該位置的所有先前影像,不論它們是否屬於視窗。 系統會通知視窗,其背景應該藉由將 WM_ERASEBKGND 訊息傳送至視窗來繪製。 如需詳細資訊,請參閱 筆刷。
若要將背景筆刷指派給類別,請使用適當的 GDI 函數建立筆刷,並將傳回的筆刷控制碼指派給WNDCLASSEX結構的hbrBackground成員。
應用程式可以將 hbrBackground 成員設定為其中一個標準系統色彩值,而不是建立筆刷。 如需標準系統色彩值的清單,請參閱 SetSysColors。
若要使用標準系統色彩,應用程式必須將背景色彩值增加一個。 例如, COLOR_BACKGROUND + 1 是系統背景色彩。 或者,您可以使用GetSysColorBrush函式來擷取對應至標準系統色彩之筆刷的控制碼,然後在WNDCLASSEX結構的hbrBackground成員中指定控制碼。
系統不需要視窗類別具有類別背景筆刷。 如果此參數設定為 Null,視窗必須在收到 WM_ERASEBKGND 訊息時繪製自己的背景。
類別功能表
如果建立視窗時沒有明確功能表, 則類別 中的視窗會定義預設功能表, 功能表是一份命令清單,使用者可以從中選擇要執行應用程式的動作。
您可以將WNDCLASSEX結構的lpszMenuName成員設定為指定功能表資源名稱的 Null 終止字串位址,將功能表指派給類別。 功能表假設為指定應用程式中的資源。 系統會在需要時自動載入功能表。 如果功能表資源是以整數而非名稱識別,則應用程式可以在指派值之前套用MAKEINTRESOURCE宏,將lpszMenuName成員設定為該整數。
系統不需要類別功能表。 如果應用程式將WNDCLASSEX結構的lpszMenuName成員設定為Null,則 類別中的視窗沒有功能表列。 即使沒有提供類別功能表,應用程式仍可在建立視窗時定義視窗的功能表列。
如果為類別提供功能表,而且已建立該類別的子視窗,則會忽略功能表。 如需詳細資訊,請參閱 功能表。
[類別樣式]
類別樣式會定義視窗類別的其他專案。 您可以使用位 OR (來結合兩個或多個樣式 |) 運算子。 若要將樣式指派給視窗類別,請將樣式指派給WNDCLASSEX結構的樣式成員。 如需類別樣式的清單,請參閱 視窗類別樣式。
類別和裝置內容
裝置內容是一組特殊的值,應用程式會用來在其視窗的工作區中繪製。 系統需要顯示器上每個視窗的裝置內容,但允許系統儲存和處理該裝置內容的方式有一些彈性。
如果未明確指定任何裝置內容樣式,系統會假設每個視窗都會使用從系統維護的內容集區擷取的裝置內容。 在這種情況下,每個視窗都必須在繪製之前擷取並初始化裝置內容,並在繪製後釋出它。
為了避免在每次需要繪製視窗內時擷取裝置內容,應用程式可以指定視窗類別 的CS_OWNDC 樣式。 此類別樣式會指示系統建立私人裝置內容,也就是為類別中的每個視窗配置唯一的裝置內容。 應用程式只需要擷取內容一次,然後將其用於所有後續繪製。
額外的類別記憶體
系統會在內部維護系統中每個視窗類別的 WNDCLASSEX 結構。 當應用程式註冊視窗類別時,它可以指示系統組態並附加一些額外的記憶體位元組至 WNDCLASSEX 結構的結尾。 此記憶體稱為 額外的類別記憶體 ,且由屬於 類別的所有視窗共用。 使用額外的類別記憶體來儲存與 類別相關的任何資訊。
由於會從系統的本機堆積配置額外的記憶體,因此應用程式應該謹慎使用額外的類別記憶體。 如果要求的額外類別記憶體數量大於 40 個位元組, RegisterClassEx 函式就會失敗。 如果應用程式需要超過 40 個位元組,它應該配置自己的記憶體,並將記憶體的指標儲存在額外的類別記憶體中。
SetClassWord和SetClassLong函式會將值複製到額外的類別記憶體。 若要從額外的類別記憶體擷取值,請使用 GetClassWord 和 GetClassLong 函式。 WNDCLASSEX結構的cbClsExtra成員會指定要配置的額外類別記憶體數量。 不使用額外類別記憶體的應用程式必須將 cbClsExtra 成員初始化為零。
額外的視窗記憶體
系統會維護每個視窗的內部資料結構。 註冊視窗類別時,應用程式可以指定一些額外的記憶體位元組,稱為 額外的視窗記憶體。 建立 類別的視窗時,系統會配置並附加指定數量的額外視窗記憶體至視窗結構的結尾。 應用程式可以使用此記憶體來儲存視窗特定資料。
由於會從系統的本機堆積配置額外的記憶體,因此應用程式應該謹慎使用額外的視窗記憶體。 如果要求的額外視窗記憶體數量大於 40 個位元組, RegisterClassEx 函式就會失敗。 如果應用程式需要超過 40 個位元組,它應該配置自己的記憶體,並將記憶體的指標儲存在額外的視窗記憶體中。
SetWindowLong函式會將值複製到額外的記憶體。 GetWindowLong函式會從額外的記憶體中擷取值。 WNDCLASSEX結構的cbWndExtra成員會指定要配置的額外視窗記憶體數量。 不使用記憶體的應用程式必須將 cbWndExtra 初始化為零。