日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

眼見為實(shí)(2):介紹Windows的窗口、消息、子類化

系統(tǒng) 2225 0

眼見為實(shí)(2):介紹Windows的窗口、消息、子類化和超類化

這篇文章本來只是想介紹一下子類化和超類化這兩個比較“生僻”的名詞。為了敘述的完整性而討論了Windows的窗口和消息,也簡要討論了進(jìn)程和線程。子類化(Subclassing)和超類化(Superclassing)是伴隨Windows窗口機(jī)制而產(chǎn)生的兩個復(fù)用代碼的方法。不要把“子類化、超類化”與面向?qū)ο笳Z言中的派生類、基類混淆起來。“子類化、超類化”中的“類”是指Windows的窗口類。

0 運(yùn)行程序

希望讀者在閱讀本節(jié)前先看看 "談?wù)刉indows程序中的字符編碼" 開頭的第0節(jié)和附錄0。第0節(jié)介紹了Windows系統(tǒng)的幾個重要模塊。附錄0概述了Windows的啟動過程,從上電到啟動Explorer.exe。本節(jié)介紹的是運(yùn)行程序時發(fā)生的事情。

0.1 程序的啟動

當(dāng)我們通過Explorer.exe運(yùn)行一個程序時,Explorer.exe會調(diào)用CreateProcess函數(shù)請求系統(tǒng)為這個程序創(chuàng)建進(jìn)程。當(dāng)然,其它程序也可以調(diào)用CreateProcess函數(shù)創(chuàng)建進(jìn)程。

系統(tǒng)在為進(jìn)程分配內(nèi)部資源,建立獨(dú)立的地址空間后,會為進(jìn)程創(chuàng)建一個主線程。我們可以把進(jìn)程看作單位,把線程看作員工。進(jìn)程擁有資源,但真正在CPU上運(yùn)行和調(diào)度的是線程。系統(tǒng)以掛起狀態(tài)創(chuàng)建主線程,即主線程創(chuàng)建好,不會立即運(yùn)行,而是等待系統(tǒng)調(diào)度。系統(tǒng)向Win32子系統(tǒng)的管理員csrss.exe登記新創(chuàng)建的進(jìn)程和線程。登記結(jié)束后,系統(tǒng)通知掛起的主線程可以運(yùn)行,新程序才開始運(yùn)行。

這時,在創(chuàng)建進(jìn)程中CreateProcess函數(shù)返回;在被創(chuàng)建進(jìn)程中,主線程在完成最后的初始化后進(jìn)入程序的入口函數(shù)(Entry-point)。創(chuàng)建進(jìn)程與被創(chuàng)建進(jìn)程在各自的地址空間獨(dú)立運(yùn)行。這時,即使我們結(jié)束創(chuàng)建進(jìn)程,也不會影響被創(chuàng)建進(jìn)程。

0.2 程序的執(zhí)行

可執(zhí)行文件(PE文件)的文件頭結(jié)構(gòu)包含入口函數(shù)的地址。入口函數(shù)一般是Windows在運(yùn)行時庫中提供的,我們在編譯時可以根據(jù)程序類型設(shè)定。 在VC中編譯、運(yùn)行程序的小知識點(diǎn) 討論了Entry-point,讀者可以參考。

入口函數(shù)前的過程可以被看作程序的裝載過程。在裝載時,系統(tǒng)已經(jīng)做過全局和靜態(tài)變量(在編譯時可以確定地址)的初始化,有初值的全局變量擁有了它們的初值,沒有初值的變量被設(shè)為0,我們可以在入口函數(shù)處設(shè)置斷點(diǎn)確認(rèn)這一點(diǎn)。

進(jìn)入入口函數(shù)后,程序繼續(xù)運(yùn)行環(huán)境的建立,例如調(diào)用所有全局對象的構(gòu)造函數(shù)。在一切就緒后,程序調(diào)用我們提供的主函數(shù)。主函數(shù)名是入口函數(shù)決定的,例如main或WinMain。如果我們沒有提供入口函數(shù)要求的主函數(shù),編譯時就會產(chǎn)生鏈接錯誤。

