關於視窗程式
每個視窗都是特定視窗類別的成員。 window 類別會決定個別視窗用來處理其訊息的預設視窗程式。 屬於相同類別的所有視窗都會使用相同的預設視窗程式。 例如,系統會定義下拉式方塊類別的視窗程式(COMBOBOX):所有下拉式方塊都會使用該視窗程式。
應用程式通常會註冊至少一個新的窗口類別及其相關聯的視窗程式。 註冊類別之後,應用程式可以建立該類別的許多視窗,這些視窗全都使用相同的視窗程式。 由於這表示數個來源可以同時呼叫相同的程式代碼段,所以從視窗程式修改共用資源時,您必須小心。 如需詳細資訊,請參閱 窗口類別。
對話框的視窗程式(稱為對話框程式)具有類似的結構和函式做為一般視窗程式。 本節中參照視窗程式的所有點也會套用至對話框程式。 如需詳細資訊,請參閱 對話方塊。
本節討論下列主題。
視窗程序的結構
視窗程式是具有四個參數並傳回帶正負號值的函式。 參數包含視窗句柄、UINT 訊息識別碼,以及使用WPARAM和 LPARAM 資料類型宣告的兩個訊息參數。 如需詳細資訊,請參閱 WindowProc。
訊息參數通常包含低序和高順序字組的資訊。 應用程式可以使用數個宏,從訊息參數擷取資訊。 例如,LOWORD 宏會從訊息參數擷取低序字(位0到15)。 其他宏包括HIWORD、LOBYTE和HIBYTE宏。
傳回值的解譯取決於特定訊息。 請參閱每個訊息的描述,以判斷適當的傳回值。
因為可以遞歸呼叫視窗程式,所以請務必將它所使用的局部變數數目降到最低。 處理個別訊息時,應用程式應該在視窗程式外部呼叫函式,以避免過度使用局部變數,可能會導致堆疊在深層遞歸期間溢位。
默認視窗程式
DefWindowProc 預設視窗程式函式會定義所有視窗共用的特定基本行為。 默認視窗程式提供視窗的最低功能。 應用程式定義的視窗程序應該將它未處理的任何訊息傳遞至 DefWindowProc 函式,以進行默認處理。
視窗程序子類別化
當應用程式建立視窗時,系統會配置記憶體區塊來儲存視窗特定的資訊,包括處理視窗訊息的視窗程式位址。 當系統需要將訊息傳遞至視窗時,它會搜尋視窗特定資訊以取得視窗程式的位址,並將訊息傳遞至該程式。
子類別 化是一種技術,可讓應用程式在窗口有機會處理訊息之前,攔截並處理傳送或張貼至特定視窗的訊息。 透過子類別化視窗,應用程式可以增強、修改或監視窗口的行為。 應用程式可以將屬於系統全域類別的視窗子類別化,例如編輯控件或清單框。 例如,應用程式可以將編輯控件子類別化,以防止控件接受特定字元。 不過,您無法將屬於另一個應用程式的視窗或類別子類別子類別子類別。 所有子類別化都必須在相同的進程中執行。
應用程式會將視窗的原始視窗程式的位址取代為新視窗程式的位址,稱為 子類別程式,藉此將視窗子類別化。 之後,子類別程式會接收傳送或張貼至視窗的任何訊息。
子類別程式可以在接收訊息時採取三個動作:它可以將訊息傳遞至原始視窗程式、修改訊息,並將它傳遞給原始視窗程式,或處理訊息,而不傳遞至原始視窗程式。 如果子類別程式處理訊息,它可以在訊息傳遞至原始視窗程式之前、之後或兩者執行。
系統提供兩種類型的子類別: 實例 和 全域。 在 實例子類別化中,應用程式會取代視窗單一實例的窗口過程位址。 應用程式必須使用實例子類別化來子類別化現有的視窗。 在 全域子類別中,應用程式會取代窗口類別之 WNDCLASSEX 結構中的視窗程式位址。 使用 類別建立的所有後續視窗都有子類別程序的位址,但 類別的現有視窗不會受到影響。
實例子類別化
應用程式會使用 SetWindowLongPtr 函式來子類別化視窗的實例。 應用程式會將 GWL_WNDPROC 旗標、視窗的句柄傳遞至子類別,並將子類別程式的位址傳遞至 SetWindowLongPtr。 子類別程式可以位於應用程式的可執行檔或 DLL 中。
當傳遞GWL_WNDPROC旗標時,SetWindowLongPtr 會傳回視窗原始視窗程序的位址。 應用程式必須在呼叫 CallWindowProc 函式的後續呼叫中儲存此位址,以將攔截的訊息傳遞至原始視窗程式。 應用程式也必須有原始視窗程式位址,才能從視窗中移除子類別。 若要移除子類別,應用程式會再次呼叫 SetWindowLongPtr ,並使用 GWL_WNDPROC 旗標和視窗句柄傳遞原始視窗程序的位址。
系統擁有系統全域類別,而且控件的各個層面可能會從一個版本的系統變更為下一個版本。 如果應用程式必須子類別屬於系統全域類別的窗口,開發人員可能需要在發行新版本的系統時更新應用程式。
因為實例子類別化會在建立窗口之後發生,所以您無法將任何額外的位元組新增至視窗。 子類別化視窗的應用程式應該使用視窗的屬性清單來儲存子類別化視窗實例所需的任何數據。 如需詳細資訊,請參閱 視窗屬性。
當應用程式子類別化子類別化視窗時,它必須以執行子類別的反向順序移除子類別。 如果未反轉移除順序,可能會發生無法復原的系統錯誤。
全域子類別
若要全域子類別視窗類別,應用程式必須具有 類別視窗的句柄。 應用程式也需要句柄來移除子類別。 為了取得句柄,應用程式通常會建立要子類別化的類別隱藏視窗。 取得句柄之後,應用程式會呼叫 SetClassLongPtr 函式、指定句柄、GCL_WNDPROC旗標,以及子類別程序的位址。 SetClassLongPtr 會傳回 類別的原始視窗程式位址。
原始視窗程式位址會以實例子類別化中所使用的相同方式在全域子類別化中使用。 子類別程式會呼叫 CallWindowProc,將訊息傳遞至原始視窗程式。 應用程式會再次呼叫 SetClassLongPtr,指定原始視窗程式的位址、GCL_WNDPROC旗標,以及子類別之視窗的句柄,以從視窗類別移除子類別。 全域子類別化控件類別的應用程式,必須在應用程式終止時移除子類別;否則,可能會發生無法復原的系統錯誤。
全域子類別設定與實例子類別化具有相同的限制,加上一些額外的限制。 應用程式不應該針對類別或窗口實例使用額外的位元組,而不知道原始視窗程式如何使用它們。 如果應用程式必須將數據與視窗產生關聯,它應該使用視窗屬性。
視窗程式超類別化
超類別化 是一種技術,可讓應用程式建立具有現有類別基本功能的新窗口類別,以及應用程式所提供的增強功能。 超級類別是以稱為 基類的現有窗口類別為基礎。 基類通常是系統全域窗口類別,例如編輯控件,但可以是任何窗口類別。
超級類別有自己的視窗程式,稱為superclass程式。 超級 類別程式 可以在接收訊息時採取三個動作:它可以將訊息傳遞至原始視窗程式、修改訊息,並將其傳遞至原始視窗程式,或處理訊息,而不傳遞至原始視窗程式。 如果超類別程式處理訊息,它可以在訊息傳遞至原始視窗程式之前、之後或兩者之前或之後執行。
不同於子類別程式,超級類別程式可以處理視窗建立訊息(WM_NCCREATE、WM_CREATE等等),但它也必須將它們傳遞至原始基類視窗程式,讓基類視窗程式可以執行其初始化程式。
若要將窗口類別超類別化,應用程式會先呼叫 GetClassInfoEx 函式來擷取基類的相關信息。 GetClassInfoEx 會以基類的 WNDCLASSEX 結構值填滿 WNDCLASSEX 結構。 接下來,應用程式會將自己的實例句柄複製到 WNDCLASSEX 結構的 hInstance 成員中,並將超類別的名稱複製到 lpszClassName 成員中。 如果基類有功能表,應用程式必須提供具有相同功能表標識元的新功能表,並將功能表名稱 複製到 lpszMenuName 成員。 如果超級類別程式會處理 WM_COMMAND 訊息,而且不會將它傳遞給基類的視窗程式,功能表就不需要有對應的標識碼。 GetClassInfoEx 不會傳回 WNDCLASSEX 結構的 lpszMenuName、lpszClassName 或 hInstance 成員。
應用程式也必須設定 WNDCLASSEX 結構的 lpfnWndProc 成員。 GetClassInfoEx 函式會以 類別原始視窗程式的位址填滿此成員。 應用程式必須儲存此位址,才能將訊息傳遞至原始視窗程式,然後將超類別程式的 位址複製到 lpfnWndProc 成員。 如有必要,應用程式可以修改 WNDCLASSEX 結構的任何其他成員。 在填滿 WNDCLASSEX 結構之後,應用程式會將 結構的位址傳遞至 RegisterClassEx 函式,以註冊超類別。 然後,可以使用超級類別來建立視窗。
因為超類別化會註冊新的視窗類別,因此應用程式可以同時新增至額外的類別位元組和額外的視窗位元組。 因為實例子類別或全域子類別不應該使用這些位元組的相同原因,超類別不得使用基類或視窗的原始額外位元組。 此外,如果應用程式將額外的位元組用於類別或窗口實例,則必須參考相對於原始基類所使用的額外位元元組數目的額外位元組數。 由於基類所使用的位元組數目可能會從基類的某個版本變更為下一個版本,因此超類別本身額外位元組的起始位移也可能因基類的某個版本而異。