?
摘一段以前的文章,原文作者:茍建兵 清華大學(xué)熱能系(北京,100084)。原文很長,這里只摘了一段。
采用MFC開發(fā)Windows程序之所以能夠大幅度提高開發(fā)速度和效率主要是因為MFC在類
層次封裝了大量Windows SDK函數(shù)和典型Windows應(yīng)用的缺省處理,這樣,用戶只需
要較少的編程就可以實現(xiàn)自己的開發(fā)任務(wù)。如果在MFC基礎(chǔ)上再配合Visual C++提供
的AppWizard、ClassWizard和AppStudio工具那么更可以大幅度加快開發(fā)進程。MFC
提供大量的基類供程序員使用,常見的如CWinApp類、CFrameWnd類、CMDIFrameWnd
類、CMDIChildWnd類、CView類、CDC類和CDocument類等等。通過從這些基類中派生
出用戶自己的類,然后重載特殊的幾個函數(shù)就可以生成一個獨立的應(yīng)用程序。可以
說,采用MFC編寫Windows應(yīng)用程序是非常方便的,雖然其學(xué)習(xí)過程并不簡單,但是
其提供的靈活高效性足以使任何Windows程序開發(fā)人員為之付出努力。如果用戶不曾
使用過MFC,那么用戶可以通過附錄中所列的參考書去學(xué)習(xí)MFC的強大功能。
采用MFC應(yīng)用框架產(chǎn)生的應(yīng)用程序使用了標(biāo)準(zhǔn)化的結(jié)構(gòu),因而使得采用MFC編寫的程
序的在不同平臺上的移植變得非常容易,事實上,MFC的16位和32位版本之間差別很
小。MFC提供的標(biāo)準(zhǔn)化結(jié)構(gòu)是經(jīng)過眾多專家分析調(diào)研后總結(jié)編寫出來的,一般情況下
可以滿足絕大多數(shù)用戶的要求,但有時用戶也可以通過重載一些函數(shù)來修改其缺省
的風(fēng)格從而實現(xiàn)自己特有的風(fēng)格,如自定義應(yīng)用圖表和灰色背景等。在MFC提供的文
檔視結(jié)構(gòu)中,文檔、視和資源之間的聯(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來產(chǎn)生的應(yīng)用基本框架,那么其也同時產(chǎn)生了缺省的圖標(biāo),如果用戶不
滿意缺省圖標(biāo)(實際上用戶很少滿足于缺省圖標(biāo)),只需要將缺省圖標(biāo)刪除,然后
編輯或者直接引入一個新的圖標(biāo),在存儲這一圖標(biāo)時只需要使用與被刪除圖標(biāo)同樣
的ID即可實現(xiàn)替代。
熟悉Windows程序開發(fā)的人都知道,在Windows上通過使用灰色背景可以增強應(yīng)用程
序的視覺效果,曾有人戲稱,灰色是圖形界面永恒的顏色。使用MFC產(chǎn)生的應(yīng)用程序
的背景缺省為白色,如果用戶希望改變成灰色或者其它顏色,那就需要使用單獨處
理,解決的辦法很多,如在每次視口的OnPaint()事件中采用灰色刷子人為填充背
景,但是這不是最好的辦法。筆者發(fā)現(xiàn)最好的辦法就是采用AfxRegisterWndClass()
函數(shù)注冊一個使用灰色背景刷的新的窗口類,這需要重載PreCreateWindow()函數(shù)來
實現(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;//修改缺省的類風(fēng)格
return TRUE;
}
采用這種方法速度最快,也最省力。同時,還可以在PreCreateWindow()函數(shù)定義所
希望的任何窗口風(fēng)格,如窗口大小,光標(biāo)式樣等。
使用單文檔-多視結(jié)構(gòu)
如果用戶使用過MFC進行編程,那么就會發(fā)現(xiàn)借助于AppWizard基于MFC無論編寫SDI
(單文檔界面)還是編寫MDI(多文檔界面)都是十分方便的。MDI應(yīng)用程序目前使用越
來越普遍,人們熟悉的Microsoft公司的Office系列產(chǎn)品以及Visual系列產(chǎn)品都是典
型的多文檔應(yīng)用程序。這種多文檔界面具有多窗口的特點,因而人們可以在一個程
序中使用多個子窗口來實現(xiàn)不同數(shù)據(jù)的瀏覽查看。如果用戶要實現(xiàn)在MDI各個窗口之
間針對同一數(shù)據(jù)進行不同的可視化就是一件比較麻煩的事情。值得慶幸的是,MFC提
供的文檔-視結(jié)構(gòu)大大簡化了這一工作。文檔-視結(jié)構(gòu)通過將數(shù)據(jù)從用戶對數(shù)據(jù)的觀
察中分離出來,從而方便實現(xiàn)多視,亦即多個視口針對同一數(shù)據(jù),如果一個視口中
數(shù)據(jù)發(fā)生改變,那么其它相關(guān)視口中的內(nèi)容也會隨之發(fā)生改變以反映數(shù)據(jù)的變化。
SDI和MDI這兩種Windows標(biāo)準(zhǔn)應(yīng)用程序框架并不是總能滿足用戶的需要,就作者的工
作而言,就特別需要一種被稱為單文檔多視的應(yīng)用程序,英文可以縮寫為SDMV。通
過SDMV應(yīng)用我們可以利用文檔類來統(tǒng)一管理應(yīng)用程序的所有數(shù)據(jù),同時需要采用多
窗口以多種方式來可視化這些的數(shù)據(jù),如棒圖,趨勢圖和參數(shù)列表,從而方便用戶
從不同角度來觀察數(shù)據(jù)。MDI雖然具有多窗口的特點,但是其為多文檔,即通常情況
下,一個視口對應(yīng)一個文檔,視口+文檔便構(gòu)成一個子窗口。在各個子窗口之間數(shù)據(jù)
相互獨立,如果要保持?jǐn)?shù)據(jù)同步更新就需要采用特殊的技術(shù)了,采用這種方式既費
時又費力。通過筆者的實踐發(fā)現(xiàn),利用MFC本身提供的多視概念通過適當(dāng)改造MDI窗
口應(yīng)用程序就可以實現(xiàn)上述SDMV結(jié)構(gòu)。
所謂SDMV應(yīng)用程序本質(zhì)上仍然是一個MDI應(yīng)用程序,只是在程序中我們?nèi)藶榭刂剖蛊?br />只能生成一個文檔類,這個文檔在第一個視口創(chuàng)建時創(chuàng)建,注意,這里并不需要限
制各個視口的創(chuàng)建先后順序。此后與MDI窗口固有特性不同的是,所有新創(chuàng)建的子窗
口都不再創(chuàng)建獨立文檔,而是把該新視口直接連接到已有的文檔對象上,這樣就使
其成為單文檔多視的結(jié)構(gòu),所有相關(guān)數(shù)據(jù)都存儲在文檔對象中,一旦文擋中數(shù)據(jù)發(fā)
生改變,通過UpdateAllViews()函數(shù)通知所有相關(guān)視口,各個視口就可以在
OnUpdate()中相應(yīng)數(shù)據(jù)的變化。這種響應(yīng)機制如下圖所示:
圖 1 文檔-視結(jié)構(gòu)數(shù)據(jù)更新機制
由于MDI本質(zhì)上并不是為這種單文檔多視機制服務(wù)的,因而在實際應(yīng)用時需要解決一
些問題。
1、窗口標(biāo)題問題
窗口標(biāo)題本來不應(yīng)該成為問題,缺省情況下MDI窗口通過在文檔模板中提供的資源ID
所提供的對應(yīng)字符串來確定窗口標(biāo)題。但是對于SDMV應(yīng)用,由于各個視口實質(zhì)上是
對應(yīng)于同一個文擋,因此每個視口都具有相同標(biāo)題,只不過增加了一個數(shù)據(jù)用于指
示這是第幾個視口。如果在各個視口中指明具體的窗口名字,那么由不同的視口啟
動創(chuàng)建文檔產(chǎn)生的窗口標(biāo)題就不同,這個名字會影響到后繼視口。為了作到不同類
型的視口如棒圖視口和曲線視口具有不同的標(biāo)題,這就需要一定的技術(shù)處理。根據(jù)
筆者的摸索發(fā)現(xiàn)可以采用如下步驟實現(xiàn):
首先在從標(biāo)準(zhǔn)的MDI子窗口基類CMDIChildWnd派生一個自己的子窗口類,姑且命名為
CMyChild,然后在其成員變量中增加一個CString型變量用以存儲當(dāng)前窗口標(biāo)題:
CString winTitle;
然后在不同的視口創(chuàng)建過程中通過獲取父窗口指針按自己的意愿對上述變量進行賦
值,程序片段如下:
pChild=(CMyChild*)GetParent();
pChild->winTitle="棒圖顯示窗口";
最后在CMyChild派生類中重載CMDIChildWnd基類中的OnUpdateFrameTitle()函數(shù)來
強制實現(xiàn)窗口標(biāo)題的個性化,這一函數(shù)在各種類庫手冊上和聯(lián)機幫助中都沒有,但
的確有這樣一個具有保護屬性的函數(shù)用來實現(xiàn)窗口標(biāo)題的更新操作,這可以從MFC類
庫的源代碼中找到該函數(shù)的實現(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應(yīng)用
如何創(chuàng)建SDMV應(yīng)用比較麻煩,下面通過舉例來具體說明。該例子假設(shè)用戶需要建棒
圖類型和曲線形式的兩種視口,假設(shè)用戶已經(jīng)利用CView基類派生并且實現(xiàn)了這兩個
類,分別對應(yīng)于CMyChart和CMyTraceView兩個類。
1) 在應(yīng)用類(從CWinApp派生出來的類)的頭文件中加入下列變量和函數(shù)原型說
明:
CMultiDocTemplate* m_pMyTraceTemplate;
CMultiDocTemplate* m_pMyChartTemplate;
int ExitInstance();
2) 在應(yīng)用類的InitInstance成員函數(shù)中刪除對AddDocTemplate函數(shù)的調(diào)用和
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()函數(shù),在其中刪除所用的兩個輔助模板:
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兩項,在對應(yīng)的菜單命令中實現(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應(yīng)用的核心組成,它的任務(wù)是創(chuàng)建一個新的指定類型的視口,
它首先判斷是否有活動視口存在,文檔是否已經(jīng)創(chuàng)建,正常情況下活動視口存在則
表明文檔存在,如果不存在則利用所指定的文檔模板創(chuàng)建一個新的活動視口,否則
則只創(chuàng)建視口,同時將其連接到已存在的文檔對象上。
通過以上步驟就可以實現(xiàn)SDMV應(yīng)用,在其后的具體應(yīng)用中利用文檔對象的
UpdateAllViews()函數(shù)和視口的OnUpdate()函數(shù)就可以很好的工作了。
使用DDE服務(wù)
Windows 3.x是一個分時多任務(wù)操作環(huán)境,在此環(huán)境下,多個應(yīng)用程序可以并發(fā)地執(zhí)
行。為了在并發(fā)執(zhí)行的多個任務(wù)之間共享數(shù)據(jù)和資源,Windows 提供了幾種機制,
主要是通過剪貼板(Clipboard)和動態(tài)數(shù)據(jù)交換(Dynamic Data Exchange)。前者對
于用戶需要直接參與的數(shù)據(jù)交換來說,是一個非常方便的工具,但是如果希望數(shù)據(jù)
交換自動進行時就必須依靠DDE技術(shù)了。編寫DDE應(yīng)用的技術(shù)也發(fā)展了好幾代,從最
初的基于消息的DDE到基于DDEML(動態(tài)數(shù)據(jù)交換管理庫),再到現(xiàn)在流行的OLE技
術(shù)。DDE技術(shù)的發(fā)展使得程序開發(fā)人員編寫DDE應(yīng)用更為簡潔。從發(fā)展趨勢來看,基
于OLE的數(shù)據(jù)交換是最好的,它特別符合當(dāng)今軟件領(lǐng)域的客戶-服務(wù)器機制
(Client-Server)。為適應(yīng)多平臺和Internet的需要,在OLE基礎(chǔ)上微軟又開發(fā)了
ActiveX技術(shù)。但是不容忽視的是,基于傳統(tǒng)的DDE數(shù)據(jù)交換也自有它的應(yīng)用空間,
使用仍然廣泛。目前在Windows 3.x下,基于OLE的遠程數(shù)據(jù)交換還很不成熟,但是
在WFW(Windows for Workgroup)下基于網(wǎng)絡(luò)動態(tài)數(shù)據(jù)交換的技術(shù)卻很成熟,目前也
應(yīng)用非常普遍。關(guān)于DDE應(yīng)用的開發(fā)和NetDDE的應(yīng)用可以參看附錄7。
1、回調(diào)函數(shù)的處理
由于DDEML機制需要使用回調(diào)函數(shù),因此使用DDEML的關(guān)鍵是解決在MFC編程體系中回
調(diào)函數(shù)的使用。回調(diào)函數(shù)(Callback function)大量用于Windows的系統(tǒng)服務(wù),通過
它,程序員可以安裝設(shè)備驅(qū)動程序和消息過濾系統(tǒng),以控制Windows的有效使用。
許多程序員都發(fā)現(xiàn),利用MFC或者其它的C++應(yīng)用編寫回調(diào)函數(shù)是非常麻煩的,其根
本原因是回調(diào)函數(shù)是基于C編程的Windows SDK的技術(shù),不是針對C++的,程序員可以
將一個C函數(shù)直接作為回調(diào)函數(shù),但是如果試圖直接使用C++的成員函數(shù)作為回調(diào)函
數(shù)將發(fā)生錯誤,甚至編譯就不能通過。通過查詢資料發(fā)現(xiàn),其錯誤是普通的C++成員
函數(shù)都隱含了一個傳遞函數(shù)作為參數(shù),亦即“this”指針,C++通過傳遞一個指向自
身的指針給其成員函數(shù)從而實現(xiàn)程序函數(shù)可以訪問C++的數(shù)據(jù)成員。這也可以理解為
什么C++類的多個實例可以共享成員函數(shù)但是確有不同的數(shù)據(jù)成員。由于this指針的
作用,使得將一個CALLBACK型的成員函數(shù)作為回調(diào)函數(shù)安裝時就會因為隱含的this
指針使得函數(shù)參數(shù)個數(shù)不匹配,從而導(dǎo)致回調(diào)函數(shù)安裝失敗。要解決這一問題的關(guān)
鍵就是不讓this指針起作用,通過采用以下兩種典型技術(shù)可以解決在C++中使用回調(diào)
函數(shù)所遇到的問題。這種方法具有通用性,適合于任何C++。
1. 不使用成員函數(shù),直接使用普通C函數(shù),為了實現(xiàn)在C函數(shù)中可以訪問類的成員變
量,可以使用友元操作符(friend),在C++中將該C函數(shù)說明為類的友元即可。這種
處理機制與普通的C編程中使用回調(diào)函數(shù)一樣。
2. 使用靜態(tài)成員函數(shù),靜態(tài)成員函數(shù)不使用this指針作為隱含參數(shù),這樣就可以作
為回調(diào)函數(shù)了。靜態(tài)成員函數(shù)具有兩大特點:其一,可以在沒有類實例的情況下使
用;其二,只能訪問靜態(tài)成員變量和靜態(tài)成員函數(shù),不能訪問非靜態(tài)成員變量和非
靜態(tài)成員函數(shù)。由于在C++中使用類成員函數(shù)作為回調(diào)函數(shù)的目的就是為了訪問所有
的成員變量和成員函數(shù),如果作不到這一點將不具有實際意義。解決的辦法也很簡
單,就是使用一個靜態(tài)類指針作為類成員,通過在類創(chuàng)建時初始化該靜態(tài)指針,如
pThis=this,然后在回調(diào)函數(shù)中通過該靜態(tài)指針就可以訪問所有成員變量和成員函
數(shù)了。這種處理辦法適用于只有一個類實例的情況,因為多個類實例將共享靜態(tài)類
成員和靜態(tài)成員函數(shù),這就導(dǎo)致靜態(tài)指針指向最后創(chuàng)建的類實例。為了避免這種情
況,可以使用回調(diào)函數(shù)的一個參數(shù)來傳遞this指針,從而實現(xiàn)數(shù)據(jù)成員共享。這種
方法稍稍麻煩,這里就不再贅述。
2、在MFC中使用DDEML
對于典型的MFC應(yīng)用程序,主框架窗口類(CMainFrame)只有一個實例,因此可以使用
靜態(tài)成員函數(shù)作為回調(diào)函數(shù),從而實現(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服務(wù)","錯誤");
DestroyWindow();
}
(3) 回調(diào)函數(shù)實現(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應(yīng)用直接使用DDEML服務(wù),那么該MFC應(yīng)用在編譯時將會遇到變量類型HSZ
重復(fù)定義錯誤。經(jīng)過追蹤發(fā)現(xiàn),錯誤在于在DDEML.H對HSZ作了如下定義:
DECLARE_HANDLE32(HSZ);
而在AFXEXT.H(通過stdafx.h引入)中對HSZ又作了如下說明:
typedef BPSTR FAR* HSZ; // Long handle to a string
兩個定義一個為32位整數(shù),一個為BASIC字符串指針,當(dāng)然會發(fā)生編譯器不能作變量
類型轉(zhuǎn)換的錯誤。實際上,將HSZ聲明為BASIC字符串指針主要用于在MFC應(yīng)用中使用
VBX控制。要改正這一錯誤,就必須保證不要在同一個代碼模塊中使用DDEML和VBX支
持,通過將使用DDEML和VBX的代碼分開,并在使用DDEML代碼的模塊中最開頭定義如
下編譯器宏就可以解決上述問題:
#define NO_VBX_SUPPORT
使用3D控制
毫無疑問,3D控制的使用可以顯著提高Windows應(yīng)用程序的界面友好性,目前,許多
流行的Windows應(yīng)用程序都使用了3D控制,典型的如Microsoft公司的Office系列軟
件,而且,在Windows 95和Windows NT 4.0中,3D控制更是作為操作系統(tǒng)的一部分
直接提供,這意味著在其上運行的軟件不需要作任何特殊處理,就具有3D界面效
果,但是,很遺憾的是,在Windows 3.x中,除了命令按鈕控制使用3D控制以外,其
余所有的控制,如編輯框,列表框,檢查框等都只使用2D控制,要想使用3D控制,
程序設(shè)計人員就必須在自己的程序中作一定的修改,考慮到目前3D效果的流行,這
點努力是值得的。
為了支持3D效果,Microsoft公司提供了一個專門用于3D控制的動態(tài)連接庫,即
CTL3D.DLL,但是在其Visual C++中卻沒有如何使用3D控制的討論,并且,Visual
C++也不直接支持3D編碼,因為它不包括使用3D控制所必須的頭文件。但是,這并不
意味著在Visual C++中不能使用3D控制,只不過用戶需要從其它地方獲取技術(shù)支持
罷了。由于使用的是動態(tài)連接庫機制,因此,任何其它語言提供的3D頭文件和
CTL3D.DLL的輸入庫都是可用的。作者使用的就是Borland公司的Borland C++中提供
的CTL3D.H和CTL3D.LIB。在C/C++中使用3D控制的方法也有很多種,在這里,為節(jié)約
篇幅,只討論與本文相關(guān)的主題,即使用MFC編程時如何使用3D控制。
在MFC的所有對話框中使用3D控制可以遵循如下步驟:
1. 在CWinApp::InitInstance函數(shù)中調(diào)用Ctl3dRegister和Ctl3dAutosubclass函
數(shù):
Ctl3dRegister(AfxGetInstanceHandle());
Ctl3dAutoSubclass(AfxGetInstanceHandle());
值得一提的是,在AppWizard產(chǎn)生的應(yīng)用框架的CWinApp::InitInstance中有一個函
數(shù)調(diào)用為SetDialogBkColor,此函數(shù)的作用是將所有對話框的背景顏色設(shè)置為灰
色,這個功能與3D界面實現(xiàn)相同的功能,可以移去此語句。
由于CTL3D在初始化時讀入所有的系統(tǒng)顏色并自己維持,為了使應(yīng)用程序能夠正確反
映系統(tǒng)顏色的變化,MFC應(yīng)用程序可以在WM_SYSCOLORCHANGE消息中調(diào)用
Ctl3dColorChange函數(shù)。
2. 在MFC應(yīng)用程序的CWinApp類中的ExitInstance函數(shù)中調(diào)用Ctl3dUnregister函
數(shù),以方便Windows對CTL3D庫的正確管理。
3. 在MFC應(yīng)用程序的項目文件中加入CTL3D.LIB(可以用IMPORT.EXE產(chǎn)生)。
使用上述CTL3D的自動子類化的機制可以大大簡化使用3D控制,如果這不滿足你的要
求,那么你就必須單獨在需要使用3D控制的對話框的OnInitDialog()中自行子類化
相關(guān)的控制類了,典型的如下代碼片斷所示:
BOOL CMyDialog::OnInitDialog()
{
Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);
return TRUE;
}
上面講了在對話框中使用3D效果的辦法,如果用戶想在非對話框中使用3D控制,典
型的在FormView導(dǎo)出類中使用,可以在導(dǎo)出類的OnInitialUpdate函數(shù)中進行適當(dāng)修
改,修改的大小取決于你是否使用了3D控制的自動子類化機制。如果使用前面提到
的自動子類化方法,那么僅需要在相應(yīng)的OnInitialUpdate函數(shù)中調(diào)用
Ctl3dSubclassDlg函數(shù)了,如下代碼片斷所示:
void CMyView::OnInitialUpdate()
{
Ctl3dSubclassDlg(m_hWnd,CTL3D_ALL);
}
否則,則需要修改如下:
void CMyView::OnInitialUpdate()
{
Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);
}
使用自定義消息
1、MFC的消息映射機制
Windows是一個典型的消息驅(qū)動的操作系統(tǒng),程序的運行是靠對各種消息的響應(yīng)來實
現(xiàn)的,這些消息的來源非常廣泛,既包括Windows系統(tǒng)本身,如WM_CLOSE、
WM_PAINT、WM_CREATE和WM_TIMER等常用消息,又包括用戶菜單選擇、鍵盤加速
鍵以及工具條和對話框按鈕等等,如果應(yīng)用程序要與其它程序協(xié)同工作,那么消息的來
源還包括其它應(yīng)用程序發(fā)送的消息,串行口和并行口等硬件發(fā)送的消息等等。總
之,Windows程序的開發(fā)是圍繞著對眾多消息的合理響應(yīng)和實現(xiàn)來實現(xiàn)程序的各種功
能的。使用過C語言來開發(fā)Windows程序的人都知道,在Windows程序的窗口回調(diào)函數(shù)
中需要安排Switch語句來響應(yīng)大量的消息,同時由于消息的間斷性使得不同的消息
響應(yīng)之間信息的傳遞是通過大量的全局變量或者靜態(tài)數(shù)據(jù)來實現(xiàn)的。
人們常用的兩種類庫OWL和MFC都提供了消息映射機制用以加速開發(fā)速度,使用者只
需要按規(guī)定定義好對應(yīng)消息的處理函數(shù)自身即可,至于實際調(diào)用由類庫本身所提供
的機制進行,或采用虛函數(shù),或采用消息映射宏。為了有效節(jié)約內(nèi)存,MFC并不大量
采用虛函數(shù)機制,而是采用宏來將特定的消息映射到派生類中的響應(yīng)成員函數(shù)。這
種機制不但適用于Windows自身的140條消息,而且適用于菜單命令消息和按鈕控制
消息。MFC提供的消息映射機制是非常強大的,它允許在類的各個層次上對消息進行
控制,而不簡單的局限于消息產(chǎn)生者本身。在應(yīng)用程序接收到窗口命令時,MFC將按
如下次序?qū)ふ蚁鄳?yīng)的消息控制函數(shù):
SDI應(yīng)用
MDI應(yīng)用
視口
視口
文檔
文檔
SDI主框架
MDI子框架
應(yīng)用
MDI主框架
應(yīng)用
大多數(shù)應(yīng)用對每一個命令通常都只有一個特定的命令控制函數(shù),而這個命令控制函
數(shù)也只屬于某一特定的類,但是如果在應(yīng)用中對同一消息有多個命令控制函數(shù),那
么只有優(yōu)先級較高的命令控制函數(shù)才會被調(diào)用。為了簡化對常用命令的處理,MFC在
基類中提供并實現(xiàn)了許多消息映射的入口,如打印命令,打印預(yù)覽命令,退出命令
以及聯(lián)機幫助命令等,這樣在派生類中就繼承了所有的基類中的消息映射函數(shù),從
而可以大大簡化編程。如果我們要在自己派生類中實現(xiàn)對消息的控制,那么必須在
派生類中加上相應(yīng)的控制函數(shù)和映射入口。
2、使用自己的消息
在程序設(shè)計的更深層次,人們常常會發(fā)現(xiàn)只依賴于菜單和命令按鈕產(chǎn)生的消息是不
夠的,常常因為程序運行的邏輯結(jié)構(gòu)和不同視口之間數(shù)據(jù)的同步而需要使用一些自
定義的消息,這樣通過在相應(yīng)層次上安排消息響應(yīng)函數(shù)就可以實現(xiàn)自己的特殊需
要。比如如果我們要在特定的時間間隔內(nèi)通知所有數(shù)據(jù)輸出視口重新取得新數(shù)據(jù),
要依靠菜單命令和按鈕命令實現(xiàn)不夠理想,比較理想的解決辦法是采用定時器事件
進行特定的計算操作,操作完成后再采用SendMessage發(fā)送自己的特定消息,只有當(dāng)
這一消息得到處理后才會返回主控程序進行下一時間計算。通過在文檔層次上安排
對消息的響應(yīng)取得最新計算數(shù)據(jù),而后通過UpdateAllViews()成員函數(shù)來通知所有
相關(guān)視口更新數(shù)據(jù)的顯示。視口通過重載OnUpdate()成員函數(shù)就可以實現(xiàn)特定數(shù)據(jù)
的更新顯示。
如果用戶能夠熟練使用SendMessage()函數(shù)和PostMessage()函數(shù),那么要發(fā)送自定
義消息并不難,通常有兩種選擇,其一是發(fā)送WM_COMMAND消息,通過消息的WORD
wParam參數(shù)傳遞用戶的命令I(lǐng)D,舉例如下:
SendMessage(WM_COMMAND,IDC_GETDATA,0); //MFC主框架發(fā)送
然后在文檔層次上安排消息映射入口:
ON_COMMAND(IDC_GETDATA, OnGetData)
同時在文檔類中實現(xiàn)OnGetData()函數(shù):
void CSimuDoc::OnGetData()
{
TRACE("Now in SimuDoc,From OnGetData\n");
UpdateAllViews(NULL);
}
注意在上中的消息映射入口需要用戶手工加入,Visual C++提供的ClassWizard并不
能替用戶完成這一工作。上中例子沒有使用PostMessage函數(shù)而使用SendMessage函
數(shù)的原因是利用了SendMessage函數(shù)的特點,即它只有發(fā)送消息得到適當(dāng)處理后方才
返回,這樣有助于程序控制。
另一種發(fā)送自定義消息的辦法是直接發(fā)送命令I(lǐng)D,在控制層次上采用ON_MESSAGE來
實現(xiàn)消息映射入口,注意這時的命令控制函數(shù)的原型根據(jù)Windows本身消息處理的規(guī)
定必須如下:
afx_msg LONG OnCaculationOnce(WPARAM wParam,LPARAM lParam);
相對來講,這種機制不如上述機制簡單,也就不再贅述。
使用不帶文擋-視結(jié)構(gòu)的MFC應(yīng)用
文檔-視結(jié)構(gòu)的功能是非常強大的,可以適合于大多數(shù)應(yīng)用程序,但是有時我們只需
要非常簡單的程序,為了減少最終可執(zhí)行文件尺寸和提高運行速度,我們沒有必要
使用文擋-視結(jié)構(gòu),典型的有簡單SDI應(yīng)用和基于對話框的應(yīng)用。
1、簡單SDI應(yīng)用
此時只需要使用CWinApp和CFrameWnd兩個類就完全可以了。由于CWinApp類封裝了
WinMain函數(shù)和消息處理循環(huán),因此任何使用MFC進行編程的程序都不能脫離開該
類。實際上使用CWinApp類非常簡單,主要是派生一個用戶自己的應(yīng)用類,如
CMyApp,然后只需重載CWinApp類的InitInstance()函數(shù):
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的使用需要有相應(yīng)的CLW文件存在,而收工建代碼時沒有對應(yīng)
的CLW文件,因此不能直接使用,解決辦法是進入App Studio實用工具后使用
ClassWizard,此時系統(tǒng)會發(fā)覺不存在相應(yīng)的CLW文件,系統(tǒng)將提示你重建CLW文件并
彈出相應(yīng)對話框,這時候你不需要選擇任何文件就直接選擇OK按鈕,這樣系統(tǒng)將為
你產(chǎn)生一個空的CLW文件,這樣就可以使用ClassWizard實用工具了。為了將CWinApp
和CFrameWnd的派生類有機地結(jié)合在一起,只需在CFrameWnd派生類的構(gòu)造函數(shù)中進
行窗口創(chuàng)建即可。典型代碼如下:
CMainFrame::CMainFrame()
{
Create(NULL,"DDE Client Application",WS_OVERLAPPEDWINDOW,rectDefault,
NULL,MAKEINTRESOURCE(IDR_MAINFRAME));
}
采用ClassWizard實用程序生成相關(guān)類代碼后,所有的類的其它實現(xiàn)和維護就同普通
由AppWizard實用程序產(chǎn)生的代碼一樣了。
2、基于對話框的程序
有些主要用于數(shù)據(jù)的輸入和輸出等的應(yīng)用在使用時沒有必要改變窗口大小,典型的
如各種聯(lián)機注冊程序,這些使用對話框作為應(yīng)用的主界面就足夠了,而且開發(fā)此類
應(yīng)用具有方便快捷的特點,代碼也比較短小,如果直接采用各種控制類生成所需要
的控制就特別麻煩。在Visual C++ 4.x版本中使用AppWizard就可以直接生成基于對
話框的應(yīng)用。在Visual 1.x中沒有此功能,因此這類應(yīng)用需要程序員自己實現(xiàn)。
實際上使用MFC實現(xiàn)基于對話框的應(yīng)用非常簡單,同樣只使用兩個MFC類作為基類,
這兩個類為CWinApp類和CDialog類。所使用的對話框主界面同樣可以先用App
Studio編輯對話框界面,再使用ClassWizard產(chǎn)生相應(yīng)代碼框架,然后修改CMyApp類
的聲明,增加一個該對話框類的成員變量m_Mydlg,最后修改CMyApp類的
InitInstance()函數(shù)如下:
BOOL CMyApp::InitInstance()
{
m_Mydlg.DoModal();
return TRUE;
}
MFC應(yīng)用的人工優(yōu)化
使用C/C++編寫Windows程序的優(yōu)點就是靈活高效,運行速度快,Visual C++編譯器
本身的優(yōu)化工作相當(dāng)出色,但這并不等于不需要進行適當(dāng)?shù)娜斯?yōu)化,為了提高程
序的運行速度,程序員可以從以下幾方面努力:
1) 減少不必要的重復(fù)顯示
相對來講,Windows的GDI操作是比較慢的,因此在程序中我們應(yīng)該盡可能地控制整
個視口的顯示和更新,如果前后兩此數(shù)據(jù)不發(fā)生變化,那么就不要重新進行視口的
GDI圖形操作,尤其對于背景圖顯示時非萬不得已時不要重繪,同時不要經(jīng)常五必要
的刷新整個窗口。
2) 在視口極小化時不要進行更新屏幕操作
在窗口處于極小化時沒有必要繼續(xù)進行視口更新工作,這樣可以顯著提高速度。為
此需要在子窗口一級捕獲上述信息(視口不能捕獲該類信息),再在視口中進行相
應(yīng)操作。如下代碼片段所示:
首先在子窗口類中添加如下程序段:
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)建的例程中獲取:
pChild=(CMyChild*)GetParent();
3) 使用永久性的資源
在頻繁進行GDI輸出的視口中,如在監(jiān)控軟件中常常使用的趨勢圖顯示和棒圖顯示等
等,應(yīng)該考慮在類層次上建立頻繁使用的每種畫筆和刷子,這可以避免頻繁的在堆
中創(chuàng)建和刪除GDI對象,從而提高速度。
4) 使用自有設(shè)備描述句柄
亦即在創(chuàng)建視口時通過指定WM_OWNDC風(fēng)格來擁有自己的顯示設(shè)備句柄,這雖然會多
消耗一些內(nèi)存,一個DC大約占800字節(jié)的內(nèi)存,但是這避免了每次進行GDI操作前創(chuàng)
建并合理初始化顯示設(shè)備句柄這些重復(fù)操作。特別是要自定義坐標(biāo)系統(tǒng)和使用特殊
字體的視口這一點尤其重要。在16M機器日益普遍的今天為了節(jié)約一點點內(nèi)存而降低
速度的做法并不可取。
5) 優(yōu)化編譯時指定/G3選項和/FPix87選項
/G3選項將強迫編譯器使用386處理器的處理代碼,使用嵌入式協(xié)處理器指令對那些
頻繁進行浮點運算的程序很有幫助。采用這兩種編譯開關(guān)雖然提高了對用戶機型的
要求,但在386逐漸被淘汰,486市場大幅度萎縮,586市場日益普及的今天上述問題
已經(jīng)不再成為問題了。