0.3 進(jìn)程和線程

我們通常把存儲介質(zhì)(例如硬盤)上的可執(zhí)行文件稱作程序。程序被裝載、運(yùn)行后就成為進(jìn)程。系統(tǒng)會為每個進(jìn)程創(chuàng)建一個主線程,主線程通過入口函數(shù)進(jìn)入我們提供的主函數(shù)。我們可以在程序中創(chuàng)建其它線程。

線程可以創(chuàng)建一個或多個窗口,也可以不創(chuàng)建窗口。系統(tǒng)會為有窗口的線程建立消息隊列。有消息隊列的線程就可以接收消息,例如我們可以用PostThreadMessage函數(shù)向線程發(fā)送消息。

沒有窗口的線程只要調(diào)用了PeekMessage或GetMessage,系統(tǒng)也會為它創(chuàng)建消息隊列。

1 窗口和消息

1.1 線程的消息隊列

每個運(yùn)行的程序就是一個進(jìn)程。每個進(jìn)程有一個或多個線程。有的線程沒有窗口,有的線程有一個或多個窗口。

我們可以向線程發(fā)送消息,但大多數(shù)消息都是發(fā)給窗口的。發(fā)給窗口的消息同樣放在線程的消息隊列中。我們可以把線程的消息隊列看作信箱,把窗口看作收信人。我們在向指定窗口發(fā)送消息時,系統(tǒng)會找到該窗口所屬的線程,然后把消息放到該線程的消息隊列中。

線程消息隊列是系統(tǒng)內(nèi)部的數(shù)據(jù)結(jié)構(gòu),我們在程序中看不到這個結(jié)構(gòu)。但我們可以通過Windows的API向消息隊列發(fā)送、投遞消息;從消息隊列接收消息;轉(zhuǎn)換和分派接收到的消息。

1.2 最小的Windows程序

Windows的程序員大概都看過這么一個 最小的Windows程序

    // 例程1
  
    #include "windows.h"
  
    static const char m_szName[] = "窗口";
  
    //////////////////////////////////////////////////////////////////////////////////////////////////// 
  
    // 主窗口回調(diào)函數(shù) 如果直接用 DefWindowProc, 關(guān)閉窗口時不會結(jié)束消息循環(huán)
  
    static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  
    { 
  
    	switch (uMsg) {
  
    	case WM_DESTROY:
  
    		PostQuitMessage(0);	// 關(guān)閉窗口時發(fā)送WM_QUIT消息結(jié)束消息循環(huán)
  
    		break;
  
    	default:
  
    		return DefWindowProc(hWnd, uMsg, wParam, lParam);
  
    	} 
  
    	return 0;
  
    }
  
    //////////////////////////////////////////////////////////////////////////////////////////////////// 
  
    // 主函數(shù) 
  
    int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
  
    {
  
    	WNDCLASS wc; 
  
    	memset(&wc, 0, sizeof(WNDCLASS));
  
    	wc.style = CS_VREDRAW|CS_HREDRAW;
  
    	wc.lpfnWndProc = (WNDPROC)WindowProc;
  
    	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  
    	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
  
    	wc.lpszClassName = m_szName;
  
    	RegisterClass(&wc);		// 登記窗口類
  
  
    	HWND hWnd;
  
    	hWnd = CreateWindow(m_szName,m_szName,WS_OVERLAPPEDWINDOW,100,100,320,240,
  
    		NULL,NULL,hInstance,NULL);	// 創(chuàng)建窗口
  
    	ShowWindow(hWnd, nCmdShow);		// 顯示窗口
  
  
    	MSG sMsg; 
  
    	while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循環(huán)
  
    		if (ret != -1) {
  
    			TranslateMessage(&sMsg);
  
    			DispatchMessage(&sMsg);
  
    		}
  
    	}
  
    	return 0;
  
    }
  

這個程序雖然只顯示一個窗口,但經(jīng)常被用來說明Windows程序的基本結(jié)構(gòu)。在MFC框架內(nèi)部我們同樣可以找到類似的程序結(jié)構(gòu)。這個程序包含以下基本概念:

  • 窗口類、窗口和窗口過程
  • 消息循環(huán)

