?
摘一段以前的文章,原文作者:茍建兵 清華大學熱能系(北京,100084)。原文很長,這里只摘了一段。
采用MFC開發(fā)Windows程序之所以能夠大幅度提高開發(fā)速度和效率主要是因為MFC在類
層次封裝了大量Windows SDK函數和典型Windows應用的缺省處理,這樣,用戶只需
要較少的編程就可以實現(xiàn)自己的開發(fā)任務。如果在MFC基礎上再配合Visual C++提供
的AppWizard、ClassWizard和AppStudio工具那么更可以大幅度加快開發(fā)進程。MFC
提供大量的基類供程序員使用,常見的如CWinApp類、CFrameWnd類、CMDIFrameWnd
類、CMDIChildWnd類、CView類、CDC類和CDocument類等等。通過從這些基類中派生
出用戶自己的類,然后重載特殊的幾個函數就可以生成一個獨立的應用程序。可以
說,采用MFC編寫Windows應用程序是非常方便的,雖然其學習過程并不簡單,但是
其提供的靈活高效性足以使任何Windows程序開發(fā)人員為之付出努力。如果用戶不曾
使用過MFC,那么用戶可以通過附錄中所列的參考書去學習MFC的強大功能。
采用MFC應用框架產生的應用程序使用了標準化的結構,因而使得采用MFC編寫的程
序的在不同平臺上的移植變得非常容易,事實上,MFC的16位和32位版本之間差別很
小。MFC提供的標準化結構是經過眾多專家分析調研后總結編寫出來的,一般情況下
可以滿足絕大多數用戶的要求,但有時用戶也可以通過重載一些函數來修改其缺省
的風格從而實現(xiàn)自己特有的風格,如自定義應用圖表和灰色背景等。在MFC提供的文
檔視結構中,文檔、視和資源之間的聯(lián)系是通過定義文檔模板來實現(xiàn)的,如:
m_pSimuTemplate = new CMultiDocTemplate(
IDR_SIMUTYPE,
RUNTIME_CLASS(CSimuDoc),
RUNTIME_CLASS(CMyChild), // Derived MDI child frame
RUNTIME_CLASS(CSimuView));
上中第一項IDR_SIMUTYPE就包括了視口的菜單,加速鍵和圖表等資源,如果用戶使
用AppWizard來產生的應用基本框架,那么其也同時產生了缺省的圖標,如果用戶不
滿意缺省圖標(實際上用戶很少滿足于缺省圖標),只需要將缺省圖標刪除,然后
編輯或者直接引入一個新的圖標,在存儲這一圖標時只需要使用與被刪除圖標同樣
的ID即可實現(xiàn)替代。
熟悉Windows程序開發(fā)的人都知道,在Windows上通過使用灰色背景可以增強應用程
序的視覺效果,曾有人戲稱,灰色是圖形界面永恒的顏色。使用MFC產生的應用程序
的背景缺省為白色,如果用戶希望改變成灰色或者其它顏色,那就需要使用單獨處
理,解決的辦法很多,如在每次視口的OnPaint()事件中采用灰色刷子人為填充背
景,但是這不是最好的辦法。筆者發(fā)現(xiàn)最好的辦法就是采用AfxRegisterWndClass()
函數注冊一個使用灰色背景刷的新的窗口類,這需要重載PreCreateWindow()函數來
實現(xiàn)這一點,如下程序代碼片段所示:
BOOL CSimuView::PreCreateWindow(CREATESTRUCT& cs)
{
HBRUSH hbkbrush=CreateSolidBrush(RGB(192,192,192));//創(chuàng)建灰色背景刷
LPCSTR lpMyOwnClass=AfxRegisterWndClass(CS_HREDRAW
|CS_VREDRAW|CS_OWNDC,0,hbkbrush);//注冊新類
cs.lpszClass=lpMyOwnClass;//修改缺省的類風格
return TRUE;
}
采用這種方法速度最快,也最省力。同時,還可以在PreCreateWindow()函數定義所
希望的任何窗口風格,如窗口大小,光標式樣等。
使用單文檔-多視結構
如果用戶使用過MFC進行編程,那么就會發(fā)現(xiàn)借助于AppWizard基于MFC無論編寫SDI
(單文檔界面)還是編寫MDI(多文檔界面)都是十分方便的。MDI應用程序目前使用越
來越普遍,人們熟悉的Microsoft公司的Office系列產品以及Visual系列產品都是典
型的多文檔應用程序。這種多文檔界面具有多窗口的特點,因而人們可以在一個程
序中使用多個子窗口來實現(xiàn)不同數據的瀏覽查看。如果用戶要實現(xiàn)在MDI各個窗口之
間針對同一數據進行不同的可視化就是一件比較麻煩的事情。值得慶幸的是,MFC提
供的文檔-視結構大大簡化了這一工作。文檔-視結構通過將數據從用戶對數據的觀
察中分離出來,從而方便實現(xiàn)多視,亦即多個視口針對同一數據,如果一個視口中
數據發(fā)生改變,那么其它相關視口中的內容也會隨之發(fā)生改變以反映數據的變化。
SDI和MDI這兩種Windows標準應用程序框架并不是總能滿足用戶的需要,就作者的工
作而言,就特別需要一種被稱為單文檔多視的應用程序,英文可以縮寫為SDMV。通
過SDMV應用我們可以利用文檔類來統(tǒng)一管理應用程序的所有數據,同時需要采用多
窗口以多種方式來可視化這些的數據,如棒圖,趨勢圖和參數列表,從而方便用戶
從不同角度來觀察數據。MDI雖然具有多窗口的特點,但是其為多文檔,即通常情況
下,一個視口對應一個文檔,視口+文檔便構成一個子窗口。在各個子窗口之間數據
相互獨立,如果要保持數據同步更新就需要采用特殊的技術了,采用這種方式既費
時又費力。通過筆者的實踐發(fā)現(xiàn),利用MFC本身提供的多視概念通過適當改造MDI窗
口應用程序就可以實現(xiàn)上述SDMV結構。
所謂SDMV應用程序本質上仍然是一個MDI應用程序,只是在程序中我們人為控制使其
只能生成一個文檔類,這個文檔在第一個視口創(chuàng)建時創(chuàng)建,注意,這里并不需要限
制各個視口的創(chuàng)建先后順序。此后與MDI窗口固有特性不同的是,所有新創(chuàng)建的子窗
口都不再創(chuàng)建獨立文檔,而是把該新視口直接連接到已有的文檔對象上,這樣就使
其成為單文檔多視的結構,所有相關數據都存儲在文檔對象中,一旦文擋中數據發(fā)
生改變,通過UpdateAllViews()函數通知所有相關視口,各個視口就可以在
OnUpdate()中相應數據的變化。這種響應機制如下圖所示:
圖 1 文檔-視結構數據更新機制
由于MDI本質上并不是為這種單文檔多視機制服務的,因而在實際應用時需要解決一
些問題。
1、窗口標題問題
窗口標題本來不應該成為問題,缺省情況下MDI窗口通過在文檔模板中提供的資源ID
所提供的對應字符串來確定窗口標題。但是對于SDMV應用,由于各個視口實質上是
對應于同一個文擋,因此每個視口都具有相同標題,只不過增加了一個數據用于指
示這是第幾個視口。如果在各個視口中指明具體的窗口名字,那么由不同的視口啟
動創(chuàng)建文檔產生的窗口標題就不同,這個名字會影響到后繼視口。為了作到不同類
型的視口如棒圖視口和曲線視口具有不同的標題,這就需要一定的技術處理。根據
筆者的摸索發(fā)現(xiàn)可以采用如下步驟實現(xiàn):
首先在從標準的MDI子窗口基類CMDIChildWnd派生一個自己的子窗口類,姑且命名為
CMyChild,然后在其成員變量中增加一個CString型變量用以存儲當前窗口標題:
CString winTitle;
然后在不同的視口創(chuàng)建過程中通過獲取父窗口指針按自己的意愿對上述變量進行賦
值,程序片段如下:
pChild=(CMyChild*)GetParent();
pChild->winTitle="棒圖顯示窗口";
最后在CMyChild派生類中重載CMDIChildWnd基類中的OnUpdateFrameTitle()函數來
強制實現(xiàn)窗口標題的個性化,這一函數在各種類庫手冊上和聯(lián)機幫助中都沒有,但
的確有這樣一個具有保護屬性的函數用來實現(xiàn)窗口標題的更新操作,這可以從MFC類
庫的源代碼中找到該函數的實現(xiàn)。重載后的源代碼如下:
void CMyChild::OnUpdateFrameTitle(BOOL bAddToTitle)
{
// update our parent window first
GetMDIFrame()->OnUpdateFrameTitle(bAddToTitle);
if ((GetStyle() & FWS_ADDTOTITLE) == 0)
return; // leave child window alone!
CDocument* pDocument = GetActiveDocument();
if (bAddToTitle && pDocument != NULL)
{
char szOld[256];
GetWindowText(szOld, sizeof(szOld));
char szText[256];
lstrcpy(szText,winTitle); //Modified by author!
if (m_nWindow > 0)
wsprintf(szText + lstrlen(szText), ":%d", m_nWindow);
// set title if changed, but don't remove completely
if (lstrcmp(szText, szOld) != 0)
SetWindowText(szText);
}
}
2、如何創(chuàng)建SDMV應用
如何創(chuàng)建SDMV應用比較麻煩,下面通過舉例來具體說明。該例子假設用戶需要建棒
圖類型和曲線形式的兩種視口,假設用戶已經利用CView基類派生并且實現(xiàn)了這兩個
類,分別對應于CMyChart和CMyTraceView兩個類。
1) 在應用類(從CWinApp派生出來的類)的頭文件中加入下列變量和函數原型說
明:
CMultiDocTemplate* m_pMyTraceTemplate;
CMultiDocTemplate* m_pMyChartTemplate;
int ExitInstance();
2) 在應用類的InitInstance成員函數中刪除對AddDocTemplate函數的調用和
OpenFileNew()語句,并且加入如下代碼:
m_pMyTraceTemplate = new CMultiDocTemplate(
IDR_MYTRACEVIEW,
RUNTIME_CLASS(CSimuDoc),
RUNTIME_CLASS(CMyChild), // Derived MDI child frame
RUNTIME_CLASS(CMyTraceView));
m_pMyChartTemplate = new CMultiDocTemplate(
IDR_MYCHART,
RUNTIME_CLASS(CSimuDoc),
RUNTIME_CLASS(CMyChild), // Derived MDI child frame
RUNTIME_CLASS(CMyChart));
3) 實現(xiàn)ExitInstance()函數,在其中刪除所用的兩個輔助模板:
int CTestApp::ExitInstance()
{
if(m_pMyChartTemplate) delete m_pMyChartTemplate;
if(m_pMyTraceTemplate) delete m_pMyTraceTemplate;
return TRUE;
}
4) 在菜單資源中去掉File菜單中的New和Open項,加入New Chart View和New
Trace View兩項,在對應的菜單命令中實現(xiàn)如下:
void CMainFrame::OnNewMychart()
{
// TODO: Add your command handler code here
OnNewView(((CSimuApp*)AfxGetApp())->m_pMyChartTemplate);
}
void CMainFrame::OnNewMyTrace()
{
// TODO: Add your command handler code here
OnNewView(((CSimuApp*)AfxGetApp())->m_pMyTraceTemplate);
}
上中OnNewView的實現(xiàn)如下:
BOOL CMainFrame::OnNewView(CMultiDocTemplate* pDocTemplate)
{
CMDIChildWnd* pActiveChild = MDIGetActive();
CDocument* pDocument;
if (pActiveChild == NULL ||
(pDocument = pActiveChild->GetActiveDocument()) == NULL)
{
TRACE0("Now New the specify view\n");
ASSERT(pDocTemplate != NULL);
ASSERT(pDocTemplate->IsKindOf(RUNTIME_CLASS(CDocTemplate)));
pDocTemplate->OpenDocumentFile(NULL);
return TRUE;
}
// otherwise we have a new frame to the same document!
CMultiDocTemplate* pTemplate = pDocTemplate;
ASSERT_VALID(pTemplate);
CFrameWnd* pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild);
if (pFrame == NULL)
{
TRACE0("Warning: failed to create new frame\n");
return FALSE; // command failed
}
pTemplate->InitialUpdateFrame(pFrame, pDocument);
return TRUE;
}
OnNewView是整個SDMV應用的核心組成,它的任務是創(chuàng)建一個新的指定類型的視口,
它首先判斷是否有活動視口存在,文檔是否已經創(chuàng)建,正常情況下活動視口存在則
表明文檔存在,如果不存在則利用所指定的文檔模板創(chuàng)建一個新的活動視口,否則
則只創(chuàng)建視口,同時將其連接到已存在的文檔對象上。
通過以上步驟就可以實現(xiàn)SDMV應用,在其后的具體應用中利用文檔對象的
UpdateAllViews()函數和視口的OnUpdate()函數就可以很好的工作了。
使用DDE服務
Windows 3.x是一個分時多任務操作環(huán)境,在此環(huán)境下,多個應用程序可以并發(fā)地執(zhí)
行。為了在并發(fā)執(zhí)行的多個任務之間共享數據和資源,Windows 提供了幾種機制,
主要是通過剪貼板(Clipboard)和動態(tài)數據交換(Dynamic Data Exchange)。前者對
于用戶需要直接參與的數據交換來說,是一個非常方便的工具,但是如果希望數據
交換自動進行時就必須依靠DDE技術了。編寫DDE應用的技術也發(fā)展了好幾代,從最
初的基于消息的DDE到基于DDEML(動態(tài)數據交換管理庫),再到現(xiàn)在流行的OLE技
術。DDE技術的發(fā)展使得程序開發(fā)人員編寫DDE應用更為簡潔。從發(fā)展趨勢來看,基
于OLE的數據交換是最好的,它特別符合當今軟件領域的客戶-服務器機制
(Client-Server)。為適應多平臺和Internet的需要,在OLE基礎上微軟又開發(fā)了
ActiveX技術。但是不容忽視的是,基于傳統(tǒng)的DDE數據交換也自有它的應用空間,
使用仍然廣泛。目前在Windows 3.x下,基于OLE的遠程數據交換還很不成熟,但是
在WFW(Windows for Workgroup)下基于網絡動態(tài)數據交換的技術卻很成熟,目前也
應用非常普遍。關于DDE應用的開發(fā)和NetDDE的應用可以參看附錄7。
1、回調函數的處理
由于DDEML機制需要使用回調函數,因此使用DDEML的關鍵是解決在MFC編程體系中回
調函數的使用?;卣{函數(Callback function)大量用于Windows的系統(tǒng)服務,通過
它,程序員可以安裝設備驅動程序和消息過濾系統(tǒng),以控制Windows的有效使用。
許多程序員都發(fā)現(xiàn),利用MFC或者其它的C++應用編寫回調函數是非常麻煩的,其根
本原因是回調函數是基于C編程的Windows SDK的技術,不是針對C++的,程序員可以
將一個C函數直接作為回調函數,但是如果試圖直接使用C++的成員函數作為回調函
數將發(fā)生錯誤,甚至編譯就不能通過。通過查詢資料發(fā)現(xiàn),其錯誤是普通的C++成員
函數都隱含了一個傳遞函數作為參數,亦即“this”指針,C++通過傳遞一個指向自
身的指針給其成員函數從而實現(xiàn)程序函數可以訪問C++的數據成員。這也可以理解為
什么C++類的多個實例可以共享成員函數但是確有不同的數據成員。由于this指針的
作用,使得將一個CALLBACK型的成員函數作為回調函數安裝時就會因為隱含的this
指針使得函數參數個數不匹配,從而導致回調函數安裝失敗。要解決這一問題的關
鍵就是不讓this指針起作用,通過采用以下兩種典型技術可以解決在C++中使用回調
函數所遇到的問題。這種方法具有通用性,適合于任何C++。
1. 不使用成員函數,直接使用普通C函數,為了實現(xiàn)在C函數中可以訪問類的成員變
量,可以使用友元操作符(friend),在C++中將該C函數說明為類的友元即可。這種
處理機制與普通的C編程中使用回調函數一樣。
2. 使用靜態(tài)成員函數,靜態(tài)成員函數不使用this指針作為隱含參數,這樣就可以作
為回調函數了。靜態(tài)成員函數具有兩大特點:其一,可以在沒有類實例的情況下使
用;其二,只能訪問靜態(tài)成員變量和靜態(tài)成員函數,不能訪問非靜態(tài)成員變量和非
靜態(tài)成員函數。由于在C++中使用類成員函數作為回調函數的目的就是為了訪問所有
的成員變量和成員函數,如果作不到這一點將不具有實際意義。解決的辦法也很簡
單,就是使用一個靜態(tài)類指針作為類成員,通過在類創(chuàng)建時初始化該靜態(tài)指針,如
pThis=this,然后在回調函數中通過該靜態(tài)指針就可以訪問所有成員變量和成員函
數了。這種處理辦法適用于只有一個類實例的情況,因為多個類實例將共享靜態(tài)類
成員和靜態(tài)成員函數,這就導致靜態(tài)指針指向最后創(chuàng)建的類實例。為了避免這種情
況,可以使用回調函數的一個參數來傳遞this指針,從而實現(xiàn)數據成員共享。這種
方法稍稍麻煩,這里就不再贅述。
2、在MFC中使用DDEML
對于典型的MFC應用程序,主框架窗口類(CMainFrame)只有一個實例,因此可以使用
靜態(tài)成員函數作為回調函數,從而實現(xiàn)DDE機制。具體的代碼片段如下:
(1) 在CMainFrame類中聲明如下靜態(tài)成員:
static CMainFrame* pThis;
static DWORD idInst;
static HDDEDATA CALLBACK EXPORT DdeCallback(UINT,UINT,HCONV,HSZ,HSZ, HDDEDATA,DWORD,DWORD);
(2) 在類的創(chuàng)建代碼(OnCreate())中作如下說明:
pThis=this;
lpDdeCallback=MakeProcInstance((FARPROC)DdeCallback,hInstance);
if(DdeInitialize(&idInst,(PFNCALLBACK)lpDdeCallback,CBF_FAIL_EXECUTES
|CBF_SKIP_REGISTRATIONS|CBF_SKIP_UNREGISTRATIONS,0L))
{
AfxMessageBox("不能初始化DDE服務","錯誤");
DestroyWindow();
}
(3) 回調函數實現(xiàn)如下:
HDDEDATA FAR PASCAL _export CMainFrame::DdeCallback(UINT iType,UINT iFmt, HCONV hConv,HSZ hsz1,HSZ hsz2,HDDEDATA hData,DWORD dwData1,DWORD dwData2)
{
char szBuffer[16];
int i;
switch(iType)
{
case XTYP_CONNECT: //hsz1=topiv, hsz2=service
return (HDDEDATA)TRUE;//TRUE;
case XTYP_ADVSTART: //hsz1=topic, hsz2=item
case XTYP_REQUEST:
case XTYP_ADVREQ:
case XTYP_POKE: //hsz1=Topic, hsz2=item, hData=data
case XTYP_ADVSTOP:
return NULL;
}
}
3、避免變量類型沖突
如果在MFC應用直接使用DDEML服務,那么該MFC應用在編譯時將會遇到變量類型HSZ
重復定義錯誤。經過追蹤發(fā)現(xiàn),錯誤在于在DDEML.H對HSZ作了如下定義:
DECLARE_HANDLE32(HSZ);
而在AFXEXT.H(通過stdafx.h引入)中對HSZ又作了如下說明:
typedef BPSTR FAR* HSZ; // Long handle to a string
兩個定義一個為32位整數,一個為BASIC字符串指針,當然會發(fā)生編譯器不能作變量
類型轉換的錯誤。實際上,將HSZ聲明為BASIC字符串指針主要用于在MFC應用中使用
VBX控制。要改正這一錯誤,就必須保證不要在同一個代碼模塊中使用DDEML和VBX支
持,通過將使用DDEML和VBX的代碼分開,并在使用DDEML代碼的模塊中最開頭定義如
下編譯器宏就可以解決上述問題:
#define NO_VBX_SUPPORT
使用3D控制
毫無疑問,3D控制的使用可以顯著提高Windows應用程序的界面友好性,目前,許多
流行的Windows應用程序都使用了3D控制,典型的如Microsoft公司的Office系列軟
件,而且,在Windows 95和Windows NT 4.0中,3D控制更是作為操作系統(tǒng)的一部分
直接提供,這意味著在其上運行的軟件不需要作任何特殊處理,就具有3D界面效
果,但是,很遺憾的是,在Windows 3.x中,除了命令按鈕控制使用3D控制以外,其
余所有的控制,如編輯框,列表框,檢查框等都只使用2D控制,要想使用3D控制,
程序設計人員就必須在自己的程序中作一定的修改,考慮到目前3D效果的流行,這
點努力是值得的。
為了支持3D效果,Microsoft公司提供了一個專門用于3D控制的動態(tài)連接庫,即
CTL3D.DLL,但是在其Visual C++中卻沒有如何使用3D控制的討論,并且,Visual
C++也不直接支持3D編碼,因為它不包括使用3D控制所必須的頭文件。但是,這并不
意味著在Visual C++中不能使用3D控制,只不過用戶需要從其它地方獲取技術支持
罷了。由于使用的是動態(tài)連接庫機制,因此,任何其它語言提供的3D頭文件和
CTL3D.DLL的輸入庫都是可用的。作者使用的就是Borland公司的Borland C++中提供
的CTL3D.H和CTL3D.LIB。在C/C++中使用3D控制的方法也有很多種,在這里,為節(jié)約
篇幅,只討論與本文相關的主題,即使用MFC編程時如何使用3D控制。
在MFC的所有對話框中使用3D控制可以遵循如下步驟:
1. 在CWinApp::InitInstance函數中調用Ctl3dRegister和Ctl3dAutosubclass函
數:
Ctl3dRegister(AfxGetInstanceHandle());
Ctl3dAutoSubclass(AfxGetInstanceHandle());
值得一提的是,在AppWizard產生的應用框架的CWinApp::InitInstance中有一個函
數調用為SetDialogBkColor,此函數的作用是將所有對話框的背景顏色設置為灰
色,這個功能與3D界面實現(xiàn)相同的功能,可以移去此語句。
由于CTL3D在初始化時讀入所有的系統(tǒng)顏色并自己維持,為了使應用程序能夠正確反
映系統(tǒng)顏色的變化,MFC應用程序可以在WM_SYSCOLORCHANGE消息中調用
Ctl3dColorChange函數。
2. 在MFC應用程序的CWinApp類中的ExitInstance函數中調用Ctl3dUnregister函
數,以方便Windows對CTL3D庫的正確管理。
3. 在MFC應用程序的項目文件中加入CTL3D.LIB(可以用IMPORT.EXE產生)。
使用上述CTL3D的自動子類化的機制可以大大簡化使用3D控制,如果這不滿足你的要
求,那么你就必須單獨在需要使用3D控制的對話框的OnInitDialog()中自行子類化
相關的控制類了,典型的如下代碼片斷所示:
BOOL CMyDialog::OnInitDialog()
{
Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);
return TRUE;
}
上面講了在對話框中使用3D效果的辦法,如果用戶想在非對話框中使用3D控制,典
型的在FormView導出類中使用,可以在導出類的OnInitialUpdate函數中進行適當修
改,修改的大小取決于你是否使用了3D控制的自動子類化機制。如果使用前面提到
的自動子類化方法,那么僅需要在相應的OnInitialUpdate函數中調用
Ctl3dSubclassDlg函數了,如下代碼片斷所示:
void CMyView::OnInitialUpdate()
{
Ctl3dSubclassDlg(m_hWnd,CTL3D_ALL);
}
否則,則需要修改如下:
void CMyView::OnInitialUpdate()
{
Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);
}
使用自定義消息
1、MFC的消息映射機制
Windows是一個典型的消息驅動的操作系統(tǒng),程序的運行是靠對各種消息的響應來實
現(xiàn)的,這些消息的來源非常廣泛,既包括Windows系統(tǒng)本身,如WM_CLOSE、
WM_PAINT、WM_CREATE和WM_TIMER等常用消息,又包括用戶菜單選擇、鍵盤加速
鍵以及工具條和對話框按鈕等等,如果應用程序要與其它程序協(xié)同工作,那么消息的來
源還包括其它應用程序發(fā)送的消息,串行口和并行口等硬件發(fā)送的消息等等。總
之,Windows程序的開發(fā)是圍繞著對眾多消息的合理響應和實現(xiàn)來實現(xiàn)程序的各種功
能的。使用過C語言來開發(fā)Windows程序的人都知道,在Windows程序的窗口回調函數
中需要安排Switch語句來響應大量的消息,同時由于消息的間斷性使得不同的消息
響應之間信息的傳遞是通過大量的全局變量或者靜態(tài)數據來實現(xiàn)的。
人們常用的兩種類庫OWL和MFC都提供了消息映射機制用以加速開發(fā)速度,使用者只
需要按規(guī)定定義好對應消息的處理函數自身即可,至于實際調用由類庫本身所提供
的機制進行,或采用虛函數,或采用消息映射宏。為了有效節(jié)約內存,MFC并不大量
采用虛函數機制,而是采用宏來將特定的消息映射到派生類中的響應成員函數。這
種機制不但適用于Windows自身的140條消息,而且適用于菜單命令消息和按鈕控制
消息。MFC提供的消息映射機制是非常強大的,它允許在類的各個層次上對消息進行
控制,而不簡單的局限于消息產生者本身。在應用程序接收到窗口命令時,MFC將按
如下次序尋找相應的消息控制函數:
SDI應用
MDI應用
視口
視口
文檔
文檔
SDI主框架
MDI子框架
應用
MDI主框架
應用
大多數應用對每一個命令通常都只有一個特定的命令控制函數,而這個命令控制函
數也只屬于某一特定的類,但是如果在應用中對同一消息有多個命令控制函數,那
么只有優(yōu)先級較高的命令控制函數才會被調用。為了簡化對常用命令的處理,MFC在
基類中提供并實現(xiàn)了許多消息映射的入口,如打印命令,打印預覽命令,退出命令
以及聯(lián)機幫助命令等,這樣在派生類中就繼承了所有的基類中的消息映射函數,從
而可以大大簡化編程。如果我們要在自己派生類中實現(xiàn)對消息的控制,那么必須在
派生類中加上相應的控制函數和映射入口。
2、使用自己的消息
在程序設計的更深層次,人們常常會發(fā)現(xiàn)只依賴于菜單和命令按鈕產生的消息是不
夠的,常常因為程序運行的邏輯結構和不同視口之間數據的同步而需要使用一些自
定義的消息,這樣通過在相應層次上安排消息響應函數就可以實現(xiàn)自己的特殊需
要。比如如果我們要在特定的時間間隔內通知所有數據輸出視口重新取得新數據,
要依靠菜單命令和按鈕命令實現(xiàn)不夠理想,比較理想的解決辦法是采用定時器事件
進行特定的計算操作,操作完成后再采用SendMessage發(fā)送自己的特定消息,只有當
這一消息得到處理后才會返回主控程序進行下一時間計算。通過在文檔層次上安排
對消息的響應取得最新計算數據,而后通過UpdateAllViews()成員函數來通知所有
相關視口更新數據的顯示。視口通過重載OnUpdate()成員函數就可以實現(xiàn)特定數據
的更新顯示。
如果用戶能夠熟練使用SendMessage()函數和PostMessage()函數,那么要發(fā)送自定
義消息并不難,通常有兩種選擇,其一是發(fā)送WM_COMMAND消息,通過消息的WORD
wParam參數傳遞用戶的命令ID,舉例如下:
SendMessage(WM_COMMAND,IDC_GETDATA,0); //MFC主框架發(fā)送
然后在文檔層次上安排消息映射入口:
ON_COMMAND(IDC_GETDATA, OnGetData)
同時在文檔類中實現(xiàn)OnGetData()函數:
void CSimuDoc::OnGetData()
{
TRACE("Now in SimuDoc,From OnGetData\n");
UpdateAllViews(NULL);
}
注意在上中的消息映射入口需要用戶手工加入,Visual C++提供的ClassWizard并不
能替用戶完成這一工作。上中例子沒有使用PostMessage函數而使用SendMessage函
數的原因是利用了SendMessage函數的特點,即它只有發(fā)送消息得到適當處理后方才
返回,這樣有助于程序控制。
另一種發(fā)送自定義消息的辦法是直接發(fā)送命令ID,在控制層次上采用ON_MESSAGE來
實現(xiàn)消息映射入口,注意這時的命令控制函數的原型根據Windows本身消息處理的規(guī)
定必須如下:
afx_msg LONG OnCaculationOnce(WPARAM wParam,LPARAM lParam);
相對來講,這種機制不如上述機制簡單,也就不再贅述。
使用不帶文擋-視結構的MFC應用
文檔-視結構的功能是非常強大的,可以適合于大多數應用程序,但是有時我們只需
要非常簡單的程序,為了減少最終可執(zhí)行文件尺寸和提高運行速度,我們沒有必要
使用文擋-視結構,典型的有簡單SDI應用和基于對話框的應用。
1、簡單SDI應用
此時只需要使用CWinApp和CFrameWnd兩個類就完全可以了。由于CWinApp類封裝了
WinMain函數和消息處理循環(huán),因此任何使用MFC進行編程的程序都不能脫離開該
類。實際上使用CWinApp類非常簡單,主要是派生一個用戶自己的應用類,如
CMyApp,然后只需重載CWinApp類的InitInstance()函數:
BOOL CMyApp::InitInstance()
{
m_pMainWnd=new CMainFrame();
ASSERT(m_pMainWnd!=NULL); //error checking only
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
至于所需要的主框架類,則可以直接使用ClassWizard實用程序生成,該類的頭文件
與實現(xiàn)代碼可以與CMyApp類的頭文件和實現(xiàn)代碼放在一起。注意,這里由一個技
巧,由于ClassWizard的使用需要有相應的CLW文件存在,而收工建代碼時沒有對應
的CLW文件,因此不能直接使用,解決辦法是進入App Studio實用工具后使用
ClassWizard,此時系統(tǒng)會發(fā)覺不存在相應的CLW文件,系統(tǒng)將提示你重建CLW文件并
彈出相應對話框,這時候你不需要選擇任何文件就直接選擇OK按鈕,這樣系統(tǒng)將為
你產生一個空的CLW文件,這樣就可以使用ClassWizard實用工具了。為了將CWinApp
和CFrameWnd的派生類有機地結合在一起,只需在CFrameWnd派生類的構造函數中進
行窗口創(chuàng)建即可。典型代碼如下:
CMainFrame::CMainFrame()
{
Create(NULL,"DDE Client Application",WS_OVERLAPPEDWINDOW,rectDefault,
NULL,MAKEINTRESOURCE(IDR_MAINFRAME));
}
采用ClassWizard實用程序生成相關類代碼后,所有的類的其它實現(xiàn)和維護就同普通
由AppWizard實用程序產生的代碼一樣了。
2、基于對話框的程序
有些主要用于數據的輸入和輸出等的應用在使用時沒有必要改變窗口大小,典型的
如各種聯(lián)機注冊程序,這些使用對話框作為應用的主界面就足夠了,而且開發(fā)此類
應用具有方便快捷的特點,代碼也比較短小,如果直接采用各種控制類生成所需要
的控制就特別麻煩。在Visual C++ 4.x版本中使用AppWizard就可以直接生成基于對
話框的應用。在Visual 1.x中沒有此功能,因此這類應用需要程序員自己實現(xiàn)。
實際上使用MFC實現(xiàn)基于對話框的應用非常簡單,同樣只使用兩個MFC類作為基類,
這兩個類為CWinApp類和CDialog類。所使用的對話框主界面同樣可以先用App
Studio編輯對話框界面,再使用ClassWizard產生相應代碼框架,然后修改CMyApp類
的聲明,增加一個該對話框類的成員變量m_Mydlg,最后修改CMyApp類的
InitInstance()函數如下:
BOOL CMyApp::InitInstance()
{
m_Mydlg.DoModal();
return TRUE;
}
MFC應用的人工優(yōu)化
使用C/C++編寫Windows程序的優(yōu)點就是靈活高效,運行速度快,Visual C++編譯器
本身的優(yōu)化工作相當出色,但這并不等于不需要進行適當的人工優(yōu)化,為了提高程
序的運行速度,程序員可以從以下幾方面努力:
1) 減少不必要的重復顯示
相對來講,Windows的GDI操作是比較慢的,因此在程序中我們應該盡可能地控制整
個視口的顯示和更新,如果前后兩此數據不發(fā)生變化,那么就不要重新進行視口的
GDI圖形操作,尤其對于背景圖顯示時非萬不得已時不要重繪,同時不要經常五必要
的刷新整個窗口。
2) 在視口極小化時不要進行更新屏幕操作
在窗口處于極小化時沒有必要繼續(xù)進行視口更新工作,這樣可以顯著提高速度。為
此需要在子窗口一級捕獲上述信息(視口不能捕獲該類信息),再在視口中進行相
應操作。如下代碼片段所示:
首先在子窗口類中添加如下程序段:
void CMyChild::OnSysCommand(UINT nID,LPARAM lparam)
{
CMDIChildWnd::OnSysCommand(nID,lparam);
if(nID==SC_MINIMIZE){
RedrawFlag=0;
}
else
RedrawFlag=1;
}
再在視口更新時中修改如下:
void CMyChart::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
{
if(pChild->RedrawFlag)
{
InvalidateRect(&r,FALSE);
TRACE("Now In CMyChart::OnUpdate\n");
}
}
至于上中pChild指針可以在視口創(chuàng)建的例程中獲?。?br />pChild=(CMyChild*)GetParent();
3) 使用永久性的資源
在頻繁進行GDI輸出的視口中,如在監(jiān)控軟件中常常使用的趨勢圖顯示和棒圖顯示等
等,應該考慮在類層次上建立頻繁使用的每種畫筆和刷子,這可以避免頻繁的在堆
中創(chuàng)建和刪除GDI對象,從而提高速度。
4) 使用自有設備描述句柄
亦即在創(chuàng)建視口時通過指定WM_OWNDC風格來擁有自己的顯示設備句柄,這雖然會多
消耗一些內存,一個DC大約占800字節(jié)的內存,但是這避免了每次進行GDI操作前創(chuàng)
建并合理初始化顯示設備句柄這些重復操作。特別是要自定義坐標系統(tǒng)和使用特殊
字體的視口這一點尤其重要。在16M機器日益普遍的今天為了節(jié)約一點點內存而降低
速度的做法并不可取。
5) 優(yōu)化編譯時指定/G3選項和/FPix87選項
/G3選項將強迫編譯器使用386處理器的處理代碼,使用嵌入式協(xié)處理器指令對那些
頻繁進行浮點運算的程序很有幫助。采用這兩種編譯開關雖然提高了對用戶機型的
要求,但在386逐漸被淘汰,486市場大幅度萎縮,586市場日益普及的今天上述問題
已經不再成為問題了。