下面分別介紹。

1.3 窗口類、窗口和窗口過程

創(chuàng)建窗口時要提供窗口類的名字。窗口類相當(dāng)于窗口的模板,我們可以基于同一個窗口類創(chuàng)建多個窗口。我們可以使用Windows預(yù)先登記好的窗口類。但在更多的情況下,我們要登記自己的窗口類。在登記窗口類時,我們要登記名稱、風(fēng)格、圖標(biāo)、光標(biāo)、菜單等項,其中最重要的就是窗口過程的地址。

窗口過程是一個函數(shù)。窗口收到的所有消息都會被送到這個函數(shù)處理。那么,發(fā)到線程消息隊列的消息是怎么被送到窗口的呢?

1.4 消息循環(huán)

熟悉嵌入式多任務(wù)程序的程序員,都知道任務(wù)(相當(dāng)于Windows的線程)的結(jié)構(gòu)基本上都是:

    	while (1) {
  
    		等待信號;
  
    		處理信號;
  
    	}
  

任務(wù)收到信號就處理,否則就掛起,讓其它任務(wù)運(yùn)行。這就是消息驅(qū)動程序的基本結(jié)構(gòu)。Windows程序通常也是這樣:

    	while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循環(huán)
  
    		if (ret != -1) {
  
    			TranslateMessage(&sMsg);
  
    			DispatchMessage(&sMsg);
  
    		}
  
    	}
  

GetMessage從消息隊列接收消息;TranslateMessage根據(jù)按鍵產(chǎn)生WM_CHAR消息,放入消息隊列;DispatchMessage根據(jù)消息中的窗口句柄將消息分發(fā)到窗口,即調(diào)用窗口過程函數(shù)處理消息。

1.5 通過消息通信

創(chuàng)建窗口的函數(shù)會返回一個窗口句柄。窗口句柄在系統(tǒng)范圍內(nèi)(不是進(jìn)程范圍)標(biāo)識一個唯一的窗口實(shí)例。通過向窗口發(fā)送消息,我們可以實(shí)現(xiàn)進(jìn)程內(nèi)和進(jìn)程間的通信。

我們可以用SendMessage或PostMessage向窗口發(fā)送或投遞消息。SendMessage必須等到目標(biāo)窗口處理過消息才會返回。我試過:如果向一個沒有消息循環(huán)的窗口SendMessage,SendMessage函數(shù)永遠(yuǎn)不會返回。PostMessage在把消息放入線程的消息隊列后立即返回。

其實(shí)只有投遞的消息才是通過DispatchMessage分派到窗口過程的。通過SendMessage發(fā)送的消息,在線程GetMessage時,就已經(jīng)被分派到窗口過程了,不經(jīng)過DispatchMessage。

1.5.1 窗口程序與控制臺程序的通信實(shí)例

大家是不是覺得“例程1”沒什么意思,讓我們用它來做個小游戲:讓“例程1”和一個控制臺程序做一次親密接觸。我們首先將“例程1”的窗口過程修改為:

    static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  
    {
  
    	static DWORD tid = 0;
  
    	switch (uMsg) {
  
    	case WM_DESTROY:
  
    		PostQuitMessage(0);	// 關(guān)閉窗口時發(fā)送WM_QUIT消息結(jié)束消息循環(huán)
  
    		break;
  
    	case WM_USER:
  
    		tid = wParam;	// 保存控制臺程序的線程ID
  
    		SetWindowText(hWnd, "收到");
  
    		break;
  
    	case WM_CHAR:
  
    		if (tid) {
  
    			switch(wParam) {
  
    			case '1':
  
    				PostThreadMessage(tid, WM_USER+1, 0, 0);	// 向控制臺程序發(fā)送消息1
  
    				break;
  
    			case '2':
  
    				PostThreadMessage(tid, WM_USER+2, 0, 0);	// 向控制臺程序發(fā)送消息2
  
    				break;
  
    			}
		}
  
    		break;
  
    	default:
  
    		return DefWindowProc(hWnd, uMsg, wParam, lParam);
  
    	} 
  
    	return 0;
  
    }
  

然后,我們創(chuàng)建一個 控制臺程序 ,代碼如下:

    #include "windows.h"
  
    #include "stdio.h"
  
    static HWND m_hWnd = 0;
  
    void process_msg(UINT msg, WPARAM wp, LPARAM lp)
  
    {
  
    	char buf[100];
  
    	static int i = 1;
  
    	if (!m_hWnd) {
  
    		return;
  
    	}
  
    	switch (msg) {
  
    	case WM_USER+1:
  
    		SendMessage(m_hWnd, WM_GETTEXT, sizeof(buf), (LPARAM)buf);
  
    		printf("你現(xiàn)在叫:%s\n\n", buf);		// 讀取、顯示對方的名字
  
    		break;
  
  
    	case WM_USER+2:
  
    		sprintf(buf, "我是窗口%d", i++);
  
    		SendMessage(m_hWnd, WM_SETTEXT, sizeof(buf), (LPARAM)buf);	// 修改對方名字
  
    		printf("給你改名\n\n");
  
    		break;
  
    	}
  
    }
  
  
    int main()
  
    {
  
    	MSG sMsg;
  
    	printf("Start with thread id %d\n", GetCurrentThreadId());
  
    	m_hWnd = FindWindow(NULL,"窗口");
  
    	if (m_hWnd) {
  
    		printf("找到窗口%x\n\n", m_hWnd);
  
    		SendMessage(m_hWnd, WM_USER, GetCurrentThreadId(), 0);
  
    	}
  
    	else {
  
    		printf("沒有找到窗口\n\n");
  
    	}
  
  
    	while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循環(huán)
  
    		if (ret != -1) {
  
    			process_msg(sMsg.message, sMsg.wParam, sMsg.lParam);
  
    		}
  
    	}
  
    	return 0;
  
    }

  

大家能看懂這游戲怎么玩嗎?首先運(yùn)行“例程1”wnd,然后運(yùn)行控制臺程序msg。msg會找到wnd的窗口,并將自己的主線程ID發(fā)給wnd。wnd收到msg的消息后,會顯示收到。這時,wnd和msg已經(jīng)建立了通信的渠道:wnd可以向msg的主線程發(fā)消息,msg可以向wnd的窗口發(fā)消息。

我們?nèi)绻趙nd窗口按下鍵'1',wnd會向msg發(fā)送消息1,msg收到后會通過WM_GETTEXT消息獲得wnd的窗口名稱并顯示。我們?nèi)绻趙nd窗口按下鍵'2',wnd會向msg發(fā)送消息2,msg收到后會通過WM_SETTEXT消息修改wnd的窗口名稱。

眼見為實(shí)(2):介紹Windows的窗口、消息、子類化和超類化

這個小例子演示了控制臺程序的消息循環(huán),向線程發(fā)消息,以及進(jìn)程間的消息通信。

1.5.2 地址空間的問題

不同的進(jìn)程擁有獨(dú)立的地址空間,如果我們在消息參數(shù)中包含一個進(jìn)程A的地址,然后發(fā)送到進(jìn)程B。進(jìn)程B如果在自己的地址空間里操作這個地址,就會發(fā)生錯誤。那么,為什么上例中的WM_GETTEXT和WM_SETEXT可以正常工作?

這是因?yàn)閃M_GETTEXT和WM_SETEXT都是Windows自己定義的消息,Windows知道參數(shù)的含義,并作了特殊的處理,即在進(jìn)程B的空間分配一塊內(nèi)存作為中轉(zhuǎn),并在進(jìn)程A和進(jìn)程B的緩沖區(qū)之間復(fù)制數(shù)據(jù)。例如:在1.5.1節(jié)的例子中,如果我們設(shè)置斷點(diǎn)觀察,就會發(fā)現(xiàn)msg發(fā)送的WM_SETTEXT消息中的lParam不等于wnd接收到的WM_SETTEXT消息中的lParam。

如果我們在自己定義的消息中傳遞內(nèi)存地址,系統(tǒng)不會做任何特殊處理,所以必然發(fā)生錯誤。

Windows提供了WM_COPYDATA消息用來向窗口傳遞數(shù)據(jù),Windows同樣會為這個消息作特殊處理。

在進(jìn)程間發(fā)送這些需要額外分配內(nèi)存的消息時,我們應(yīng)該用SendMessage,而不是PostMessage。因?yàn)镾endMessage會等待接收方處理完后再返回,這樣系統(tǒng)才有機(jī)會額外釋放分配的內(nèi)存。在這種場合使用PostMessage,系統(tǒng)會忽略要求投遞的消息,讀者可以在msg程序中試驗(yàn)一下。

2 子類化和超類化

窗口類是窗口的模板,窗口是窗口類的實(shí)例。窗口類和每個窗口實(shí)例都有自己的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。Windows雖然沒有公開這些數(shù)據(jù)結(jié)構(gòu),但提供了讀寫這些數(shù)據(jù)的API。

例如:用GetClassLong和SetClassLong函數(shù)可以讀寫窗口類的數(shù)據(jù);用GetWindowLong和SetWindowLong可以讀寫指定窗口實(shí)例的數(shù)據(jù)。使用這些接口,可以在運(yùn)行時讀取或修改窗口類或窗口實(shí)例的窗口過程地址。這些接口是子類化的實(shí)現(xiàn)基礎(chǔ)。

2.1 子類化

子類化的目的是在不修改現(xiàn)有代碼的前提下,擴(kuò)展現(xiàn)有窗口的功能。它的思路很簡單,就是將窗口過程地址修改為一個新函數(shù)地址,新的窗口過程函數(shù)處理自己感興趣的消息,將其它消息傳遞給原窗口過程。通過子類化,我們不需要現(xiàn)有窗口的源代碼,就可以定制窗口功能。

子類化可以分為實(shí)例子類化和全局子類化。實(shí)例子類化就是修改窗口實(shí)例的窗口過程地址,全局子類化就是修改窗口類的窗口過程地址。實(shí)例子類化只影響被修改的窗口。全局子類化會影響在修改之后,按照該窗口類創(chuàng)建的所有窗口。顯然,全局子類化不會影響修改前已經(jīng)創(chuàng)建的窗口。

子類化方法雖然是二十年前的概念,卻很好地實(shí)踐了面向?qū)ο蠹夹g(shù)的開閉原則(OCP:The Open-Closed Principle):對擴(kuò)展開放,對修改關(guān)閉。

2.2 超類化

超類化的概念更簡單,就是讀取現(xiàn)有窗口類的數(shù)據(jù),保存窗口過程函數(shù)地址。對窗口類數(shù)據(jù)作必要的修改,設(shè)置新窗口過程,再換一個名稱后登記一個新窗口類。新窗口類的窗口過程函數(shù)還是僅處理自己感興趣的消息,而將其它消息傳遞給原窗口過程函數(shù)處理。使用GetClassInfo函數(shù)可以讀取現(xiàn)有窗口類的數(shù)據(jù)。

3 MFC中的消息循環(huán)和子類化

MFC將子類化方法應(yīng)用得淋漓盡致,是一個不錯的例子。候捷先生的《深入淺出MFC》已經(jīng)將MFC的主要框架分析得很透徹了,本節(jié)只是看看MFC的消息循環(huán),簡單分析MFC對子類化的應(yīng)用。

3.1 消息循環(huán)

隨便建立一個 MFC單文檔程序 ,在視圖類中添加WM_RBUTTONDOWN的處理函數(shù),并在該處理函數(shù)中設(shè)置斷點(diǎn)。運(yùn)行,斷下后,查看調(diào)用堆棧:

    CHelloView::OnRButtonDown(unsigned int, CPoint)
  
    CWnd::OnWndMsg(unsigned int, unsigned int, long, long *)
  
    CWnd::WindowProc(unsigned int, unsigned int, long)
  
    AfxCallWndProc(CWnd *, HWND__ *, unsigned int, unsigned int, long)
  
    AfxWndProc(HWND__ *, unsigned int, unsigned int, long)
  
    AfxWndProcBase(HWND__ *, unsigned int, unsigned int, long)
  
    USER32! 7e418734()
  
    USER32! 7e418816()
  
    USER32! 7e4189cd()
  
    USER32! 7e4196c7()
  
    CWinThread::PumpMessage()
  
    CWinThread::Run()
  
    CWinApp::Run()
  
    AfxWinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
  
    WinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
  
    WinMainCRTStartup()
  
    KERNEL32! 7c816fd7()
  

WinMainCRTStartup是這個程序的入口函數(shù)。候捷先生已經(jīng)詳細(xì)介紹過AfxWinMain。我們就看看CWinThread::PumpMessage中的消息循環(huán):

    BOOL CWinThread::PumpMessage()
  
    {
  
    	if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) {
  
    		return FALSE;
  
    	}
  
    	if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
  
    	{
  
    		::TranslateMessage(&m_msgCur);
  
    		::DispatchMessage(&m_msgCur);
  
    	}
  
    	return TRUE;
  
    }
  

這就是MFC程序主線程中的消息循環(huán),它把發(fā)送到線程消息隊列的消息分派到線程的窗口。

3.2 子類化

CWnd::CreateEx在創(chuàng)建窗口前調(diào)用SetWindowsHookEx函數(shù)安裝了一個鉤子函數(shù)_AfxCbtFilterHook。窗口剛創(chuàng)建好,鉤子函數(shù)_AfxCbtFilterHook就被調(diào)用。_AfxCbtFilterHook調(diào)用SetWindowLong將窗口過程替換為AfxWndProcBase,并將SetWindowLong返回的原窗口地址保存到成員變量oldWndProc。上節(jié)調(diào)用堆棧中的AfxWndProcBase就是由此而來。

可見,通過CWnd::CreateEx創(chuàng)建的所有窗口都會被子類化,即它們的窗口過程都會被替換為AfxWndProcBase。MFC為什么要這樣做?

讓我們再看看調(diào)用堆棧中的CWnd::WindowProc函數(shù):

    LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
  
    {
  
    	LRESULT lResult = 0;
  
    	if (!OnWndMsg(message, wParam, lParam, &lResult))
  
    		lResult = DefWindowProc(message, wParam, lParam);
  
    	return lResult;
  
    }
  

按照侯捷先生的介紹,CWnd::OnWndMsg就是“MFC消息泵”的入口,消息通過這個入口流入MFC消息映射中的消息處理函數(shù)。消息泵只會處理我們定制過的消息,我們沒有添加過處理的消息會原封不動地流過"消息泵",進(jìn)入DefWindowProc函數(shù)。在DefWindowProc函數(shù)中,消息會傳給子類化時保存的原窗口地址oldWndProc。

CWnd::CreateEx里的鉤子會子類化所有窗口嗎?其實(shí)不盡然。的確,MFC所有窗口相關(guān)的類都是從CWnd派生的,這些類的實(shí)例在創(chuàng)建窗口時都會調(diào)用CWnd::CreateEx,都會被子類化。但是,通過對話框模板創(chuàng)建的窗口是通過CreateDlgIndirect創(chuàng)建的,不經(jīng)過CWnd::CreateEx函數(shù)。

但這點(diǎn)其實(shí)也不是問題,因?yàn)槿绻覀兿胪ㄟ^MFC定制一個控件的消息映射,就必須先子類化這個控件,MFC還是有機(jī)會將窗口過程替換成自己的AfxWndProcBase。下一節(jié)將介紹對話框控件的子類化。

4 子類化和超類化的例子

我寫了一個很簡單的對話框程序,用來演示 子類化和超類化 。這個對話框程序有兩個編輯框,我將編輯框的右鍵菜單換成了一個消息框。兩個編輯框的定制分別采用了子類化和超類化技術(shù):

眼見為實(shí)(2):介紹Windows的窗口、消息、子類化和超類化

4.1 子類化的例子

首先從CEdit派生出CMyEdit1,定制WM_RBUTTONDOWN的處理。很多文章都建議我們在對話框的OnInitDialog中用SubclassDlgItem實(shí)現(xiàn)子類化:

    	m_edit1.SubclassDlgItem(IDC_EDIT1, this);
  

這樣做當(dāng)然可以。其實(shí)如果我們已經(jīng)為IDC_EDIT1添加過CMyEdit1對象:

    void CSubclassingDlg::DoDataExchange(CDataExchange* pDX)
  
    {
  
    CDialog::DoDataExchange(pDX);
  
    //{{AFX_DATA_MAP(CSubclassingDlg)
  
    DDX_Control(pDX, IDC_EDIT1, m_edit1);
  
    //}}AFX_DATA_MAP
  
    }
  

DDX_Control會自動幫我們完成子類化,沒有必要手工調(diào)用SubclassDlgItem。大家可以通過在PreSubclassWindow中設(shè)置斷點(diǎn)看看。

通過DDX_Control或者SubclassDlgItem子類化控件的效果是一樣的,MFC都是把窗口過程替換成AfxWndProcBase。用戶添加過處理函數(shù)的消息通過MFC消息泵流入用戶的處理函數(shù)。

4.2 必經(jīng)之路:PreSubclassWindow

PreSubclassWindow是一個很好的定制控件的位置。如果我們通過重載CWnd::PreCreateWindow定制控件,而用戶在對話框中使用控件。由于對話框中的控件窗口是通過CreateDlgIndirect創(chuàng)建,不經(jīng)過CWnd::CreateEx函數(shù),PreCreateWindow函數(shù)不會被調(diào)用。

其實(shí),用戶要在對話框中使用定制控件,必須用DDX或者SubclassDlgItem函數(shù)子類化控件,這時PreSubclassWindow一定會被調(diào)用。

如果用戶直接創(chuàng)建定制控件窗口,CWnd::CreateEx函數(shù)就一定會被調(diào)用,控件窗口一定會被子類化以安裝MFC消息泵。所以在MFC中,PreSubclassWindow是創(chuàng)建窗口的必經(jīng)之路。

4.3 超類化的例子

我很少看到超類化的例子(除了羅云彬的Win32匯編),在大多數(shù)應(yīng)用中,子類化技術(shù)已經(jīng)足夠了。但我還是寫了一個例子:CMyEdit2從CEdit派生。CMyEdit2::RegisterMe獲取窗口類Edit的信息,保存原窗口過程,設(shè)置新窗口過程MyWndProc和新名稱MyEdit,登記一個新窗口類。新窗口過程MyWndProc定制自己需要處理的消息,將其它消息送回原窗口過程。

我在對話框的OnInitDialog中先調(diào)用CMyEdit2::RegisterMe登記新窗口類,然后創(chuàng)建窗口。這樣創(chuàng)建窗口必須經(jīng)過CWnd::CreateEx,所以MFC還是會把窗口過程換成AfxWndProcBase。沒有被MFC消息映射攔截的消息才會流入MyWndProc。

5 結(jié)束語

這篇文章介紹了一些Windows和MFC的基礎(chǔ)知識。寫這篇文章的目的不是介紹什么編程技巧,而是讓我們更了解程序運(yùn)行時發(fā)生的事情。惟有深入其中,方能跳出其外,不受羈絆。

眼見為實(shí)(2):介紹Windows的窗口、消息、子類化和超類化


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 姚安县| 安新县| 衡东县| 崇州市| 白城市| 镇安县| 砚山县| 忻城县| 抚州市| 嵊泗县| 灵石县| 海淀区| 迭部县| 邵武市| 柳林县| 如东县| 竹溪县| 壶关县| 开原市| 安丘市| 漯河市| 北碚区| 丰宁| 青冈县| 汤原县| 拜泉县| 莲花县| 阳西县| 奉新县| 社旗县| 大悟县| 舞阳县| 西乌珠穆沁旗| 河东区| 盐池县| 雷山县| 廉江市| 马鞍山市| 靖宇县| 凤凰县| 齐河县|