Visual C++/MFC入門教程
VC開(kāi)發(fā)指南
講述Visual C++/MFC開(kāi)發(fā)的基本知識(shí),文檔/視結(jié)構(gòu),窗體控件的使用和一些基本的網(wǎng)絡(luò)開(kāi)發(fā)知識(shí)。同時(shí)指出一些在開(kāi)發(fā)中容易犯的錯(cuò)誤和一些注意事項(xiàng)。本教程主要側(cè)重于講解MFC中各個(gè)類的使用和函數(shù)功能,同時(shí)在重要內(nèi)容上都帶有例程。
整個(gè)教程分為六章,分別為
+-- 第一章 VC入門
|------ 1.1 如何學(xué)好VC
|------ 1.2 理解Windows消息機(jī)制
|------ 1.3 利用Visual C++/MFC開(kāi)發(fā)Windows程序的優(yōu)勢(shì)
|------ 1.4 利用MFC進(jìn)行開(kāi)發(fā)的通用方法介紹
|------ 1.5 MFC中常用類,宏,函數(shù)介紹
+-- 第二章 圖形輸出
|------ 2.1 和GUI有關(guān)的各種對(duì)象
|------ 2.2 在窗口中輸出文字
|------ 2.3 使用點(diǎn),刷子,筆進(jìn)行繪圖
|------ 2.4 在窗口中繪制設(shè)備相關(guān)位圖,圖標(biāo),設(shè)備無(wú)關(guān)位圖
|------ 2.5 使用各種映射方式
|------ 2.6 多邊形和剪貼區(qū)域
+-- 第三章 文檔視結(jié)構(gòu)
|------ 3.1 文檔 視圖 框架窗口間的關(guān)系和消息傳送規(guī)律
|------ 3.2 接收用戶輸入
|------ 3.3 使用菜單
|------ 3.4 文檔,視,框架之 間相互作用
|------ 3.5 利用序列化進(jìn)行文件讀寫
|------ 3.6 MFC中所提供的各種視類介紹
+-- 第四章 窗口控件
|------ 4.1 Button
|------ 4.2 Static Box
|------ 4.3 Edit Box
|------ 4.4 Scroll Bar
|------ 4.5 List Box/Check List Box
|------ 4.6 Combo Box/Combo Box Ex
|------ 4.7 Tree Ctrl
|------ 4.8 List Ctrl
|------ 4.9 Tab Ctrl
|------ 4.A Tool Bar
|------ 4.B Status Bar
|------ 4.C Dialog Bar
|------ 4.D 利用AppWizard創(chuàng)建并使用ToolBar StatusBar Dialog Bar
|------ 4.E General Window
+-- 第五章 對(duì)話框
|------ 5.1 使用資源編輯器編輯對(duì)話框
|------ 5.2 創(chuàng)建有模式對(duì)話框
|------ 5.3 創(chuàng)建無(wú)模式對(duì)話框
|------ 5.4 在對(duì)話框中進(jìn)行消息映射
|------ 5.5 在對(duì)話框中進(jìn)行數(shù)據(jù)交換和數(shù)據(jù)檢查
|------ 5.6 使用屬性對(duì)話框
|------ 5.7 使用通用對(duì)話框
|------ 5.8 建立以對(duì)話框?yàn)榛A(chǔ)的應(yīng)用
|------ 5.9 使用對(duì)話框作為子窗口
+-- 第六章 網(wǎng)絡(luò)通信開(kāi)發(fā)
|------ 6.1 WinSock介紹
|------ 6.2 利用WinSock進(jìn)行無(wú)連接的通信
+------ 6.3 利用WinSock建立有連接的通信
1.1 如何學(xué)好VC
這個(gè)問(wèn)題很多朋友都問(wèn)過(guò)我,當(dāng)然流汗是必須的,但同時(shí)如果按照某種思路進(jìn)行有計(jì)劃的學(xué)習(xí)就會(huì)起到更好的效果。萬(wàn)事開(kāi)頭難,為了幫助朋友們更快的掌握VC開(kāi)發(fā),下面我將自己的一點(diǎn)體會(huì)講一下:
1、需要有好的C/C++基礎(chǔ)。正所謂“磨刀不誤砍柴工”,最開(kāi)始接觸VC時(shí)不要急于開(kāi)始Windows程序開(kāi)發(fā),而是應(yīng)該進(jìn)行一些字符界面程序的編寫。這樣做的目的主要是增加對(duì)語(yǔ)言的熟悉程度,同時(shí)也訓(xùn)練自己的思維和熟悉一些在編程中常犯的錯(cuò)誤。更重要的是理解并能運(yùn)用C++的各種特性,這些在以后的開(kāi)發(fā)中都會(huì)有很大的幫助,特別是利用MFC進(jìn)行開(kāi)發(fā)的朋友對(duì)C++一定要能熟練運(yùn)用。
2、理解Windows的消息機(jī)制,窗口句柄和其他GUI句柄的含義和用途。了解和MFC各個(gè)類功能相近的API函數(shù)。
3、一定要理解MFC中消息映射的作用。
4、訓(xùn)練自己在編寫代碼時(shí)不使用參考書而是使用Help Online。
5、記住一些常用的消息名稱和參數(shù)的意義。
6、學(xué)會(huì)看別人的代碼。
7、多看書,少買書,買書前一定要慎重。
8、閑下來(lái)的時(shí)候就看參考書。
9、多來(lái)我的主頁(yè)。^O^
后面幾條是我個(gè)人的一點(diǎn)意見(jiàn),你可以根據(jù)需要和自身的情況選用適用于自己的方法。
此外我將一些我在選擇參考書時(shí)的原則:
對(duì)于初學(xué)者:應(yīng)該選擇一些內(nèi)容比較全面的書籍,并且書籍中的內(nèi)容應(yīng)該以合理的方式安排,在使用該書時(shí)可以達(dá)到循序漸進(jìn)的效果,書中的代碼要有詳細(xì)的講解。盡量買翻譯的書,因?yàn)檫@些書一般都比較易懂,而且語(yǔ)言比較輕松。買書前一定要慎重如果買到不好用的書可能會(huì)對(duì)自己的學(xué)習(xí)積極性產(chǎn)生打擊。
對(duì)于已經(jīng)掌握了VC的朋友:這種程度的開(kāi)發(fā)者應(yīng)該加深自己對(duì)系統(tǒng)原理,技術(shù)要點(diǎn)的認(rèn)識(shí)。需要選擇一些對(duì)原理講解的比較透徹的書籍,這樣一來(lái)才會(huì)對(duì)新技術(shù)有更多的了解,最好書中對(duì)技術(shù)的應(yīng)用有一定的闡述。盡量選擇示范代碼必較精簡(jiǎn)的書,可以節(jié)約銀子。
此外最好涉獵一些輔助性的書籍。
1.2 理解Windows消息機(jī)制
Windows系統(tǒng)是一個(gè)消息驅(qū)動(dòng)的OS,什么是消息呢?我很難說(shuō)得清楚,也很難下一個(gè)定義(誰(shuí)在噓我),我下面從不同的幾個(gè)方面講解一下,希望大家看了后有一點(diǎn)了解。
1、消息的組成:一個(gè)消息由一個(gè)消息名稱(UINT),和兩個(gè)參數(shù)(WPARAM,LPARAM)。當(dāng)用戶進(jìn)行了輸入或是窗口的狀態(tài)發(fā)生改變時(shí)系統(tǒng)都會(huì)發(fā)送消息到某一個(gè)窗口。例如當(dāng)菜單轉(zhuǎn)中之后會(huì)有WM_COMMAND消息發(fā)送,WPARAM的高字中(HIWORD(wParam))是命令的ID號(hào),對(duì)菜單來(lái)講就是菜單ID。當(dāng)然用戶也可以定義自己的消息名稱,也可以利用自定義消息來(lái)發(fā)送通知和傳送數(shù)據(jù)。
2、誰(shuí)將收到消息:一個(gè)消息必須由一個(gè)窗口接收。在窗口的過(guò)程(WNDPROC)中可以對(duì)消息進(jìn)行分析,對(duì)自己感興趣的消息進(jìn)行處理。例如你希望對(duì)菜單選擇進(jìn)行處理那么你可以定義對(duì)WM_COMMAND進(jìn)行處理的代碼,如果希望在窗口中進(jìn)行圖形輸出就必須對(duì)WM_PAINT進(jìn)行處理。
3、未處理的消息到那里去了:M$為窗口編寫了默認(rèn)的窗口過(guò)程,這個(gè)窗口過(guò)程將負(fù)責(zé)處理那些你不處理消息。正因?yàn)橛辛诉@個(gè)默認(rèn)窗口過(guò)程我們才可以利用Windows的窗口進(jìn)行開(kāi)發(fā)而不必過(guò)多關(guān)注窗口各種消息的處理。例如窗口在被拖動(dòng)時(shí)會(huì)有很多消息發(fā)送,而我們都可以不予理睬讓系統(tǒng)自己去處理。
4、窗口句柄:說(shuō)到消息就不能不說(shuō)窗口句柄,系統(tǒng)通過(guò)窗口句柄來(lái)在整個(gè)系統(tǒng)中唯一標(biāo)識(shí)一個(gè)窗口,發(fā)送一個(gè)消息時(shí)必須指定一個(gè)窗口句柄表明該消息由那個(gè)窗口接收。而每個(gè)窗口都會(huì)有自己的窗口過(guò)程,所以用戶的輸入就會(huì)被正確的處理。例如有兩個(gè)窗口共用一個(gè)窗口過(guò)程代碼,你在窗口一上按下鼠標(biāo)時(shí)消息就會(huì)通過(guò)窗口一的句柄被發(fā)送到窗口一而不是窗口二。
5、示例:下面有一段偽代碼演示如何在窗口過(guò)程中處理消息
LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
switch(uMessageType)
{//使用SWITCH語(yǔ)句將各種消息分開(kāi)
case(WM_PAINT):
doYourWindow(...);//在窗口需要重新繪制時(shí)進(jìn)行輸出
break;
case(WM_LBUTTONDOWN):
doYourWork(...);//在鼠標(biāo)左鍵被按下時(shí)進(jìn)行處理
break;
default:
callDefaultWndProc(...);//對(duì)于其它情況就讓系統(tǒng)自己處理
break;
}
}
接下來(lái)談?wù)勈裁词窍C(jī)制:系統(tǒng)將會(huì)維護(hù)一個(gè)或多個(gè)消息隊(duì)列,所有產(chǎn)生的消息都回被放入或是插入隊(duì)列中。系統(tǒng)會(huì)在隊(duì)列中取出每一條消息,根據(jù)消息的接收句柄而將該消息發(fā)送給擁有該窗口的程序的消息循環(huán)。每一個(gè)運(yùn)行的程序都有自己的消息循環(huán),在循環(huán)中得到屬于自己的消息并根據(jù)接收窗口的句柄調(diào)用相應(yīng)的窗口過(guò)程。而在沒(méi)有消息時(shí)消息循環(huán)就將控制權(quán)交給系統(tǒng)所以Windows可以同時(shí)進(jìn)行多個(gè)任務(wù)。下面的偽代碼演示了消息循環(huán)的用法:
while(1)
{
id=getMessage(...);
if(id == quit)
break;
translateMessage(...);
}
當(dāng)該程序沒(méi)有消息通知時(shí)getMessage就不會(huì)返回,也就不會(huì)占用系統(tǒng)的CPU時(shí)間。 圖示消息投遞模式
在16位的系統(tǒng)中系統(tǒng)中只有一個(gè)消息隊(duì)列,所以系統(tǒng)必須等待當(dāng)前任務(wù)處理消息后才可以發(fā)送下一消息到相應(yīng)程序,如果一個(gè)程序陷如死循環(huán)或是耗時(shí)操作時(shí)系統(tǒng)就會(huì)得不到控制權(quán)。這種多任務(wù)系統(tǒng)也就稱為協(xié)同式的多任務(wù)系統(tǒng)。Windows3.X就是這種系統(tǒng)。
而32位的系統(tǒng)中每一運(yùn)行的程序都會(huì)有一個(gè)消息隊(duì)列,所以系統(tǒng)可以在多個(gè)消息隊(duì)列中轉(zhuǎn)換而不必等待當(dāng)前程序完成消息處理就可以得到控制權(quán)。這種多任務(wù)系統(tǒng)就稱為搶先式的多任務(wù)系統(tǒng)。Windows95/NT就是這種系統(tǒng)。
1.3 利用Visual C++/MFC開(kāi)發(fā)Windows程序的優(yōu)勢(shì)
MFC借助C++的優(yōu)勢(shì)為Windows開(kāi)發(fā)開(kāi)辟了一片新天地,同時(shí)也借助ApplicationWizzard使開(kāi)發(fā)者擺脫離了那些每次都必寫基本代碼,借助ClassWizard和消息映射使開(kāi)發(fā)者擺脫了定義消息處理時(shí)那種混亂和冗長(zhǎng)的代碼段。更令人興奮的是利用C++的封裝功能使開(kāi)發(fā)者擺脫Windows中各種句柄的困擾,只需要面對(duì)C++中的對(duì)象,這樣一來(lái)使開(kāi)發(fā)更接近開(kāi)發(fā)語(yǔ)言而遠(yuǎn)離系統(tǒng)。(但我個(gè)人認(rèn)為了解系統(tǒng)原理對(duì)開(kāi)發(fā)很有幫助)
正因?yàn)镸FC是建立在C++的基礎(chǔ)上,所以我強(qiáng)調(diào)C/C++語(yǔ)言基礎(chǔ)對(duì)開(kāi)發(fā)的重要性。利用C++的封裝性開(kāi)發(fā)者可以更容易理解和操作各種窗口對(duì)象;利用C++的派生性開(kāi)發(fā)者可以減少開(kāi)發(fā)自定義窗口的時(shí)間和創(chuàng)造出可重用的代碼;利用虛擬性可以在必要時(shí)更好的控制窗口的活動(dòng)。而且C++本身所具備的超越C語(yǔ)言的特性都可以使開(kāi)發(fā)者編寫出更易用,更靈活的代碼。
在MFC中對(duì)消息的處理利用了消息映射的方法,該方法的基礎(chǔ)是宏定義實(shí)現(xiàn),通過(guò)宏定義將消息分派到不同的成員函數(shù)進(jìn)行處理。下面簡(jiǎn)單講述一下這種方法的實(shí)現(xiàn)方法:
代碼如下
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
//}}AFX_MSG_MAP
ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
END_MESSAGE_MAP()
經(jīng)過(guò)編譯后,代碼被替換為如下形式(這只是作講解,實(shí)際情況比這復(fù)雜得多):
//BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
CMainFrame::newWndProc(...)
{
switch(...)
{
//{{AFX_MSG_MAP(CMainFrame)
// ON_WM_CREATE()
case(WM_CREATE):
OnCreate(...);
break;
//}}AFX_MSG_MAP
// ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
case(WM_COMMAND):
if(HIWORD(wP)==ID_FONT_DROPDOWN)
{
DoNothing(...);
}
break;
//END_MESSAGE_MAP()
}
}
newWndProc就是窗口過(guò)程只要是該類的實(shí)例生成的窗口都使用該窗口過(guò)程。
所以了解了Windows的消息機(jī)制在加上對(duì)消息映射的理解就很容易了解MFC開(kāi)發(fā)的基本思路了。
1.4 利用MFC進(jìn)行開(kāi)發(fā)的通用方法介紹
以下是我在最初學(xué)習(xí)VC時(shí)所常用的開(kāi)發(fā)思路和方法,希望能對(duì)初學(xué)VC的朋友有所幫助和啟發(fā)。
1、開(kāi)發(fā)需要讀寫文件的應(yīng)用程序并且有簡(jiǎn)單的輸入和輸出可以利用單文檔視結(jié)構(gòu)。
2、開(kāi)發(fā)注重交互的簡(jiǎn)單應(yīng)用程序可以使用對(duì)話框?yàn)榛A(chǔ)的窗口,如果文件讀寫簡(jiǎn)單這可利用CFile進(jìn)行。
3、開(kāi)發(fā)注重交互并且文件讀寫復(fù)雜的的簡(jiǎn)單應(yīng)用程序可以利用以CFormView為基礎(chǔ)視的單文檔視結(jié)構(gòu)。
4、利用對(duì)話框得到用戶輸入的數(shù)據(jù),在等級(jí)提高后可使用就地輸入。
5、在對(duì)多文檔要求不強(qiáng)烈時(shí)盡量避免多文檔視結(jié)構(gòu),可以利用分隔條產(chǎn)生單文檔多視結(jié)構(gòu)。
6、在要求在多個(gè)文檔間傳遞數(shù)據(jù)時(shí)使用多文檔視結(jié)構(gòu)。
7、學(xué)會(huì)利用子窗口,并在自定義的子窗口包含多個(gè)控件達(dá)到封裝功能的目的。
8、盡量避免使用多文檔多視結(jié)構(gòu)。
9、不要使用多重繼承并盡量減少一個(gè)類中封裝過(guò)多的功能。
1.5 MFC中常用類,宏,函數(shù)介紹
常用類
CRect:用來(lái)表示矩形的類,擁有四個(gè)成員變量:top left bottom right。分別表是左上角和右下角的坐標(biāo)。可以通過(guò)以下的方法構(gòu)造:
CRect( int l, int t, int r, int b ); 指明四個(gè)坐標(biāo)
CRect( const RECT& srcRect ); 由RECT結(jié)構(gòu)構(gòu)造
CRect( LPCRECT lpSrcRect ); 由RECT結(jié)構(gòu)構(gòu)造
CRect( POINT point, SIZE size ); 有左上角坐標(biāo)和尺寸構(gòu)造
CRect( POINT topLeft, POINT bottomRight ); 有兩點(diǎn)坐標(biāo)構(gòu)造
下面介紹幾個(gè)成員函數(shù):
int Width( ) const; 得到寬度
int Height( ) const; 得到高度
CSize Size( ) const; 得到尺寸
CPoint& TopLeft( ); 得到左上角坐標(biāo)
CPoint& BottomRight( ); 得到右下角坐標(biāo)
CPoint CenterPoint( ) const; 得當(dāng)中心坐標(biāo)
此外矩形可以和點(diǎn)(CPoint)相加進(jìn)行位移,和另一個(gè)矩形相加得到“并”操作后的矩形。
CPoint:用來(lái)表示一個(gè)點(diǎn)的坐標(biāo),有兩個(gè)成員變量:x y。 可以和另一個(gè)點(diǎn)相加。
CString:用來(lái)表示可變長(zhǎng)度的字符串。使用CString可不指明內(nèi)存大小,CString會(huì)根據(jù)需要自行分配。下面介紹幾個(gè)成員函數(shù):
GetLength 得到字符串長(zhǎng)度
GetAt 得到指定位置處的字符
operator + 相當(dāng)于strcat
void Format( LPCTSTR lpszFormat, ... ); 相當(dāng)于sprintf
Find 查找指定字符,字符串
Compare 比較
CompareNoCase 不區(qū)分大小寫比較
MakeUpper 改為小寫
MakeLower 改為大寫
CStringArray:用來(lái)表示可變長(zhǎng)度的字符串?dāng)?shù)組。數(shù)組中每一個(gè)元素為CString對(duì)象的實(shí)例。下面介紹幾個(gè)成員函數(shù):
Add 增加CString
RemoveAt 刪除指定位置CString對(duì)象
RemoveAll 刪除數(shù)組中所有CString對(duì)象
GetAt 得到指定位置的CString對(duì)象
SetAt 修改指定位置的CString對(duì)象
InsertAt 在某一位置插入CString對(duì)象
常用宏
RGB
TRACE
ASSERT
VERIFY
常用函數(shù)
CWindApp* AfxGetApp();
HINSTANCE AfxGetInstanceHandle( );
HINSTANCE AfxGetResourceHandle( );
int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于彈出一個(gè)消息框
************************
2.1 和GUI有關(guān)的各種對(duì)象
在Windows中有各種GUI對(duì)象(不要和C++對(duì)象混淆),當(dāng)你在進(jìn)行繪圖就需要利用這些對(duì)象。而各種對(duì)象都擁有各種屬性,下面分別講述各種GUI對(duì)象和擁有的屬性。
字體對(duì)象CFont用于輸出文字時(shí)選用不同風(fēng)格和大小的字體。可選擇的風(fēng)格包括:是否為斜體,是否為粗體,字體名稱,是否有下劃線等。顏色和背景色不屬于字體的屬性。關(guān)于如何創(chuàng)建和使用字體在2.2 在窗口中輸出文字中會(huì)詳細(xì)講解。
刷子CBrush對(duì)象決定填充區(qū)域時(shí)所采用的顏色或模板。對(duì)于一個(gè)固定色的刷子來(lái)講它的屬性為顏色,是否采用網(wǎng)格和網(wǎng)格的類型如水平的,垂直的,交叉的等。你也可以利用8*8的位圖來(lái)創(chuàng)建一個(gè)自定義模板的刷子,在使用這種刷子填充時(shí)系統(tǒng)會(huì)利用位圖逐步填充區(qū)域。關(guān)于如何創(chuàng)建和使用刷子在2.3 使用刷子,筆進(jìn)行繪圖中會(huì)詳細(xì)講解。
畫筆CPen對(duì)象在畫點(diǎn)和畫線時(shí)有用。它的屬性包括顏色,寬度,線的風(fēng)格,如虛線,實(shí)線,點(diǎn)劃線等。關(guān)于如何創(chuàng)建和使用畫筆在2.3 使用刷子,筆進(jìn)行繪圖中會(huì)詳細(xì)講解。
位圖CBitmap對(duì)象可以包含一幅圖像,可以保存在資源中。關(guān)于如何使用位圖在2.4 在窗口中繪制設(shè)備相關(guān)位圖,圖標(biāo),設(shè)備無(wú)關(guān)位圖中會(huì)詳細(xì)講解。
還有一種特殊的GUI對(duì)象是多邊形,利用多邊形可以很好的限制作圖區(qū)域或是改變窗口外型。關(guān)于如何創(chuàng)建和使用多邊形在2.6 多邊形和剪貼區(qū)域中會(huì)詳細(xì)講解。
在Windows中使用GUI對(duì)象必須遵守一定的規(guī)則。首先需要?jiǎng)?chuàng)建一個(gè)合法的對(duì)象,不同的對(duì)象創(chuàng)建方法不同。然后需要將該GUI對(duì)象選入DC中,同時(shí)保存DC中原來(lái)的GUI對(duì)象。如果選入一個(gè)非法的對(duì)象將會(huì)引起異常。在使用完后應(yīng)該恢復(fù)原來(lái)的對(duì)象,這一點(diǎn)特別重要,如果保存一個(gè)臨時(shí)對(duì)象在DC中,而在臨時(shí)對(duì)象被銷毀后可能引起異常。有一點(diǎn)必須注意,每一個(gè)對(duì)象在重新創(chuàng)建前必須銷毀,下面的代碼演示了這一種安全的使用方法:
OnDraw(CDC* pDC)
{
CPen pen1,pen2;
pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//創(chuàng)建對(duì)象
pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//創(chuàng)建對(duì)象
CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//選擇對(duì)象進(jìn)DC
drawWithPen1...
(CPen*)pDC->SelectObject(&pen2);//選擇對(duì)象進(jìn)DC
drawWithPen2...
pen1.DeleteObject();//再次創(chuàng)建前先銷毀
pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次創(chuàng)建對(duì)象
(CPen*)pDC->SelectObject(&pen1);//選擇對(duì)象進(jìn)DC
drawWithPen1...
pDC->SelectObject(pOldPen);//恢復(fù)
}
此外系統(tǒng)中還擁有一些庫(kù)存GUI對(duì)象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )選入這些對(duì)象,它們包括一些固定顏色的刷子,畫筆和一些基本字體。
BLACK_BRUSH Black brush.
DKGRAY_BRUSH Dark gray brush.
GRAY_BRUSH Gray brush.
HOLLOW_BRUSH Hollow brush.
LTGRAY_BRUSH Light gray brush.
NULL_BRUSH Null brush.
WHITE_BRUSH White brush.
BLACK_PEN Black pen.
NULL_PEN Null pen.
WHITE_PEN White pen.
ANSI_FIXED_FONT ANSI fixed system font.
ANSI_VAR_FONT ANSI variable system font.
DEVICE_DEFAULT_FONT Device-dependent font.
OEM_FIXED_FONT OEM-dependent fixed font.
SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font.
SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows.
DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette.
這些對(duì)象留在DC中是安全的,所以你可以利用選入庫(kù)存對(duì)象來(lái)作為恢復(fù)DC中GUI對(duì)象。
大家可能都注意到了繪圖時(shí)都需要一個(gè)DC對(duì)象,DC(Device Context設(shè)備環(huán)境)對(duì)象是一個(gè)抽象的作圖環(huán)境,可能是對(duì)應(yīng)屏幕,也可能是對(duì)應(yīng)打印機(jī)或其它。這個(gè)環(huán)境是設(shè)備無(wú)關(guān)的,所以你在對(duì)不同的設(shè)備輸出時(shí)只需要使用不同的設(shè)備環(huán)境就行了,而作圖方式可以完全不變。這也就是Windows耀眼的一點(diǎn)設(shè)備無(wú)關(guān)性。如同你將對(duì)一幅畫使用照相機(jī)或復(fù)印機(jī)將會(huì)產(chǎn)生不同的輸出,而不需要對(duì)畫進(jìn)行任何調(diào)整。DC的使用會(huì)穿插在本章中進(jìn)行介紹。
******************
2.2 在窗口中輸出文字
在這里我假定讀者已經(jīng)利用ApplicationWizard生成了一個(gè)SDI界面的程序代碼。接下來(lái)的你只需要在CView派生類的OnDraw成員函數(shù)中加入繪圖代碼就可以了。在這里我需要解釋一下OnDraw函數(shù)的作用,OnDraw函數(shù)會(huì)在窗口需要重繪時(shí)自動(dòng)被調(diào)用,傳入的參數(shù)CDC* pDC對(duì)應(yīng)的就是DC環(huán)境。使用OnDraw的優(yōu)點(diǎn)就在于在你使用打印功能的時(shí)候傳入OnDraw的DC環(huán)境將會(huì)是打印機(jī)繪圖環(huán)境,使用打印預(yù)覽時(shí)傳入的是一個(gè)稱為CPreviewDC的繪圖環(huán)境,所以你只需要一份代碼就可以完成窗口/打印預(yù)覽/打印機(jī)繪圖三重功能。利用Windows的設(shè)備無(wú)關(guān)性和M$為打印預(yù)覽所編寫的上千行代碼你可以很容易的完成一個(gè)具有所見(jiàn)即所得的軟件。
輸出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )兩個(gè)函數(shù),對(duì)TextOut來(lái)講只能輸出單行的文字,而DrawText可以指定在一個(gè)矩形中輸出單行或多行文字,并且可以規(guī)定對(duì)齊方式和使用何種風(fēng)格。nFormat可以是多種以下標(biāo)記的組合(利用位或操作)以達(dá)到選擇輸出風(fēng)格的目的。
DT_BOTTOM底部對(duì)齊 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE.
DT_CALCRECT計(jì)算指定文字時(shí)所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text.
DT_CENTER中部對(duì)齊 Centers text horizontally.
DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified.
You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (\) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash.
DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight.
DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text.
DT_LEFT左對(duì)齊 Aligns text flush-left.
DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified.
Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override.
DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used.
DT_NOPREFIX禁止使用&前綴 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off.
DT_PATH_ELLIPSIS
DT_RIGHT右對(duì)齊 Aligns text flush-right.
DT_SINGLELINE單行輸出 Specifies single line only. Carriage returns and linefeeds do not break the line.
DT_TABSTOP設(shè)置TAB字符所占寬度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight.
DT_TOP定部對(duì)齊 Specifies top-justified text (single line only).
DT_VCENTER中部對(duì)齊 Specifies vertically centered text (single line only).
DT_WORDBREAK每行只在單詞間被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line.
在輸出文字時(shí)如果希望改變文字的顏色,你可以利用CDC::SetTextColor( COLORREF crColor )進(jìn)行設(shè)置,如果你希望改變背景色就利用CDC::SetBkColor( COLORREF crColor ),很多時(shí)候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )設(shè)置,可接受的參數(shù)有
OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode.
TRANSPARENT Background is not changed before drawing.
接下來(lái)講講如何創(chuàng)建字體,你可以創(chuàng)建的字體有兩種:庫(kù)存字體CDC::CreateStockObject( int nIndex )和自定義字體。在創(chuàng)建非庫(kù)存字體時(shí)需要填充一個(gè)LOGFONT結(jié)構(gòu)并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont )(可以參考文章在同一系統(tǒng)中顯示GB字符和BIG5字符),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其中的參數(shù)和LOGFONT中的分量有一定的對(duì)應(yīng)關(guān)系。下面分別講解參數(shù)的意義:
nHeight 字體高度(邏輯單位)等于零為缺省高度,否則取絕對(duì)值并和可用的字體高度進(jìn)行匹配。nWidth 寬度(邏輯單位)如果為零則使用可用的橫縱比進(jìn)行匹配。nEscapement 出口矢量與X軸間的角度nOrientation 字體基線與X軸間的角度nWeight 字體粗細(xì),可取以下值
Constant Value
FW_DONTCARE 0
FW_THIN 100
FW_EXTRALIGHT 200
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_NORMAL 400
FW_REGULAR 400
FW_MEDIUM 500
FW_SEMIBOLD 600
FW_DEMIBOLD 600
FW_BOLD 700
FW_EXTRABOLD 800
FW_ULTRABOLD 800
FW_BLACK 900
FW_HEAVY 900
bItalic 是否為斜體bUnderline 是否有下劃線cStrikeOut 是否帶刪除線nCharSet 指定字符集合,可取以下值
Constant Value
ANSI_CHARSET 0
DEFAULT_CHARSET 1
SYMBOL_CHARSET 2
SHIFTJIS_CHARSET 128
OEM_CHARSET 255
nOutPrecision 輸出精度
OUT_CHARACTER_PRECIS OUT_STRING_PRECIS
OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS
OUT_DEVICE_PRECIS OUT_TT_PRECIS
OUT_RASTER_PRECIS
nClipPrecision 剪輯精度,可取以下值
CLIP_CHARACTER_PRECIS CLIP_MASK
CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS
CLIP_ENCAPSULATE CLIP_TT_ALWAYS
CLIP_LH_ANGLES
nQuality 輸出質(zhì)量,可取以下值
DEFAULT_QUALITY Appearance of the font does not matter.
DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
nPitchAndFamily 字體間的間距l(xiāng)pszFacename 指定字體名稱,為了得到系統(tǒng)所擁有的字體可以利用EmunFontFamiliesEx。(可以參考文章在同一系統(tǒng)中顯示GB字符和BIG5字符)
此外可以利用CFontDialog來(lái)得到用戶選擇的字體的LOGFONT數(shù)據(jù)。
最后我講一下文本坐標(biāo)的計(jì)算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在輸出時(shí)所占用的寬度和高度,這樣就可以在手工輸出多行文字時(shí)使用正確的行距。另外如果需要更精確的對(duì)字體高度和寬度進(jìn)行計(jì)算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 該函數(shù)將會(huì)填充TEXTMETRIC結(jié)構(gòu),該結(jié)構(gòu)中的分量可以非常精確的描述字體的各種屬性。
2.3 使用點(diǎn),刷子,筆進(jìn)行繪圖
在Windows中畫點(diǎn)的方法很簡(jiǎn)單,只需要調(diào)用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定點(diǎn)畫上指定顏色,同時(shí)返回原來(lái)的顏色。COLORREF CDC::GetPixel( int x, int y)可以得到指定點(diǎn)的顏色。在Windows中應(yīng)該少使用畫點(diǎn)的函數(shù),因?yàn)檫@樣做的執(zhí)行效率比較低。
刷子和畫筆在Windows作圖中是使用最多的GUI對(duì)象,本節(jié)在講解刷子和畫筆使用方法的同時(shí)也講述一寫基本作圖函數(shù)。
在畫點(diǎn)或畫線時(shí)系統(tǒng)使用當(dāng)前DC中的畫筆,所以在創(chuàng)建畫筆后必須將其選入DC才會(huì)在繪圖時(shí)產(chǎn)生效果。畫筆可以通過(guò)CPen對(duì)象來(lái)產(chǎn)生,通過(guò)調(diào)用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )來(lái)創(chuàng)建。其中nPenStyle指名畫筆的風(fēng)格,可取如下值:
PS_SOLID 實(shí)線 Creates a solid pen.
PS_DASH 虛線,寬度必須為一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units.
PS_DOT 點(diǎn)線,寬度必須為一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOT 點(diǎn)劃線,寬度必須為一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOTDOT 雙點(diǎn)劃線,寬度必須為一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units.
PS_NULL 空線,使用時(shí)什么也不會(huì)產(chǎn)生 Creates a null pen.
PS_ENDCAP_ROUND 結(jié)束處為圓形 End caps are round.
PS_ENDCAP_SQUARE 結(jié)束處為方形 End caps are square.
nWidth和crColor為線的寬度和顏色。
刷子是在畫封閉曲線時(shí)用來(lái)填充的顏色,例如當(dāng)你畫圓形或方形時(shí)系統(tǒng)會(huì)用當(dāng)前的刷子對(duì)內(nèi)部進(jìn)行填充。刷子可利用CBrush對(duì)象產(chǎn)生。通過(guò)以下幾種函數(shù)創(chuàng)建刷子:
BOOL CreateSolidBrush( COLORREF crColor ); 創(chuàng)建一種固定顏色的刷子
BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 創(chuàng)建指定顏色和網(wǎng)格的刷子,nIndex可取以下值:
HS_BDIAGONAL Downward hatch (left to right) at 45 degrees
HS_CROSS Horizontal and vertical crosshatch
HS_DIAGCROSS Crosshatch at 45 degrees
HS_FDIAGONAL Upward hatch (left to right) at 45 degrees
HS_HORIZONTAL Horizontal hatch
HS_VERTICAL Vertical hatch
BOOL CreatePatternBrush( CBitmap* pBitmap ); 創(chuàng)建以8*8位圖為模板的刷子
在選擇了畫筆和刷子后就可以利用Windows的作圖函數(shù)進(jìn)行作圖了,基本的畫線函數(shù)有以下幾種
CDC::MoveTo( int x, int y ); 改變當(dāng)前點(diǎn)的位置
CDC::LineTo( int x, int y ); 畫一條由當(dāng)前點(diǎn)到參數(shù)指定點(diǎn)的線
CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 畫弧線
CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 將多條線依次序連接
基本的作圖函數(shù)有以下幾種:
CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形
CDC::RoundRect( LPCRECT lpRect, POINT point ); 圓角矩形
CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D邊框
CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形
CDC::Ellipse( LPCRECT lpRect ); 橢圓形
CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
CDC::Polygon( LPPOINT lpPoints, int nCount ); 多邊形
對(duì)于矩形,圓形或類似的封閉曲線,系統(tǒng)會(huì)使用畫筆繪制邊緣,使用刷子填充內(nèi)部。如果你不希望填充或是畫出邊緣,你可以選入空刷子(NULL_PEN)或是(NULL_BRUSH)空筆。
下面的代碼創(chuàng)建一條兩象素寬的實(shí)線并選入DC。并進(jìn)行簡(jiǎn)單的作圖:
{
...
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(128,128,128));
CPen* pOldPen=(CPen*)dc.SelectObject(&pen);
dc.SelectStockObject(NULL_BRUSH);//選入空刷子
dc.Rectangle(CRect(0,0,20,20));//畫矩形
...
}
2.4 在窗口中繪制設(shè)備相關(guān)位圖,圖標(biāo),設(shè)備無(wú)關(guān)位圖
在Windows中可以將預(yù)先準(zhǔn)備好的圖像復(fù)制到顯示區(qū)域中,這種內(nèi)存拷貝執(zhí)行起來(lái)是非常快的。在Windows中提供了兩種使用圖形拷貝的方法:通過(guò)設(shè)備相關(guān)位圖(DDB)和設(shè)備無(wú)關(guān)位圖(DIB)。
DDB可以用MFC中的CBitmap來(lái)表示,而DDB一般是存儲(chǔ)在資源文件中,在加載時(shí)只需要通過(guò)資源ID號(hào)就可以將圖形裝入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以裝入指定DDB,但是在繪制時(shí)必須借助另一個(gè)和當(dāng)前繪圖DC兼容的內(nèi)存DC來(lái)進(jìn)行。通過(guò)CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )繪制圖形,同時(shí)指定光柵操作的類型。BitBlt可以將源DC中位圖復(fù)制到目的DC中,其中前四個(gè)參數(shù)為目的區(qū)域的坐標(biāo),接下來(lái)是源DC指針,然后是源DC中的起始坐標(biāo),由于BitBlt為等比例復(fù)制,所以不需要再次指定長(zhǎng)寬,(StretchBlt可以進(jìn)行縮放)最后一個(gè)參數(shù)為光柵操作的類型,可取以下值:
BLACKNESS 輸出區(qū)域?yàn)楹谏?nbsp; Turns all output black.
DSTINVERT 反色輸出區(qū)域 Inverts the destination bitmap.
MERGECOPY 在源和目的間使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator.
MERGEPAINT 在反色后的目的和源間使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator.
NOTSRCCOPY 將反色后的源拷貝到目的區(qū) Copies the inverted source bitmap to the destination.
PATINVERT 源和目的間進(jìn)行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator.
SRCAND 源和目的間進(jìn)行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator.
SRCCOPY 復(fù)制源到目的區(qū) Copies the source bitmap to the destination bitmap.
SRCINVERT 源和目的間進(jìn)行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator.
SRCPAINT 源和目的間進(jìn)行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator.
WHITENESS 輸出區(qū)域?yàn)榘咨?nbsp; Turns all output white.
下面用代碼演示這種方法:
CYourView::OnDraw(CDC* pDC)
{
CDC memDC;//定義一個(gè)兼容DC
memDC.CreateCompatibleDC(pDC);//創(chuàng)建DC
CBitmap bmpDraw;
bmpDraw.LoadBitmap(ID_BMP);//裝入DDB
CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw);//保存原有DDB,
并選入新DDB入DC
pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY);//將源DC中(0,0,20,20)
復(fù)制到目的DC(0,0,20,20)
pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND);//將源DC中(0,0,20,20)
和目的DC(20,20,40,40)中區(qū)域進(jìn)行AND操作
memDC.SelectObject(pbmpOld);//選入原DDB
}
(圖標(biāo)并不是一個(gè)GDI對(duì)象,所以不需要選入DC)在MFC中沒(méi)有一個(gè)專門的圖標(biāo)類,因?yàn)閳D標(biāo)的操作比較簡(jiǎn)單,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 裝入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )繪制。由于在圖標(biāo)中可以指定透明區(qū)域,所以在某些需要使用非規(guī)則圖形而且面積不大的時(shí)候使用圖標(biāo)會(huì)比較簡(jiǎn)單。下面給出簡(jiǎn)單的代碼:
OnDraw(CDC* pDC)
{
HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1);
HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2);
pDC->DrawIcon(0,0,hIcon1);
pDC->DrawIcon(0,40,hIcon2);
DestroyIcon(hIcon1);
DestroyIcon(hIcon2);
}
同樣在MFC也沒(méi)有提供一個(gè)DIB的類,所以在使用DIB位圖時(shí)我們需要自己讀取位圖文件中的頭信息, 并讀入數(shù)據(jù),并利用API函數(shù)StretchDIBits繪制。位圖文件以BITMAPFILEHEADER結(jié)構(gòu)開(kāi)始,然后是BITMAPINFOHEADER 結(jié)構(gòu)和調(diào)色版信息和數(shù)據(jù),其實(shí)位圖格式是圖形格式中最簡(jiǎn)單的一種,而且也是Windows可以理解的一種。我不詳細(xì) 講解DIB位圖的結(jié)構(gòu),提供一個(gè)CDib類供大家使用,這個(gè)類包含了基本的功能如:Load,Save,Draw。
2.5 使用各種映射方式
所謂的映射方式簡(jiǎn)單點(diǎn)講就是坐標(biāo)的安排方式,系統(tǒng)默認(rèn)的映射方式為MM_TEXT即X坐標(biāo)向右增加,Y坐標(biāo)向下增加,(0,0)在屏幕左上方,DC中的每一點(diǎn)就是屏幕上的一個(gè)象素。也許你會(huì)認(rèn)為這種方式下是最好理解的,但是一個(gè)點(diǎn)和象素對(duì)應(yīng)的關(guān)系在屏幕上看來(lái)是正常的,但到了打印機(jī)上就會(huì)很不正常。因?yàn)槲覀冏鲌D是以點(diǎn)為單位并且打印機(jī)的分辨率遠(yuǎn)遠(yuǎn)比顯示器高(800DPI 800點(diǎn)每英寸)所以在打印機(jī)上圖形看起來(lái)就會(huì)很小。這樣就需要為打印另做一套代碼而加大了工作量。如果每個(gè)點(diǎn)對(duì)應(yīng)0.1毫米那么在屏幕上的圖形就會(huì)和打印出來(lái)的圖形一樣大小。
通過(guò)int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下幾種:
MM_HIENGLISH 每點(diǎn)對(duì)應(yīng)0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up.
MM_HIMETRIC 每點(diǎn)對(duì)應(yīng)0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up.
MM_LOENGLISH 每點(diǎn)對(duì)應(yīng)0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up.
MM_LOMETRIC 每點(diǎn)對(duì)應(yīng)0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up.
MM_TEXT 象素對(duì)應(yīng) Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down.
以上幾種映射默認(rèn)的原點(diǎn)在屏幕左上方。除MM_TEXT外都為X坐標(biāo)向右增加,Y坐標(biāo)向上增加,和自然坐標(biāo)是一致的。所以在作圖是要注意什么時(shí)候應(yīng)該使用負(fù)坐標(biāo)。而且以上的映射都是X-Y等比例的,即相同的長(zhǎng)度在X,Y軸上顯示的長(zhǎng)度都是相同的。
另外的一種映射方式為MM_ANISOTROPIC,這種方式可以規(guī)定不同的長(zhǎng)寬比例。在設(shè)置這中映射方式后必須調(diào)用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )來(lái)設(shè)定長(zhǎng)寬比例。系統(tǒng)會(huì)根據(jù)兩次設(shè)定的長(zhǎng)寬的比值來(lái)確定長(zhǎng)寬比例。下面給出一段代碼比較映射前后的長(zhǎng)寬比例:
OnDraw(CDC* pDC)
{
CRect rcC1(200,0,400,200);
pDC->FillSolidRect(rcC1,RGB(0,0,255));
pDC->SetMapMode(MM_ANISOTROPIC );
CSize sizeO;
sizeO=pDC->SetWindowExt(5,5);
TRACE("winExt %d %d\n",sizeO.cx,sizeO.cy);
sizeO=pDC->SetViewportExt(5,10);
TRACE("ViewExt %d %d\n",sizeO.cx,sizeO.cy);
CRect rcC(0,0,200,200);
pDC->FillSolidRect(rcC,RGB(0,128,0));
}
上面代碼在映射后畫出的圖形將是一個(gè)長(zhǎng)方形。
最后講講視原點(diǎn)(viewport origin),你可以通過(guò)調(diào)用CPoint CDC::SetViewportOrg( POINT point )重新設(shè)置原點(diǎn)的位置,這就相對(duì)于對(duì)坐標(biāo)進(jìn)行了位移。例如你將原點(diǎn)設(shè)置在(20,20)那么原來(lái)的(0,0)就變成了(-20,-20)。
2.6 多邊形和剪貼區(qū)域
多邊形也是一個(gè)GDI對(duì)象,同樣遵守其他GDI對(duì)象的規(guī)則,只是通常都不將其選入DC中。在MFC中多邊形有CRgn表示。多邊形用來(lái)表示一個(gè)不同與矩形的區(qū)域,和矩形具有相似的操作。如:檢測(cè)某點(diǎn)是否在內(nèi)部,并操作等。此外還得到一個(gè)包含此多邊形的最小矩形。下面介紹一下多邊形類的成員函數(shù):
CreateRectRgn 由矩形創(chuàng)建一個(gè)多邊形
CreateEllipticRgn 由橢圓創(chuàng)建一個(gè)多邊形
CreatePolygonRgn 創(chuàng)建一個(gè)有多個(gè)點(diǎn)圍成的多邊形
PtInRegion 某點(diǎn)是否在內(nèi)部
CombineRgn 兩個(gè)多邊形相并
EqualRgn 兩個(gè)多邊形是否相等
在本節(jié)中講演多邊形的意義在于重新在窗口中作圖時(shí)提高效率。因?yàn)橐l(fā)窗口重繪的原因是某個(gè)區(qū)域失效,而失效的區(qū)域用多邊形來(lái)表示。假設(shè)窗口大小為500*400當(dāng)上方的另一個(gè)窗口從(0,0,10,10)移動(dòng)到(20,20,30,30)這時(shí)(0,0,10,10)區(qū)域就失效了,而你只需要重繪這部分區(qū)域而不是所有區(qū)域,這樣你程序的執(zhí)行效率就會(huì)提高。
通過(guò)調(diào)用API函數(shù)int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效區(qū)域,但是一般用不著那么精確而只需得到包含該區(qū)域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成這一功能。
3.1 文檔 視圖 框架窗口間的關(guān)系和消息傳送規(guī)律
在MFC中M$引入了文檔-視結(jié)構(gòu)的概念,文檔相當(dāng)于數(shù)據(jù)容器,視相當(dāng)于查看數(shù)據(jù)的窗口或是和數(shù)據(jù)發(fā)生交互的窗口。(這一結(jié)構(gòu)在MFC中的OLE,ODBC開(kāi)發(fā)時(shí)又得到更多的拓展)因此一個(gè)完整的應(yīng)用一般由四個(gè)類組成:CWinApp應(yīng)用類,CFrameWnd窗口框架類,CDocument文檔類,CView視類。(VC6中支持創(chuàng)建不帶文檔-視的應(yīng)用)
在程序運(yùn)行時(shí)CWinApp將創(chuàng)建一個(gè)CFrameWnd框架窗口實(shí)例,而框架窗口將創(chuàng)建文檔模板,然后有文檔模板創(chuàng)建文檔實(shí)例和視實(shí)例,并將兩者關(guān)聯(lián)。一般來(lái)講我們只需對(duì)文檔和視進(jìn)行操作,框架的各種行為已經(jīng)被MFC安排好了而不需人為干預(yù),這也是M$設(shè)計(jì)文檔-視結(jié)構(gòu)的本意,讓我們將注意力放在完成任務(wù)上而從界面編寫中解放出來(lái)。
在應(yīng)用中一個(gè)視對(duì)應(yīng)一個(gè)文檔,但一個(gè)文檔可以包含多個(gè)視。一個(gè)應(yīng)用中只用一個(gè)框架窗口,對(duì)多文檔界面來(lái)講可能有多個(gè)MDI子窗口。每一個(gè)視都是一個(gè)子窗口,在單文檔界面中父窗口即是框架窗口,在多文檔界面中父窗口為MDI子窗口。一個(gè)多文檔應(yīng)用中可以包含多個(gè)文檔模板,一個(gè)模板定義了一個(gè)文檔和一個(gè)或多個(gè)視之間的對(duì)應(yīng)關(guān)系。同一個(gè)文檔可以屬于多個(gè)模板,但一個(gè)模板中只允許定義一個(gè)文檔。同樣一個(gè)視也可以屬于多個(gè)文檔模板。(不知道我說(shuō)清楚沒(méi)有)
接下來(lái)看看如何在程序中得到各種對(duì)象的指針:
全局函數(shù)AfxGetApp可以得到CWinApp應(yīng)用類指針
AfxGetApp()->m_pMainWnd為框架窗口指針
在框架窗口中:CFrameWnd::GetActiveDocument得到當(dāng)前活動(dòng)文檔指針
在框架窗口中:CFrameWnd::GetActiveView得到當(dāng)前活動(dòng)視指針
在視中:CView::GetDocument得到對(duì)應(yīng)的文檔指針
在文檔中:CDocument::GetFirstViewPosition,CDocument::GetNextView用來(lái)遍歷所有和文檔關(guān)聯(lián)的視。
在文檔中:CDocument::GetDocTemplate得到文檔模板指針
在多文檔界面中:CMDIFrameWnd::MDIGetActive得到當(dāng)前活動(dòng)的MDI子窗口
一般來(lái)講用戶輸入消息(如菜單選擇,鼠標(biāo),鍵盤等)會(huì)先發(fā)往視,如果視未處理則會(huì)發(fā)往框架窗口。所以定義消息映射時(shí)定義在視中就可以了,如果一個(gè)應(yīng)用同時(shí)擁有多個(gè)視而當(dāng)前活動(dòng)視沒(méi)有對(duì)消息進(jìn)行處理則消息會(huì)發(fā)往框架窗口。
3.2 接收用戶輸入
在視中接收鼠標(biāo)輸入:
鼠標(biāo)消息是我們常需要處理的消息,消息分為:鼠標(biāo)移動(dòng),按鈕按下/松開(kāi),雙擊。利用ClassWizard可以輕松的添加這幾種消息映射,下面分別講解每種消息的處理。
WM_MOUSEMOVE對(duì)應(yīng)的函數(shù)為OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了當(dāng)前一些按鍵的消息,你可以通過(guò)“位與”操作進(jìn)行檢測(cè)。
MK_CONTROL Ctrl鍵是否被按下 Set if the CTRL key is down.
MK_LBUTTON 鼠標(biāo)左鍵是否被按下 Set if the left mouse button is down.
MK_MBUTTON 鼠標(biāo)中間鍵是否被按下 Set if the middle mouse button is down.
MK_RBUTTON 鼠標(biāo)右鍵是否被按下 Set if the right mouse button is down.
MK_SHIFT Shift鍵是否被按下 Set if the SHIFT key is down
point表示當(dāng)前鼠標(biāo)的設(shè)備坐標(biāo),坐標(biāo)原點(diǎn)對(duì)應(yīng)視左上角。
WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠標(biāo)左/右鍵按下)對(duì)應(yīng)的函數(shù)為OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )參數(shù)意義和OnMouseMove相同。
WM_LBUTTONUP/WM_RBUTTONUP(鼠標(biāo)左/右鍵松開(kāi))對(duì)應(yīng)的函數(shù)為OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )參數(shù)意義和OnMouseMove相同。
WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠標(biāo)左/右鍵雙擊)對(duì)應(yīng)的函數(shù)為OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )參數(shù)意義和OnMouseMove相同。
下面我用一段偽代碼來(lái)講解一下這些消息的用法:
代碼的作用是用鼠標(biāo)拉出一個(gè)矩形
global BOOL fDowned;//是否在拉動(dòng)
global CPoint ptDown;//按下位置
global CPoint ptUp;//松開(kāi)位置
OnLButtonDown(UINT nFlags, CPoint point)
{
fDowned=TRUE;
ptUp=ptDown=point;
DrawRect();
...
}
OnMouseMove(UINT nFlags, CPoint point)
{
if(fDowned)
{
DrawRect();//恢復(fù)上次所畫的矩形
ptUp=point;
DrawRect();//畫新矩形
}
}
OnLButtonUp(UINT nFlags, CPoint point)
{
if(fDowned)
{
DrawRect();//恢復(fù)上次所畫的矩形
ptUp=point;
DrawRect();//畫新矩形
fDowned=FALSE;
}
}
DrawRect()
{//以反色屏幕的方法畫出ptDown,ptUp標(biāo)記的矩形
CClientDC dc(this);
MakeRect(ptDown,ptUp);
SetROP(NOT);
Rect();
}
坐標(biāo)間轉(zhuǎn)換:在以上的函數(shù)中point參數(shù)對(duì)應(yīng)的都是窗口的設(shè)備坐標(biāo),我們應(yīng)該將設(shè)備坐標(biāo)和邏輯坐標(biāo)相區(qū)別,在圖32_g1由于窗口使用了滾動(dòng)條,所以傳入的設(shè)備坐標(biāo)是對(duì)應(yīng)于當(dāng)前窗口左上角的坐標(biāo),沒(méi)有考慮是否滾動(dòng),而邏輯坐標(biāo)必須考慮滾動(dòng)后對(duì)應(yīng)的坐標(biāo),所以我以黃線虛擬的表達(dá)一個(gè)邏輯坐標(biāo)的區(qū)域。可以看得出同一點(diǎn)在滾動(dòng)后的坐標(biāo)值是不同的,這一規(guī)則同樣適用于改變了映射方式的窗口,假設(shè)你將映射方式設(shè)置為每點(diǎn)為0.01毫米,那么設(shè)備坐標(biāo)所對(duì)應(yīng)的邏輯坐標(biāo)也需要重新計(jì)算。進(jìn)行這種轉(zhuǎn)換需要寫一段代碼,所幸的是系統(tǒng)提供了進(jìn)行轉(zhuǎn)換的功能DC的DPtoLP,LPtoDP,下面給出代碼完成由設(shè)備坐標(biāo)到邏輯坐標(biāo)的轉(zhuǎn)換。
CPoint CYourView::FromDP(CPoint point)
{
CClientDC dc(this);
CPoint ptRet=point;
dc.PrepareDC();//必須先準(zhǔn)備DC,這在使用滾動(dòng)時(shí)讓DC重新計(jì)算坐標(biāo)
//如果你作圖設(shè)置了不同的映射方式,則在下面需要設(shè)置
dc.SetMapMode(...)
//
dc.DPtoLP(&ptRet);//DP->LP進(jìn)行轉(zhuǎn)換
return ptRet;
}
在圖32_g1中以藍(lán)線標(biāo)記的是屏幕區(qū)域,紅線標(biāo)記的客戶區(qū)域。利用ScreenToClient,ClientToScreen可以將坐標(biāo)在這兩個(gè)區(qū)域間轉(zhuǎn)換。
在視中接收鍵盤輸入:
鍵盤消息有三個(gè):鍵盤被按下/松開(kāi),輸入字符。其中輸入字符相當(dāng)于直接得到用戶輸入的字符這在不需要處理按鍵細(xì)節(jié)時(shí)使用,而鍵盤被按下/松開(kāi)在按鍵狀態(tài)改變時(shí)發(fā)送。
WM_CHAR對(duì)應(yīng)的函數(shù)為OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar為被按下的字符,nRepCnt表明在長(zhǎng)時(shí)間為松開(kāi)時(shí)相當(dāng)于的按鍵次數(shù),nFlags中的不同位代表不同的含義,在這里一般不使用。
WM_KEYDOWN/WM_KEYUP所對(duì)應(yīng)的函數(shù)為OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按鍵的虛擬碼值,如VK_ALT為ALT鍵,VK_CONTROL為Ctrl鍵。nFlags各位的含義如下:
Value Description
0? Scan code (OEM-dependent value).
8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key).
9?0 Not used.
11?2 Used internally by Windows.
13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0).
14 Previous key state (1 if the key is down before the call, 0 if the key is up).
15 Transition state (1 if the key is being released, 0 if the key is being pressed).
3.3 使用菜單
利用菜單接受用戶命令是一中很簡(jiǎn)單的交互方法,同時(shí)也是一種很有效的方法。通常菜單作為一中資源存儲(chǔ)在文件中,因此我們可以在設(shè)計(jì)時(shí)就利用資源編輯器設(shè)計(jì)好一個(gè)菜單。關(guān)于使用VC設(shè)計(jì)菜單我就不再多講了,但你在編寫菜單時(shí)應(yīng)該盡量在屬性對(duì)話框的底部提示(Prompt)處輸入文字,這雖然不是必要的,但MFC在有狀態(tài)欄和工具條的情況下會(huì)使用該文字,文字的格式為“狀態(tài)欄出說(shuō)明\n工具條提示”。圖33_g1
我們要面臨的任務(wù)是如何知道用戶何時(shí)選擇了菜單,他選的是什么菜單項(xiàng)。當(dāng)用戶選擇了一個(gè)有效的菜單項(xiàng)時(shí)系統(tǒng)會(huì)向應(yīng)用發(fā)送一個(gè)WM_COMMAND消息,在消息的參數(shù)中表明來(lái)源。在MFC中我們只需要進(jìn)行一次映射,將某一菜單ID映射到一處理函數(shù),圖33_g2。在這里我們?cè)贑View的派生類中處理菜單消息,同時(shí)我對(duì)同一ID設(shè)置兩個(gè)消息映射,接下來(lái)將這兩種映射的作用。
ON_COMMAND 映射的作用為在用戶選擇該菜單時(shí)調(diào)用指定的處理函數(shù)。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)會(huì)使菜單被選擇時(shí)調(diào)用OnCommand1成員函數(shù)。
ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜單被顯示時(shí)通過(guò)調(diào)用指定的函數(shù)來(lái)進(jìn)行確定其狀態(tài)。在這個(gè)處理函數(shù)中你可以設(shè)置菜單的允許/禁止?fàn)顟B(tài),其顯示字符串是什么,是否在前面打鉤。函數(shù)的參數(shù)為CCmdUI* pCmdUI,CCmdUI是MFC專門為更新命令提供的一個(gè)類,你可以調(diào)用
Enable 設(shè)置允許/禁止?fàn)顟B(tài)
SetCheck 設(shè)置是否在前面打鉤
SetText 設(shè)置文字
下面我講解一個(gè)例子:我在CView派生類中有一個(gè)變量m_fSelected,并且在視中處理兩個(gè)菜單的消息,當(dāng)IDM_COMMAND1被選時(shí),對(duì)m_fSelected進(jìn)行邏輯非操作,當(dāng)IDM_COMMAND2被選中時(shí)出一提示;同時(shí)IDM_COMMAND1根據(jù)m_fSelected決定菜單顯示的文字和是否在前面打上檢查符號(hào),IDM_COMMAND2根據(jù)m_fSelected的值決定菜單的允許/禁止?fàn)顟B(tài)。下面是代碼和說(shuō)明:
void CMenuDView::OnCommand1()
{
m_fSelected=!m_fSelected;
TRACE("command1 selected\n");
}
void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(m_fSelected);//決定檢查狀態(tài)
pCmdUI->SetText(m_fSelected?"當(dāng)前被選中":"當(dāng)前未被選中");//決定所顯示的文字
}
void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI)
{//決定是否為允許
pCmdUI->Enable(m_fSelected);
}
void CMenuDView::OnCommand2()
{//選中時(shí)給出提示
AfxMessageBox("你選了command2");
}
接下來(lái)再講一些通過(guò)代碼操縱菜單的方法,在MFC中有一個(gè)類CMenu用來(lái)處理和菜單有關(guān)的功能。在生成一個(gè)CMenu對(duì)象時(shí)你需要從資源中裝如菜單,通過(guò)調(diào)用BOOL CMenu::LoadMenu( UINT nIDResource )進(jìn)行裝入,然后你就可以對(duì)菜單進(jìn)行動(dòng)態(tài)的修改,所涉及到的函數(shù)有:
CMenu* GetSubMenu( int nPos ) 一位置得到子菜單的指針,因?yàn)橐粋€(gè)CMenu對(duì)象只能表示一個(gè)彈出菜單,如果菜單中的某一項(xiàng)也為彈出菜單,就需要通過(guò)該函數(shù)獲取指針。
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一項(xiàng),nFlag為MF_SEPARATOR表示增加一個(gè)分隔條,這樣其他兩個(gè)參數(shù)將會(huì)被忽略;為MF_STRING表示添加一個(gè)菜單項(xiàng)uIDNewItem為該菜單的ID命令值;為MF_POPUP表示添加一個(gè)彈出菜單項(xiàng),這時(shí)uIDNewItem為另一菜單的句柄HMENU。lpszNewItem為菜單文字說(shuō)明。
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜單,位置由變量nPosition指明。如果nFlags包含MF_BYPOSITION則表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令I(lǐng)D為nPosition的菜單處。
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜單,如果nFlags包含MF_BYPOSITION則表明修改nPosition位置的菜單,如果包含MF_BYCOMMAND表示修改命令I(lǐng)D為nPosition處的菜單。
BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于刪除某一位置的菜單。如果nFlags包含MF_BYPOSITION則表明刪除nPosition位置的菜單,如果包含MF_BYCOMMAND表示刪除命令I(lǐng)D為nPosition處的菜單。
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位圖菜單,但這樣的菜單在選中時(shí)只是反色顯示,并不美觀。(關(guān)于使用自繪OwnerDraw菜單請(qǐng)參考我翻譯的一篇文章自繪菜單類)
視圖中是沒(méi)有菜單的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到應(yīng)用的菜單指針。
最后我講一下如何在程序中彈出一個(gè)菜單,你必須先裝入一個(gè)菜單資源,你必需得到一個(gè)彈出菜單的指針然后調(diào)用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )彈出菜單,你需要指定(x,y)為菜單彈出的位置,pWnd為接收命令消息的窗口指針。下面有一段代碼說(shuō)明方法,當(dāng)然為了處理消息你應(yīng)該在pWnd指明的窗口中對(duì)菜單命令消息進(jìn)行映射。
CMenu menu;
menu.LoadMenu(IDR_POPUP);
CMenu* pM=menu.GetSubMenu(0);
CPoint pt;
GetCursorPos(&pt);
pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this);
另一種做法是通過(guò)CMenu::CreatePopupMenu()建立一個(gè)彈出菜單,然后使用TrackPopupMenu彈出菜單。使用CreatePopupMenu創(chuàng)建的菜單也可以將其作為一個(gè)彈出項(xiàng)添加另一個(gè)菜單中。下面的偽代碼演示了如何創(chuàng)建一個(gè)彈出菜單并進(jìn)行修改后彈出:
CMenu menu1,menu2;
menu1.CreatePopupMenu
menu1.InsertMenu(1)
menu1.InsertMenu(2)
menu1.InsertMenu(3)
menu2.CreatePopupMenu
menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 將彈出菜單加入 or InsertMenu...
menu2.InsertMenu("string desc");
menu.TrackPopupMenu(...)
3.4 文檔,視,框架之間相互作用
一般來(lái)說(shuō)用戶的輸入/輸出基本都是通過(guò)視進(jìn)行,但一些例外的情況下可能需要和框架直接發(fā)生作用,而在多視的情況下如何在視之間傳遞數(shù)據(jù)。
在使用菜單時(shí)大家會(huì)發(fā)現(xiàn)當(dāng)一個(gè)菜單沒(méi)有進(jìn)行映射處理時(shí)為禁止?fàn)顟B(tài),在多視的情況下菜單的狀態(tài)和處理映射是和當(dāng)前活動(dòng)視相聯(lián)系的,這樣MFC可以保證視能正確的接收到各種消息,但有時(shí)候也會(huì)產(chǎn)生不便。有一個(gè)解決辦法就是在框架中對(duì)消息進(jìn)行處理,這樣也可以保證當(dāng)前文檔可以通過(guò)框架得到當(dāng)前消息。
在用戶進(jìn)行輸入后如何使視的狀態(tài)得到更新?這個(gè)問(wèn)題在一個(gè)文檔對(duì)應(yīng)一個(gè)視圖時(shí)是不存在的,但是現(xiàn)在有一個(gè)文檔對(duì)應(yīng)了兩個(gè)視圖,當(dāng)在一個(gè)視上進(jìn)行了輸入時(shí)如何保證另一個(gè)視也得到通知呢?MFC的做法是利用文檔來(lái)處理的,因?yàn)槲臋n管理著當(dāng)前和它聯(lián)系的視,由它來(lái)通知各個(gè)視是最合適的。讓我們同時(shí)看兩個(gè)函數(shù):
void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL )
當(dāng)文檔的UpdateAllViews被調(diào)用時(shí)和此文檔相關(guān)的所有視的OnUpdate都會(huì)被調(diào)用,而參數(shù)lHint和pHint都會(huì)被傳遞。這樣一來(lái)發(fā)生改變視就可以通知其他的兄弟了。那么還有一個(gè)問(wèn)題:如何在OnUpdate中知道是那個(gè)視圖發(fā)生了改變呢,這就可以利用pHint參數(shù),只要調(diào)用者將this指針賦值給參數(shù)就可以了,當(dāng)然完全可以利用該參數(shù)傳遞更復(fù)雜的結(jié)構(gòu)。
視的初始化,當(dāng)一個(gè)文檔被打開(kāi)或是新建一個(gè)文檔時(shí)視圖的CView::OnInitialUpdate()會(huì)被調(diào)用,你可以通過(guò)重載該函數(shù)對(duì)視進(jìn)行初始化,并在結(jié)束前調(diào)用父類的OnInitialUpdate,因?yàn)檫@樣可以保證OnUpdate會(huì)被調(diào)用。
文檔中內(nèi)容的清除,當(dāng)文檔被關(guān)閉時(shí)(比如退出或是新建前上一個(gè)文檔清除)void CDocument::DeleteContents ()會(huì)被調(diào)用,你可以通過(guò)重載該函數(shù)來(lái)進(jìn)行清理工作。
在單文檔結(jié)構(gòu)中上面兩點(diǎn)尤其重要,因?yàn)檐浖\(yùn)行文檔對(duì)象和視對(duì)象只會(huì)被產(chǎn)生并刪除一次。所以應(yīng)該將上面兩點(diǎn)和C++對(duì)象構(gòu)造和構(gòu)析分清楚。
最后將一下文檔模板(DocTemplate)的作用,文檔模板分為兩類單文檔模板和多文檔模板,分別由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于記錄文檔,視,框架之間的對(duì)應(yīng)關(guān)系。還有一點(diǎn)就是模板可以記錄應(yīng)用程序可以打開(kāi)的文件的類型,當(dāng)打開(kāi)文件時(shí)會(huì)根據(jù)文檔模板中的信息選擇正確的文檔和視。模板是一個(gè)比較抽想的概念,一般來(lái)說(shuō)是不需要我們直接進(jìn)行操作的。
當(dāng)使用者通過(guò)視修改了數(shù)據(jù)時(shí),應(yīng)該調(diào)用GetDocument()->SetModifiedFlag(TRUE)通知文檔數(shù)據(jù)已經(jīng)被更新,這樣在關(guān)閉文檔時(shí)會(huì)自動(dòng)詢問(wèn)用戶是否保存數(shù)據(jù)。
好象這一節(jié)講的有些亂,大家看后有什么想法和問(wèn)題請(qǐng)?jiān)赩CHelp論壇上留言,我會(huì)盡快回復(fù)并且會(huì)對(duì)本節(jié)內(nèi)容重新整理和修改。
3.5 利用序列化進(jìn)行文件讀寫
在很多應(yīng)用中我們需要對(duì)數(shù)據(jù)進(jìn)行保存,或是從介質(zhì)上讀取數(shù)據(jù),這就涉及到文件的操作。我們可以利用各種文件存取方法完成這些工作,但MFC中也提供了一種讀寫文件的簡(jiǎn)單方法——“序列化”。序列化機(jī)制通過(guò)更高層次的接口功能向開(kāi)發(fā)者提供了更利于使用和透明于字節(jié)流的文件操縱方法,舉一個(gè)例來(lái)講你可以將一個(gè)字串寫入文件而不需要理會(huì)具體長(zhǎng)度,讀出時(shí)也是一樣。你甚至可以對(duì)字符串?dāng)?shù)組進(jìn)行操作。在MFC提供的可自動(dòng)分配內(nèi)存的類的支持下你可以更輕松的讀/寫數(shù)據(jù)。你也可以根據(jù)需要編寫你自己的具有序列化功能的類。
序列化在最低的層次上應(yīng)該被需要序列化的類支持,也就是說(shuō)如果你需要對(duì)一個(gè)類進(jìn)行序列化,那么這個(gè)類必須支持序列化。當(dāng)通過(guò)序列化進(jìn)行文件讀寫時(shí)你只需要該類的序列化函數(shù)就可以了。
怎樣使類具有序列化功能呢?你需要以下的工作:
該類從CObject派生。
在類聲明中包括DECLARE_SERIAL宏定義。
提供一個(gè)缺省的構(gòu)造函數(shù)。
在類中實(shí)現(xiàn)Serialze函數(shù)
使用IMPLEMENT_SERIAL指明類名和版本號(hào)
下面的代碼建立了一個(gè)簡(jiǎn)單身份證記錄的類,同時(shí)也能夠支持序列化。
in H
struct strPID
{
char szName[10];
char szID[16];
struct strPID* pNext;
};
class CAllPID : public CObject
{
public:
DECLARE_SERIAL(CAllPID)
CAllPID();
~CAllPID();
public:// 序列化相關(guān)
struct strPID* pHead;
//其他的成員函數(shù)
void Serialize(CArchive& ar);
};
in CPP
IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于讀數(shù)據(jù)時(shí)的檢測(cè)
void CAllPID::Serialize(CArchive& ar)
{
int iTotal;
if(ar.IsStoring())
{//保存數(shù)據(jù)
iTotal=GetTotalID();//得到鏈表中的記錄數(shù)量
arr<26;i++)
ar<<&(((BYTE*)pItem)+i);//寫一個(gè)strPID中所有的數(shù)據(jù)
}
}
else
{//讀數(shù)據(jù)
ar>>iTotal;
for(int i=0;i26;j++)
ar>>*(((BYTE*)pID)+j);//讀一個(gè)strPID中所有的數(shù)據(jù)
//修改鏈表
}
}
}
當(dāng)然上面的代碼很不完整,但已經(jīng)可以說(shuō)明問(wèn)題。這樣CAllPID就是一個(gè)可以支持序列化的類,并且可以根據(jù)記錄的數(shù)量動(dòng)態(tài)分配內(nèi)存。在序列化中我們使用了CArchive類,該類用于在序列化時(shí)提供讀寫支持,它重載了<<和>>運(yùn)算符號(hào),并且提供Read和Write函數(shù)對(duì)數(shù)據(jù)進(jìn)行讀寫。
下面看看如何在文檔中使用序列化功能,你只需要修改文檔類的Serialize(CArchive& ar)函數(shù),并調(diào)用各個(gè)進(jìn)行序列化的類的Serial進(jìn)行數(shù)據(jù)讀寫就可以了。當(dāng)然你也可以在文檔類的內(nèi)部進(jìn)行數(shù)據(jù)讀寫,下面的代碼利用序列化功能讀寫數(shù)據(jù):
class CYourDoc : public CDocument
{
void Serialize(CArchive& ar);
CString m_szDesc;
CAllPID m_allPID;
......
}
void CYourDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{//由于CString對(duì)CArchive定義了<<和>>操作符號(hào),所以可以直接利用>>和<<
ar<>m_szDesc;
}
m_allPID.Serialize(ar);//調(diào)用數(shù)據(jù)類的序列化函數(shù)
}
3.6 MFC中所提供的各種視類介紹
MFC中提供了豐富的視類供開(kāi)發(fā)者使用,下面對(duì)各個(gè)類進(jìn)行介紹:
CView類是最基本的視類只支持最基本的操作。
CScrollView類提供了滾動(dòng)的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )設(shè)置滾動(dòng)尺寸,和坐標(biāo)映射模式。但是在繪圖和接收用戶輸入時(shí)需要對(duì)坐標(biāo)進(jìn)行轉(zhuǎn)換。請(qǐng)參見(jiàn)3.2 接收用戶輸入。
CFormView類提供用戶在資源文件中定義界面的能力,并可以將子窗口和變量進(jìn)行綁定。通過(guò)UpdateData函數(shù)讓數(shù)據(jù)在變量和子窗口間交換。
CTreeView類利用TreeCtrl界面作為視界面,通過(guò)調(diào)用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。
CListView類利用ListCtrl界面作為視界面,通過(guò)調(diào)用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。
CEditView類利用Edit接收用戶輸入,它具有輸入框的一切功能。通過(guò)調(diào)用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以設(shè)置打印字體。
CRichEditView類作為Rich Text Edit(富文本輸入)的視類,提供了可以按照格式顯示文本的能力,在使用時(shí)需要CRichEditDoc的支持。
*****************************
4.1 Button
按鈕窗口(控件)在MFC中使用CButton表示,CButton包含了三種樣式的按鈕,Push Button,Check Box,Radio Box。所以在利用CButton對(duì)象生成按鈕窗口時(shí)需要指明按鈕的風(fēng)格。
創(chuàng)建按鈕:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中l(wèi)pszCaption是按鈕上顯示的文字,dwStyle為按鈕風(fēng)格,除了Windows風(fēng)格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)還有按鈕專用的一些風(fēng)格。
BS_AUTOCHECKBOX 檢查框,按鈕的狀態(tài)會(huì)自動(dòng)改變 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box.
BS_AUTORADIOBUTTON 圓形選擇按鈕,按鈕的狀態(tài)會(huì)自動(dòng)改變 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.
BS_AUTO3STATE 允許按鈕有三種狀態(tài)即:選中,未選中,未定 Same as a three-state check box, except that the box changes its state when the user selects it.
BS_CHECKBOX 檢查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style).
BS_DEFPUSHBUTTON 默認(rèn)普通按鈕 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).
BS_LEFTTEXT 左對(duì)齊文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box.
BS_OWNERDRAW 自繪按鈕 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class.
BS_PUSHBUTTON 普通按鈕 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.
BS_RADIOBUTTON 圓形選擇按鈕 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices.
BS_3STATE 允許按鈕有三種狀態(tài)即:選中,未選中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled.
rect為窗口所占據(jù)的矩形區(qū)域,pParentWnd為父窗口指針,nID為該窗口的ID值。
獲取/改變按鈕狀態(tài):對(duì)于檢查按鈕和圓形按鈕可能有兩種狀態(tài),選中和未選中,如果設(shè)置了BS_3STATE或BS_AUTO3STATE風(fēng)格就可能出現(xiàn)第三種狀態(tài):未定,這時(shí)按鈕顯示灰色。通過(guò)調(diào)用int CButton::GetCheck( ) 得到當(dāng)前是否被選中,返回0:未選中,1:選中,2:未定。調(diào)用void CButton::SetCheck( int nCheck );設(shè)置當(dāng)前選中狀態(tài)。
處理按鈕消息:要處理按鈕消息需要在父窗口中進(jìn)行消息映射,映射宏為ON_BN_CLICKED( id, memberFxn )id為按鈕的ID值,就是創(chuàng)建時(shí)指定的nID值。處理函數(shù)原型為afx_msg void memberFxn( );
4.2 Static Box
靜態(tài)文本控件的功能比較簡(jiǎn)單,可作為顯示字符串,圖標(biāo),位圖用。創(chuàng)建一個(gè)窗口可以使用成員函數(shù):
BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle將指明該窗口的風(fēng)格,除了子窗口常用的風(fēng)格WS_CHILD,WS_VISIBLE外,你可以針對(duì)靜態(tài)控件指明專門的風(fēng)格。
SS_CENTER,SS_LEFT,SS_RIGHT 指明字符顯示的對(duì)齊方式。
SS_GRAYRECT 顯示一個(gè)灰色的矩形
SS_NOPREFIX 如果指明該風(fēng)格,對(duì)于字符&將直接顯示,否則&將作為轉(zhuǎn)義符,&將不顯示而在其后的字符將有下劃線,如果需要直接顯示&必須使用&&表示。
SS_BITMAP 顯示位圖
SS_ICON 顯示圖標(biāo)
SS_CENTERIMAGE 圖象居中顯示
控制顯示的文本利用成員函數(shù)SetWindowText/GetWindowText用于設(shè)置/得到當(dāng)前顯示的文本。
控制顯示的圖標(biāo)利用成員函數(shù)SetIcon/GetIcon用于設(shè)置/得到當(dāng)前顯示的圖標(biāo)。
控制顯示的位圖利用成員函數(shù)SetBitmap/GetBitmap用于設(shè)置/得到當(dāng)前顯示的位圖。下面一段代碼演示如何創(chuàng)建一個(gè)顯示位圖的靜態(tài)窗口并設(shè)置位圖
CStatic* pstaDis=new CStatic;
pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE,
CRect(0,0,40,40),pWnd,1);
CBitmap bmpLoad;
bmpLoad.LoadBitmap(IDB_TEST);
pstaDis->SetBitmap(bmpLoad.Detach());
4.3 Edit Box
Edit窗口是用來(lái)接收用戶輸入最常用的一個(gè)控件。創(chuàng)建一個(gè)輸入窗口可以使用成員函數(shù):
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle將指明該窗口的風(fēng)格,除了子窗口常用的風(fēng)格WS_CHILD,WS_VISIBLE外,你可以針對(duì)輸入控件指明專門的風(fēng)格。
ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明輸入文字超出顯示范圍時(shí)自動(dòng)滾動(dòng)。
ES_CENTER,ES_LEFT,ES_RIGHT 指定對(duì)齊方式
ES_MULTILINE 是否允許多行輸入
ES_PASSWORD 是否為密碼輸入框,如果指明該風(fēng)格則輸入的文字顯示為*
ES_READONLY 是否為只讀
ES_UPPERCASE,ES_LOWERCASE 顯示大寫/小寫字符
控制顯示的文本利用成員函數(shù)SetWindowText/GetWindowText用于設(shè)置/得到當(dāng)前顯示的文本。
通過(guò)GetLimitText/SetLimitText可以得到/設(shè)置在輸入框中輸入的字符數(shù)量。
由于在輸入時(shí)用戶可能選擇某一段文本,所以通過(guò)void CEdit::GetSel( int& nStartChar, int& nEndChar )得到用戶選擇的字符范圍,通過(guò)調(diào)用void CEdit::SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE )可以設(shè)置當(dāng)前選擇的文本范圍,如果指定nStartChar=0 nEndChar=-1則表示選中所有的文本。void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE )可以將選中的文本替換為指定的文字。
此外輸入框還有一些和剪貼板有關(guān)的功能,void Clear( );刪除選中的文本,void Copy( );可將選中的文本送入剪貼板,void Paste( );將剪貼板中內(nèi)容插入到當(dāng)前輸入框中光標(biāo)位置,void Cut( );相當(dāng)于Copy和Clear結(jié)合使用。
最后介紹一下輸入框幾種常用的消息映射宏:
ON_EN_CHANGE 輸入框中文字更新后產(chǎn)生
ON_EN_ERRSPACE 輸入框無(wú)法分配內(nèi)存時(shí)產(chǎn)生
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點(diǎn)時(shí)產(chǎn)生
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數(shù),并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對(duì)話框中使用輸入框,Class Wizard會(huì)自動(dòng)列出相關(guān)的消息,并能自動(dòng)產(chǎn)生消息映射代碼。
4.4 Scroll Bar
Scroll Bar一般不會(huì)單獨(dú)使用,因?yàn)镾pinCtrl可以取代滾動(dòng)條的一部分作用,但是如果你需要自己生成派生窗口,滾動(dòng)條還是會(huì)派上一些用場(chǎng)。創(chuàng)建一個(gè)滾動(dòng)條可以使用成員函數(shù): : BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle將指明該窗口的風(fēng)格,除了子窗口常用的風(fēng)格WS_CHILD,WS_VISIBLE外,你可以針對(duì)滾動(dòng)條指明專門的風(fēng)格。
SBS_VERT 風(fēng)格將創(chuàng)建一個(gè)垂直的滾動(dòng)條。
SBS_HORZ 風(fēng)格將創(chuàng)建一個(gè)水平的滾動(dòng)條。
在創(chuàng)建滾動(dòng)條后需要調(diào)用void SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE )設(shè)置滾動(dòng)范圍, int GetScrollPos( )/int SetScrollPos( )用來(lái)得到和設(shè)置當(dāng)前滾動(dòng)條的位置。
void ShowScrollBar( BOOL bShow = TRUE );用來(lái)顯示/隱藏滾動(dòng)條。
BOOL EnableScrollBar( UINT nArrowFlags = ESB_ENABLE_BOTH )用來(lái)設(shè)置滾動(dòng)條上箭頭是否為允許狀態(tài)。nArrowFlags可取以下值:
ESB_ENABLE_BOTH 兩個(gè)箭頭都為允許狀態(tài)
ESB_DISABLE_LTUP 上/左箭頭為禁止?fàn)顟B(tài)
ESB_DISABLE_RTDN 下/右箭頭為禁止?fàn)顟B(tài)
ESB_DISABLE_BOTH 兩個(gè)箭頭都為禁止?fàn)顟B(tài)
如果需要在滾動(dòng)條位置被改變時(shí)得到通知,需要在父窗口中定義對(duì)消息WM_VSCROLL/WM_HSCROLL的映射。方法為在父窗口類中重載 afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )/afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar ) 所使用的消息映射宏為:ON_WM_VSCROLL( ),ON_WM_HSCROLL( ),在映射宏中不需要指明滾動(dòng)條的ID,因?yàn)樗袧L動(dòng)條的滾動(dòng)消息都由同樣的函數(shù)處理。在OnHScroll/OnVScroll的第三個(gè)參數(shù)會(huì)指明當(dāng)前滾動(dòng)條的指針。第一個(gè)參數(shù)表示滾動(dòng)條上發(fā)生的動(dòng)作,可取以下值:
SB_TOP/SB_BOTTOM 已滾動(dòng)到頂/底部
SB_LINEUP/SB_LINEDOWN 向上/下滾動(dòng)一行
SB_PAGEDOWN/SB_PAGEUP 向上/下滾動(dòng)一頁(yè)
SB_THUMBPOSITION/SB_THUMBTRACK 滾動(dòng)條拖動(dòng)到某一位置,參數(shù)nPos指明當(dāng)前位置(參數(shù)nPos在其它的情況下是無(wú)效的)
SB_ENDSCROLL 滾動(dòng)條拖動(dòng)完成(用戶松開(kāi)鼠標(biāo))
4.5 List Box/Check List Box
ListBox窗口用來(lái)列出一系列的文本,每條文本占一行。創(chuàng)建一個(gè)列表窗口可以使用成員函數(shù):
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle將指明該窗口的風(fēng)格,除了子窗口常用的風(fēng)格WS_CHILD,WS_VISIBLE外,你可以針對(duì)列表控件指明專門的風(fēng)格。
LBS_MULTIPLESEL 指明列表框可以同時(shí)選擇多行
LBS_EXTENDEDSEL 可以通過(guò)按下Shift/Ctrl鍵選擇多行
LBS_SORT 所有的行按照字母順序進(jìn)行排序
在列表框生成后需要向其中加入或是刪除行,可以利用:
int AddString( LPCTSTR lpszItem )添加行,
int DeleteString( UINT nIndex )刪除指定行,
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。
void ResetContent( )可以刪除列表框中所有行。
通過(guò)調(diào)用int GetCount( )得到當(dāng)前列表框中行的數(shù)量。
如果需要得到/設(shè)置當(dāng)前被選中的行,可以調(diào)用int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明了選擇多行的風(fēng)格,你就需要先調(diào)用int GetSelCount( )得到被選中的行的數(shù)量,然后int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有選中的行,參數(shù)rgIndex為存放被選中行的數(shù)組。通過(guò)調(diào)用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內(nèi)指定行的字符串。
此外通過(guò)調(diào)用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當(dāng)前所有行中查找指定的字符傳的位置,nStartAfter指明從那一行開(kāi)始進(jìn)行查找。
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字符串的行。
在MFC 4.2版本中添加了CCheckListBox類,該類是由CListBox派生并擁有CListBox的所有功能,不同的是可以在每行前加上一個(gè)檢查框。必須注意的是在創(chuàng)建時(shí)必須指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE風(fēng)格。
通過(guò)void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以設(shè)置/得到檢查框的風(fēng)格,關(guān)于檢查框風(fēng)格可以參考4.1 Button中介紹。通過(guò)void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以設(shè)置和得到某行的檢查狀態(tài),關(guān)于檢查框狀態(tài)可以參考4.1 Button中介紹。
最后介紹一下列表框幾種常用的消息映射宏:
ON_LBN_DBLCLK 鼠標(biāo)雙擊
ON_EN_ERRSPACE 輸入框無(wú)法分配內(nèi)存時(shí)產(chǎn)生
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點(diǎn)時(shí)產(chǎn)生
ON_LBN_SELCHANGE 選擇的行發(fā)生改變
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數(shù),并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對(duì)話框中使用列表框,Class Wizard會(huì)自動(dòng)列出相關(guān)的消息,并能自動(dòng)產(chǎn)生消息映射代碼。
4.6 Combo Box
組合窗口是由一個(gè)輸入框和一個(gè)列表框組成。創(chuàng)建一個(gè)組合窗口可以使用成員函數(shù):
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle將指明該窗口的風(fēng)格,除了子窗口常用的風(fēng)格WS_CHILD,WS_VISIBLE外,你可以針對(duì)列表控件指明專門的風(fēng)格。
CBS_DROPDOWN 下拉式組合框
CBS_DROPDOWNLIST 下拉式組合框,但是輸入框內(nèi)不能進(jìn)行輸入
CBS_SIMPLE 輸入框和列表框同時(shí)被顯示
LBS_SORT 所有的行按照字母順序進(jìn)行排序
由于組合框內(nèi)包含了列表框,所以列表框的功能都能夠使用,如可以利用:
int AddString( LPCTSTR lpszItem )添加行,
int DeleteString( UINT nIndex )刪除指定行,
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。
void ResetContent( )可以刪除列表框中所有行。
通過(guò)調(diào)用int GetCount( )得到當(dāng)前列表框中行的數(shù)量。
如果需要得到/設(shè)置當(dāng)前被選中的行的位置,可以調(diào)用int GetCurSel( )/int SetCurSel(int iIndex)。通過(guò)調(diào)用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內(nèi)指定行的字符串。
此外通過(guò)調(diào)用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當(dāng)前所有行中查找指定的字符傳的位置,nStartAfter指明從那一行開(kāi)始進(jìn)行查找。
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字符串的行。
此外輸入框的功能都能夠使用,如可以利用:
DWORD GetEditSel( ) /BOOL SetEditSel( int nStartChar, int nEndChar )得到或設(shè)置輸入框中被選中的字符位置。
BOOL LimitText( int nMaxChars )設(shè)置輸入框中可輸入的最大字符數(shù)。
輸入框的剪貼板功能Copy,Clear,Cut,Paste動(dòng)可以使用。
最后介紹一下列表框幾種常用的消息映射宏:
ON_CBN_DBLCLK 鼠標(biāo)雙擊
ON_CBN_DROPDOWN 列表框被彈出
ON_CBN_KILLFOCUS / ON_CBN_SETFOCUS 在輸入框失去/得到輸入焦點(diǎn)時(shí)產(chǎn)生
ON_CBN_SELCHANGE 列表框中選擇的行發(fā)生改變
ON_CBN_EDITUPDATE 輸入框中內(nèi)容被更新
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數(shù),并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對(duì)話框中使用組合框,Class Wizard會(huì)自動(dòng)列出相關(guān)的消息,并能自動(dòng)產(chǎn)生消息映射代碼。
4.7 Tree Ctrl
樹(shù)形控件TreeCtrl和下節(jié)要講的列表控件 ListCtrl在系統(tǒng)中大量被使用,例如Windows資源管理器就是一個(gè)典型的例子。
樹(shù)形控件可以用于樹(shù)形的結(jié)構(gòu),其中有一個(gè)根接點(diǎn)(Root)然后下面有許多子結(jié)點(diǎn),而每個(gè)子結(jié)點(diǎn)上有允許有一個(gè)或多個(gè)或沒(méi)有子結(jié)點(diǎn)。MFC中使用CTreeCtrl類來(lái)封裝樹(shù)形控件的各種操作。通過(guò)調(diào)用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創(chuàng)建一個(gè)窗口,dwStyle中可以使用以下一些樹(shù)形控件的專用風(fēng)格:
TVS_HASLINES 在父/子結(jié)點(diǎn)之間繪制連線
TVS_LINESATROOT 在根/子結(jié)點(diǎn)之間繪制連線
TVS_HASBUTTONS 在每一個(gè)結(jié)點(diǎn)前添加一個(gè)按鈕,用于表示當(dāng)前結(jié)點(diǎn)是否已被展開(kāi)
TVS_EDITLABELS 結(jié)點(diǎn)的顯示字符可以被編輯
TVS_SHOWSELALWAYS 在失去焦點(diǎn)時(shí)也顯示當(dāng)前選中的結(jié)點(diǎn)
TVS_DISABLEDRAGDROP 不允許Drag/Drop
TVS_NOTOOLTIPS 不使用ToolTip顯示結(jié)點(diǎn)的顯示字符
在樹(shù)形控件中每一個(gè)結(jié)點(diǎn)都有一個(gè)句柄(HTREEITEM),同時(shí)添加結(jié)點(diǎn)時(shí)必須提供的參數(shù)是該結(jié)點(diǎn)的父結(jié)點(diǎn)句柄,(其中根Root結(jié)點(diǎn)只有一個(gè),既不可以添加也不可以刪除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一個(gè)結(jié)點(diǎn),pszItem為顯示的字符,hParent代表父結(jié)點(diǎn)的句柄,當(dāng)前添加的結(jié)點(diǎn)會(huì)排在hInsertAfter表示的結(jié)點(diǎn)的后面,返回值為當(dāng)前創(chuàng)建的結(jié)點(diǎn)的句柄。下面的代碼會(huì)建立一個(gè)如下形式的樹(shù)形結(jié)構(gòu): +--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假設(shè)m_tree為一個(gè)CTreeCtrl對(duì)象,而且該窗口已經(jīng)創(chuàng)建*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根結(jié)點(diǎn)上添加Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上添加一個(gè)子結(jié)點(diǎn) hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一個(gè)子結(jié)點(diǎn),排在Child1_1后面 hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem); hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem); hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem); 如果你希望在每個(gè)結(jié)點(diǎn)前添加一個(gè)小圖標(biāo),就必需先調(diào)用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明當(dāng)前所使用的ImageList,nImageListType為TVSIL_NORMAL。在調(diào)用完成后控件中使用圖片以設(shè)置的ImageList中圖片為準(zhǔn)。然后調(diào)用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加結(jié)點(diǎn),nImage為結(jié)點(diǎn)沒(méi)被選中時(shí)所使用圖片序號(hào),nSelectedImage為結(jié)點(diǎn)被選中時(shí)所使用圖片序號(hào)。下面的代碼演示了ImageList的設(shè)置。 /*m_list 為CImageList對(duì)象 IDB_TREE 為16*(16*4)的位圖,每個(gè)圖片為16*16共4個(gè)圖標(biāo)*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1);//添加, 選中時(shí)顯示圖標(biāo)1,未選中時(shí)顯示圖標(biāo)0
此外CTreeCtrl還提供了一些函數(shù)用于得到/修改控件的狀態(tài)。
HTREEITEM GetSelectedItem( );將返回當(dāng)前選中的結(jié)點(diǎn)的句柄。BOOL SelectItem( HTREEITEM hItem );將選中指明結(jié)點(diǎn)。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某結(jié)點(diǎn)所使用圖標(biāo)索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一結(jié)點(diǎn)的顯示字符。
BOOL DeleteItem( HTREEITEM hItem );用于刪除某一結(jié)點(diǎn),BOOL DeleteAllItems( );將刪除所有結(jié)點(diǎn)。
此外如果想遍歷樹(shù)可以使用下面的函數(shù):
HTREEITEM GetRootItem( );得到根結(jié)點(diǎn)。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結(jié)點(diǎn)。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結(jié)點(diǎn)的上/下一個(gè)兄弟結(jié)點(diǎn)。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結(jié)點(diǎn)。
樹(shù)形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產(chǎn)生該消息的窗口ID,memberFxn為處理函數(shù),函數(shù)的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數(shù)據(jù)結(jié)構(gòu),在具體使用時(shí)需要轉(zhuǎn)換成其他類型的結(jié)構(gòu)。對(duì)于樹(shù)形控件可能取值和對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:
TVN_SELCHANGED 在所選中的結(jié)點(diǎn)發(fā)生改變后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_ITEMEXPANDED 在某結(jié)點(diǎn)被展開(kāi)后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_BEGINLABELEDIT 在開(kāi)始編輯結(jié)點(diǎn)字符時(shí)發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_ENDLABELEDIT 在結(jié)束編輯結(jié)點(diǎn)字符時(shí)發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_GETDISPINFO 在需要得到某結(jié)點(diǎn)信息時(shí)發(fā)送,(如得到結(jié)點(diǎn)的顯示字符)所用結(jié)構(gòu):NMTVDISPINFO
關(guān)于ON_NOTIFY有很多內(nèi)容,將在以后的內(nèi)容中進(jìn)行詳細(xì)講解。
關(guān)于動(dòng)態(tài)提供結(jié)點(diǎn)所顯示的字符:首先你在添加結(jié)點(diǎn)時(shí)需要指明lpszItem參數(shù)為:LPSTR_TEXTCALLBACK。在控件顯示該結(jié)點(diǎn)時(shí)會(huì)通過(guò)發(fā)送TVN_GETDISPINFO來(lái)取得所需要的字符,在處理該消息時(shí)先將參數(shù)pNMHDR轉(zhuǎn)換為L(zhǎng)PNMTVDISPINFO,然后填充其中item.pszText。但是我們通過(guò)什么來(lái)知道該結(jié)點(diǎn)所對(duì)應(yīng)的信息呢,我的做法是在添加結(jié)點(diǎn)后設(shè)置其lParam參數(shù),然后在提供信息時(shí)利用該參數(shù)來(lái)查找所對(duì)應(yīng)的信息。下面的代碼說(shuō)明了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結(jié)點(diǎn) HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 0 ); hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 1 ); //處理消息 void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; pTVDI->item.pszText=szOut[pTVDI->item.lParam];//通過(guò)lParam得到 需要顯示的字符在數(shù)組中的位置 *pResult = 0; }
關(guān)于編輯結(jié)點(diǎn)的顯示字符:首先需要設(shè)置樹(shù)形控件的TVS_EDITLABELS風(fēng)格,在開(kāi)始編輯時(shí)該控件將會(huì)發(fā)送TVN_BEGINLABELEDIT,你可以通過(guò)在處理函數(shù)中返回TRUE來(lái)取消接下來(lái)的編輯,在編輯完成后會(huì)發(fā)送TVN_ENDLABELEDIT,在處理該消息時(shí)需要將參數(shù)pNMHDR轉(zhuǎn)換為L(zhǎng)PNMTVDISPINFO,然后通過(guò)其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說(shuō)明如何處理這些消息: //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.lParam==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.pszText==NULL);//判斷是否已經(jīng)取消取消編輯 m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進(jìn)行的消息映射必須在父窗口中進(jìn)行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。
4.8 List Ctrl
列表控件可以看作是功能增強(qiáng)的ListBox,它提供了四種風(fēng)格,而且可以同時(shí)顯示一列的多中屬性值。MFC中使用CListCtrl類來(lái)封裝列表控件的各種操作。通過(guò)調(diào)用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創(chuàng)建一個(gè)窗口,dwStyle中可以使用以下一些列表控件的專用風(fēng)格:
LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 這四種風(fēng)格決定控件的外觀,同時(shí)只可以選擇其中一種,分別對(duì)應(yīng):大圖標(biāo)顯示,小圖標(biāo)顯示,列表顯示,詳細(xì)報(bào)表顯示
LVS_EDITLABELS 結(jié)點(diǎn)的顯示字符可以被編輯,對(duì)于報(bào)表風(fēng)格來(lái)講可編輯的只為第一列。
LVS_SHOWSELALWAYS 在失去焦點(diǎn)時(shí)也顯示當(dāng)前選中的結(jié)點(diǎn)
LVS_SINGLESEL 同時(shí)只能選中列表中一項(xiàng)
首先你需要設(shè)置列表控件所使用的ImageList,如果你使用大圖標(biāo)顯示風(fēng)格,你就需要以如下形式調(diào)用:
CImageList* SetImageList( CImageList* pImageList, LVSIL_NORMAL);
如果使用其它三種風(fēng)格顯示而不想顯示圖標(biāo)你可以不進(jìn)行任何設(shè)置,否則需要以如下形式調(diào)用:
CImageList* SetImageList( CImageList* pImageList, LVSIL_SMALL);
通過(guò)調(diào)用int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控件中nItem指明位置插入一項(xiàng),lpszItem為顯示字符。除LVS_REPORT風(fēng)格外其他三種風(fēng)格都只需要直接調(diào)用InsertItem就可以了,但如果使用報(bào)表風(fēng)格就必須先設(shè)置列表控件中的列信息。
通過(guò)調(diào)用int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol為列的位置,從零開(kāi)始,lpszColumnHeading為顯示的列名,nFormat為顯示對(duì)齊方式,nWidth為顯示寬度,nSubItem為分配給該列的列索引。
在有多列的列表控件中就需要為每一項(xiàng)指明其在每一列中的顯示字符,通過(guò)調(diào)用
BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以設(shè)置每列的顯示字符。nItem為設(shè)置的項(xiàng)的位置,nSubItem為列位置,lpszText為顯示字符。下面的代碼演示了如何設(shè)置多列并插入數(shù)據(jù): m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//設(shè)置ImageList m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//設(shè)置列 m_list.InsertColumn(1,"Col 2",LVCFMT_LEFT,300,1); m_list.InsertColumn(2,"Col 3",LVCFMT_LEFT,300,2); m_list.InsertItem(0,"Item 1_1");//插入行 m_list.SetItemText(0,1,"Item 1_2");//設(shè)置該行的不同列的顯示字符 m_list.SetItemText(0,2,"Item 1_3");
此外CListCtrl還提供了一些函數(shù)用于得到/修改控件的狀態(tài)。
COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用于得到/設(shè)置顯示的字符顏色。
COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用于得到/設(shè)置顯示的背景顏色。
void SetItemCount( int iCount );用于得到添加進(jìn)列表中項(xiàng)的數(shù)量。
BOOL DeleteItem(int nItem);用于刪除某一項(xiàng),BOOL DeleteAllItems( );將刪除所有項(xiàng)。
BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用于設(shè)置背景位圖。
CString GetItemText( int nItem, int nSubItem );用于得到某項(xiàng)的顯示字符。
列表控件的消息映射同樣使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產(chǎn)生該消息的窗口ID,memberFxn為處理函數(shù),函數(shù)的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數(shù)據(jù)結(jié)構(gòu),在具體使用時(shí)需要轉(zhuǎn)換成其他類型的結(jié)構(gòu)。對(duì)于列表控件可能取值和對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:
LVN_BEGINLABELEDIT 在開(kāi)始某項(xiàng)編輯字符時(shí)發(fā)送,所用結(jié)構(gòu):NMLVDISPINFO
LVN_ENDLABELEDIT 在結(jié)束某項(xiàng)編輯字符時(shí)發(fā)送,所用結(jié)構(gòu):NMLVDISPINFO
LVN_GETDISPINFO 在需要得到某項(xiàng)信息時(shí)發(fā)送,(如得到某項(xiàng)的顯示字符)所用結(jié)構(gòu):NMLVDISPINFO
關(guān)于ON_NOTIFY有很多內(nèi)容,將在以后的內(nèi)容中進(jìn)行詳細(xì)講解。
關(guān)于動(dòng)態(tài)提供結(jié)點(diǎn)所顯示的字符:首先你在項(xiàng)時(shí)需要指明lpszItem參數(shù)為:LPSTR_TEXTCALLBACK。在控件顯示該結(jié)點(diǎn)時(shí)會(huì)通過(guò)發(fā)送TVN_GETDISPINFO來(lái)取得所需要的字符,在處理該消息時(shí)先將參數(shù)pNMHDR轉(zhuǎn)換為L(zhǎng)PNMLVDISPINFO,然后填充其中item.pszText。通過(guò)item中的iItem,iSubItem可以知道當(dāng)前顯示的為那一項(xiàng)。下面的代碼演示了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結(jié)點(diǎn) m_list.InsertItem(LPSTR_TEXTCALLBACK,...) m_list.InsertItem(LPSTR_TEXTCALLBACK,...) //處理消息 void CParentWnd::OnGetDispInfoList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; pLVDI->item.pszText=szOut[pTVDI->item.iItem];//通過(guò)iItem得到需要 顯示的字符在數(shù)組中的位置 *pResult = 0; }
關(guān)于編輯某項(xiàng)的顯示字符:(在報(bào)表風(fēng)格中只對(duì)第一列有效)首先需要設(shè)置列表控件的LVS_EDITLABELS風(fēng)格,在開(kāi)始編輯時(shí)該控件將會(huì)發(fā)送LVN_BEGINLABELEDIT,你可以通過(guò)在處理函數(shù)中返回TRUE來(lái)取消接下來(lái)的編輯,在編輯完成后會(huì)發(fā)送LVN_ENDLABELEDIT,在處理該消息時(shí)需要將參數(shù)pNMHDR轉(zhuǎn)換為L(zhǎng)PNMLVDISPINFO,然后通過(guò)其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說(shuō)明如何處理這些消息: //處理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.iItem==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.pszText==NULL);//判斷是否已經(jīng)取消取消編輯 m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進(jìn)行的消息映射必須在父窗口中進(jìn)行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。
如何得到當(dāng)前選中項(xiàng)位置:在列表控件中沒(méi)有一個(gè)類似于ListBox中GetCurSel()的函數(shù),但是可以通過(guò)調(diào)用GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到選中項(xiàng)位置。
4.9 Tab Ctrl
Tab屬性頁(yè)控件可以在一個(gè)窗口中添加不同的頁(yè)面,然后在頁(yè)選擇發(fā)生改變時(shí)得到通知。MFC中使用CTabCtrl類來(lái)封裝屬性頁(yè)控件的各種操作。通過(guò)調(diào)用BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創(chuàng)建一個(gè)窗口,dwStyle中可以使用以下一些屬性頁(yè)控件的專用風(fēng)格:
TCS_BUTTONS 使用按鈕來(lái)表示頁(yè)選擇位置
TCS_MULTILINE 分行顯示頁(yè)選擇位置
TCS_SINGLELINE 只使用一行顯示頁(yè)選擇位置
在控件創(chuàng)建后必需向其中添加頁(yè)面才可以使用,添加頁(yè)面的函數(shù)為: BOOL InsertItem( int nItem, LPCTSTR lpszItem );nItem為位置,從零開(kāi)始,lpszItem為頁(yè)選擇位置上顯示的文字。如果你希望在頁(yè)選擇位置處顯示一個(gè)圖標(biāo),你可以調(diào)用 BOOL InsertItem( int nItem, LPCTSTR lpszItem, int nImage );nImage指明所使用的圖片位置。(在此之前必須調(diào)用CImageList * SetImageList( CImageList * pImageList );設(shè)置正確的ImageList)
此外CTabCtrl還提供了一些函數(shù)用于得到/修改控件的狀態(tài)。 int GetCurSel( )/int SetCurSel( int nItem );用于得到/設(shè)置當(dāng)前被選中的頁(yè)位置。 BOOL DeleteItem( int nItem )/BOOL DeleteAllItems( );用于刪除指定/所有頁(yè)面。 void RemoveImage( int nImage );用于刪除某頁(yè)選擇位置上的圖標(biāo)。
屬性頁(yè)控件的消息映射同樣使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產(chǎn)生該消息的窗口ID,memberFxn為處理函數(shù),函數(shù)的原型如同void OnXXXTab(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數(shù)據(jù)結(jié)構(gòu),在具體使用時(shí)需要轉(zhuǎn)換成其他類型的結(jié)構(gòu)。對(duì)于列表控件可能取值和對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:
TCN_SELCHANGE 在當(dāng)前頁(yè)改變后發(fā)送,所用結(jié)構(gòu):NMHDR
TCN_SELCHANGING 在當(dāng)前頁(yè)改變時(shí)發(fā)送可以通過(guò)返回TRUE來(lái)禁止頁(yè)面的改變,所用結(jié)構(gòu):NMHDR
一般來(lái)講在當(dāng)前頁(yè)發(fā)生改變時(shí)需要隱藏當(dāng)前的一些子窗口,并顯示其它的子窗口。下面的偽代碼演示了如何使用屬性頁(yè)控件:
CParentWnd::OnCreate(...)
{
m_tab.Create(...);
m_tab.InsertItem(0,"Option 1");
m_tab.InsertItem(1,"Option 2");
Create a edit box as the m_tab's Child
Create a static box as the m_tab's Child
edit_box.ShowWindow(SW_SHOW); // edit box在屬性頁(yè)的第一頁(yè)
static_box.ShowWindow(SW_HIDE); // static box在屬性頁(yè)的第二頁(yè)
}
void CParentWnd::OnSelectChangeTab(NMHDR* pNMHDR, LRESULT* pResult)
{//處理頁(yè)選擇改變后的消息
if(m_tab.GetCurSel()==0)
{//根據(jù)當(dāng)前頁(yè)顯示/隱藏不同的子窗口
edit_box.ShowWindow(SW_SHOW);
static_box.ShowWindow(SW_HIDE);
}
else
{//
edit_box.ShowWindow(SW_HIDE);
static_box.ShowWindow(SW_SHOW);
}
}
4.A Tool Bar
工具條也是常用的控件。MFC中使用CToolBar類來(lái)封裝工具條控件的各種操作。通過(guò)調(diào)用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_TOOLBAR );創(chuàng)建一個(gè)窗口,dwStyle中可以使用以下一些工具條控件的專用風(fēng)格:
CBRS_TOP 工具條在父窗口的頂部
TCBRS_BOTTOM 工具條在父窗口的底部
CBRS_FLOATING 工具條是浮動(dòng)的
創(chuàng)建一個(gè)工具條的步驟如下:先使用Create創(chuàng)建窗口,然后使用BOOL LoadToolBar( LPCTSTR lpszResourceName );直接從資源中裝入工具條,或者通過(guò)裝入位圖并指明每個(gè)按鈕的ID,具體代碼如下:
UINT uID[5]={IDM_1,IDM_2,IDM_3,IDM_4,IDM_5};
m_toolbar.Create(pParentWnd);
m_toolbar.LoadBitmap(IDB_TOOLBAR);
m_toolbar.SetSizes(CSize(20,20),CSize(16,16));//設(shè)置按鈕大尺寸
和按鈕上位圖的尺寸
m_toolbar.SetButtons(uID,5);
AppWizard在生成代碼時(shí)也會(huì)同時(shí)生成工具條的代碼,同時(shí)還可以支持停靠功能。所以一般是不需要直接操作工具條對(duì)象。
工具條上的按鈕被按下時(shí)發(fā)送給父窗口的消息和菜單消息相同,所以可以使用ON_COMMAND宏進(jìn)行映射,同樣工具條中的按鈕也支持ON_UPDATE_COMMAND_UI的相關(guān)操作,如SetCheck,Enable,你可以將按鈕的當(dāng)作菜單上的一個(gè)具有相同ID菜單項(xiàng)。
在以后的章節(jié)4.D 利用AppWizard創(chuàng)建并使用ToolBar StatusBar Dialog Bar會(huì)給出使用的方法。
4.B Status Bar
狀態(tài)條用于顯示一些提示字符。MFC中使用CStatusBar類來(lái)封裝狀態(tài)條控件的各種操作。通過(guò)調(diào)用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );創(chuàng)建一個(gè)窗口,dwStyle中可以使用以下一些狀態(tài)條控件的專用風(fēng)格:
CBRS_TOP 狀態(tài)條在父窗口的頂部
TCBRS_BOTTOM 狀態(tài)條在父窗口的底部
創(chuàng)建一個(gè)狀態(tài)條的步驟如下:先使用Create創(chuàng)建窗口,然后調(diào)用BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );設(shè)置狀態(tài)條上各部分的ID,具體代碼如下:
UINT uID[2]={ID_SEPARATOR,ID_INDICATOR_CAPS};
m_stabar.Create(pParentWnd);
m_stabar.SetIndicators(uID,2);
通過(guò)CString GetPaneText( int nIndex )/BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )可以得到/設(shè)置狀態(tài)條上顯示的文字。
Tip:在創(chuàng)建狀態(tài)條時(shí)最好將狀態(tài)條中所有的部分ID(除MFC自定義的幾個(gè)用于狀態(tài)條的ID外)都設(shè)置為ID_SEPARATOR,在生成后調(diào)用
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );改變其風(fēng)格,ID和寬度。
AppWizard在生成代碼時(shí)也會(huì)同時(shí)生成狀態(tài)條的代碼。所以一般是不需要直接創(chuàng)建狀態(tài)條對(duì)象。此外狀態(tài)條上會(huì)自動(dòng)顯示菜單上的命令提示(必須先在資源中定義),所以也不需要人為設(shè)置顯示文字。
狀態(tài)條支持ON_UPDATE_COMMAND_UI的相關(guān)操作,如SetText,Enable。
在以后的章節(jié)4.D 利用AppWizard創(chuàng)建并使用ToolBar StatusBar Dialog Bar會(huì)給出使用的方法。
4.C Dialog Bar
Dialog Bar類似一個(gè)靜態(tài)的附在框架窗口上的對(duì)話框,由于Dialog Bar可以使用資源編輯器進(jìn)行編輯所以使用起來(lái)就很方便,在設(shè)計(jì)時(shí)就可以對(duì)Dialog Bar上的子窗口進(jìn)行定位。用于顯示一些提示字符。MFC中使用CDialogBar類來(lái)Dialog Bar控件的各種操作。通過(guò)調(diào)用
BOOL Create( CWnd* pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID );創(chuàng)建一個(gè)窗口,nIDTemplate為對(duì)話框資源,nID為該Dialog Bar對(duì)應(yīng)的窗口ID,nStyle中可以使用以下一些狀態(tài)條控件的專用風(fēng)格:
CBRS_TOP Dialog Bar在父窗口的頂部
TCBRS_BOTTOM Dialog Bar在父窗口的底部
CBRS_LEFT Dialog Bar在父窗口的左部
CBRS_RIGHT Dialog Bar在父窗口的右部
對(duì)于Dialog Bar的所產(chǎn)生消息需要在父窗口中進(jìn)行映射和處理,例如Dialog Bar上的按鈕,需要在父窗口中進(jìn)行ON_BN_CLICKED或ON_COMMAND映射,Dialog Bar上的輸入框可以在父窗口中進(jìn)行ON_EN_CHANGE,ON_EN_MAXTEXT等輸入框?qū)?yīng)的映射。
Dialog Bar支持ON_UPDATE_COMMAND_UI的相關(guān)操作,如SetText,Enable。
在以后的章節(jié)4.D 利用AppWizard創(chuàng)建并使用ToolBar StatusBar Dialog Bar會(huì)給出使用的方法。
4.D 利用AppWizard創(chuàng)建并使用ToolBar StatusBar Dialog Bar
運(yùn)行時(shí)程序界面如界面圖,該程序擁有一個(gè)工具條用于顯示兩個(gè)命令按鈕,一個(gè)用于演示如何使按鈕處于檢查狀態(tài),另一個(gè)根據(jù)第一個(gè)按鈕的狀態(tài)來(lái)禁止/允許自身。(設(shè)置檢查狀態(tài)和允許狀態(tài)都通過(guò)OnUpdateCommand實(shí)現(xiàn))此外Dialog Bar上有一個(gè)輸入框和按鈕,這兩個(gè)子窗口的禁止/允許同樣是根據(jù)工具條上的按鈕狀態(tài)來(lái)確定,當(dāng)按下Dialog Bar上的按鈕時(shí)將顯示輸入框中的文字內(nèi)容。狀態(tài)條的第一部分用于顯示各種提示,第二部分用于利用OnUpdateCommand顯示當(dāng)前時(shí)間。同時(shí)在程序中演示了如何設(shè)置菜單項(xiàng)的命令解釋字符(將在狀態(tài)條的第一部分顯示)和如何設(shè)置工具條的提示字符(利用一個(gè)小的ToolTip窗口顯示)。
生成應(yīng)用:利用AppWizard生成一個(gè)MFC工程,圖例,并設(shè)置為單文檔界面圖例,最后選擇工具條,狀態(tài)條和ReBar支持,圖例
修改菜單:利用資源編輯器刪除多余的菜單并添加一個(gè)新的彈出菜單和三個(gè)子菜單,圖例,分別是:
名稱 ID 說(shuō)明字符
Check IDM_CHECK SetCheck Demo\nSetCheck Demo
Disable IDM_DISABLE Disable Demo\nDisable Demo
ShowText on DialogBar IDM_SHOW_TXT ShowText on DialogBar Demo\nShowText on DialogBar
\n前的字符串將顯示在狀態(tài)條中作為命令解釋,\n后的部分將作為具有相同ID的工具條按鈕的提示顯示在ToolTip窗口中。
修改Dialog Bar:在Dialog Bar中添加一個(gè)輸入框和按鈕,按鈕的ID為IDM_SHOW_TXT與一個(gè)菜單項(xiàng)具有相同的ID,這樣可以利用映射菜單消息來(lái)處理按鈕消息(當(dāng)然使用不同ID值也可以利用ON_COMMAND來(lái)映射Dialog Bar上的按鈕消息,但是ClassWizard沒(méi)有提供為Dialog Bar上按鈕進(jìn)行映射的途徑,只能手工添加消息映射代碼)。圖例
修改工具條:在工具條中添加兩個(gè)按鈕,ID值為IDM_CHECK和IDM_DISABLE和其中兩個(gè)菜單項(xiàng)具有相同的ID值。圖例
利用ClassWizard為三個(gè)菜單項(xiàng)添加消息映射和更新命令。圖例
修改MainFrm.h文件
//添加一個(gè)成員變量來(lái)記錄工具條上Check按鈕的檢查狀態(tài)。
protected:
BOOL m_fCheck;
//手工添加狀態(tài)條第二部分用于顯示時(shí)間的更新命令,
和用于禁止/允許輸入框的更新命令
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnCheck();
afx_msg void OnUpdateCheck(CCmdUI* pCmdUI);
afx_msg void OnDisable();
afx_msg void OnUpdateDisable(CCmdUI* pCmdUI);
afx_msg void OnShowTxt();
afx_msg void OnUpdateShowTxt(CCmdUI* pCmdUI);
//}}AFX_MSG
//上面的部分為ClassWizard自動(dòng)產(chǎn)生的代碼
afx_msg void OnUpdateTime(CCmdUI* pCmdUI); //顯示時(shí)間
afx_msg void OnUpdateInput(CCmdUI* pCmdUI); //禁止/允許輸入框
修改MainFrm.cpp文件
//修改狀態(tài)條上各部分ID
#define ID_TIME 0x705 //作為狀態(tài)條上第二部分ID
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_SEPARATOR, //先設(shè)置為ID_SEPARATOR,
在狀態(tài)條創(chuàng)建后再進(jìn)行修改
};
//修改消息映射
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(IDM_CHECK, OnCheck)
ON_UPDATE_COMMAND_UI(IDM_CHECK, OnUpdateCheck)
ON_COMMAND(IDM_DISABLE, OnDisable)
ON_UPDATE_COMMAND_UI(IDM_DISABLE, OnUpdateDisable)
ON_COMMAND(IDM_SHOW_TXT, OnShowTxt)
ON_UPDATE_COMMAND_UI(IDM_SHOW_TXT, OnUpdateShowTxt)
//}}AFX_MSG_MAP
//以上部分為ClassWizard自動(dòng)生成代碼
ON_UPDATE_COMMAND_UI(ID_TIME, OnUpdateTime) ////顯示時(shí)間
ON_UPDATE_COMMAND_UI(IDC_INPUT_TEST, OnUpdateInput)
//禁止/允許輸入框
//修改OnCreate函數(shù),重新設(shè)置狀態(tài)條第二部分ID值
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
....
// by wenyy 修改狀態(tài)條上第二部分信息
m_wndStatusBar.SetPaneInfo(1,ID_TIME,SBPS_NORMAL,60);
//set the width
return 0;
}
//修改經(jīng)過(guò)映射的消息處理函數(shù)代碼
void CMainFrame::OnCheck()
{
//在Check按鈕被按下時(shí)改變并保存狀態(tài)
m_fCheck=!m_fCheck;
}
void CMainFrame::OnUpdateCheck(CCmdUI* pCmdUI)
{
//Check按鈕是否設(shè)置為檢查狀態(tài)
pCmdUI->SetCheck(m_fCheck);
}
void CMainFrame::OnDisable()
{
//Disable按鈕被按下
AfxMessageBox("you press disable test");
}
void CMainFrame::OnUpdateDisable(CCmdUI* pCmdUI)
{
//根據(jù)Check狀態(tài)決定自身禁止/允許狀態(tài)
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnShowTxt()
{
//得到Dialog Bar上輸入框中文字并顯示
CEdit* pE=(CEdit*)m_wndDlgBar.GetDlgItem(IDC_INPUT_TEST);
CString szO;
pE->GetWindowText(szO);
AfxMessageBox(szO);
}
void CMainFrame::OnUpdateShowTxt(CCmdUI* pCmdUI)
{
//Dialog Bar上按鈕根據(jù)Check狀態(tài)決定自身禁止/允許狀態(tài)
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnUpdateInput(CCmdUI* pCmdUI)
{
//Dialog Bar上輸入框根據(jù)Check狀態(tài)決定自身禁止/允許狀態(tài)
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnUpdateTime(CCmdUI* pCmdUI)
{
//根據(jù)當(dāng)前時(shí)間設(shè)置狀態(tài)條上第二部分文字
CTime timeCur=CTime::GetCurrentTime();
char szOut[20];
sprintf( szOut, "%02d:%02d:%02d", timeCur.GetHour(),
timeCur.GetMinute(),timeCur.GetSecond());
pCmdUI->SetText(szOut);
}
4.E General Window
從VC提供的MFC類派生圖中我們可以看出窗口的派生關(guān)系,派生圖,所有的窗口類都是由CWnd派生。所有CWnd的成員函數(shù)在其派生類中都可以使用。本節(jié)介紹一些常用的功能給大家。
改變窗口狀態(tài):
BOOL EnableWindow( BOOL bEnable = TRUE );可以設(shè)置窗口的禁止/允許狀態(tài)。BOOL IsWindowEnabled( );可以查詢窗口的禁止/允許狀態(tài)。
BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改窗口的風(fēng)格,而不需要調(diào)用SetWindowLong
BOOL IsWindowVisible( ) 可以檢查窗口是否被顯示。
BOOL ShowWindow( int nCmdShow );將改變窗口的顯示狀態(tài),nCmdShow可取如下值:
SW_HIDE 隱藏窗口
SW_MINIMIZE SW_SHOWMAXIMIZED 最小化窗口
SW_RESTORE 恢復(fù)窗口
SW_SHOW 顯示窗口
SW_SHOWMINIMIZED 最大化窗口
改變窗口位置:
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移動(dòng)窗口。
void GetWindowRect( LPRECT lpRect ) ;可以得到窗口的矩形位置。
BOOL IsIconic( ) ;可以檢測(cè)窗口是否已經(jīng)縮為圖標(biāo)。
BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改變窗口的Z次序,此外還可以移動(dòng)窗口位置。
使窗口失效,印發(fā)重繪:
void Invalidate( BOOL bErase = TRUE );使整個(gè)窗口失效,bErase將決定窗口是否產(chǎn)生重繪。
void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );將使指定的矩形/多邊形區(qū)域失效。
窗口查找:
static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以窗口的類名和窗口名查找窗口。任一參數(shù)設(shè)置為NULL表對(duì)該參數(shù)代表的數(shù)據(jù)進(jìn)行任意匹配。如FindWindow("MyWnd",NULL)表明查找類名為MyWnd的所有窗口。
BOOL IsChild( const CWnd* pWnd ) 檢測(cè)窗口是否為子窗口。
CWnd* GetParent( ) 得到父窗口指針。
CWnd* GetDlgItem( int nID ) 通過(guò)子窗口ID得到窗口指針。
int GetDlgCtrlID( ) 得到窗口ID值。
static CWnd* PASCAL WindowFromPoint( POINT point );將從屏幕上某點(diǎn)坐標(biāo)得到包含該點(diǎn)的窗口指針。
static CWnd* PASCAL FromHandle( HWND hWnd );通過(guò)HWND構(gòu)造一個(gè)CWnd*指針,但該指針在空閑時(shí)會(huì)被刪除,所以不能保存供以后使用。
時(shí)鐘:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以創(chuàng)建一個(gè)時(shí)鐘,如果lpfnTimer回調(diào)函數(shù)為NULL,窗口將會(huì)收到WM_TIMER消息,并可以在afx_msg void OnTimer( UINT nIDEvent );中安排處理代碼
BOOL KillTimer( int nIDEvent );刪除一個(gè)指定時(shí)鐘。
可以利用重載來(lái)添加消息處理的虛函數(shù):
afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );窗口被創(chuàng)建時(shí)被調(diào)用
afx_msg void OnDestroy( );窗口被銷毀時(shí)被調(diào)用
afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到窗口尺寸時(shí)被調(diào)用
afx_msg void OnSize( UINT nType, int cx, int cy );窗口改變大小后被調(diào)用
afx_msg void OnMove( int x, int y );窗口被移動(dòng)后時(shí)被調(diào)用
afx_msg void OnPaint( );窗口需要重繪時(shí)時(shí)被調(diào)用,你可以填如繪圖代碼,對(duì)于視圖類不需要重載OnPaint,所有繪圖代碼應(yīng)該在OnDraw中進(jìn)行
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字符輸入時(shí)被調(diào)用
afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );鍵盤上鍵被按下/放開(kāi)時(shí)被調(diào)用
afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );鼠標(biāo)左/右鍵按下時(shí)被調(diào)用
afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );鼠標(biāo)左/右鍵放開(kāi)時(shí)被調(diào)用
afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );鼠標(biāo)左/右鍵雙擊時(shí)被調(diào)用
afx_msg void OnMouseMove( UINT nFlags, CPoint point );鼠標(biāo)在窗口上移動(dòng)時(shí)被調(diào)用
*********************************************
5.1 使用資源編輯器編輯對(duì)話框
在Windows開(kāi)發(fā)中彈出對(duì)話框是一種常用的輸入/輸出手段,同時(shí)編輯好的對(duì)話框可以保存在資源文件中。Visual C++提供了對(duì)話框編輯工具,利用編輯工具可以方便的添加各種控件到對(duì)話框中,而且利用ClassWizard可以方便的生成新的對(duì)話框類和映射消息。
首先資源列表中按下右鍵,可以在彈出菜單中選擇“插入對(duì)話框”,如圖1。然后再打開(kāi)該對(duì)話框進(jìn)行編輯,你會(huì)在屏幕上看到一個(gè)控件板,如圖2。你可以將所需要添加的控件拖到對(duì)話框上,或是先選中后再在對(duì)話框上用鼠標(biāo)畫出所占的區(qū)域。
接下來(lái)我們?cè)趯?duì)話框上產(chǎn)生一個(gè)輸入框,和一個(gè)用于顯示圖標(biāo)的圖片框。之后我們使用鼠標(biāo)右鍵單擊產(chǎn)生的控件并選擇其屬性,如圖3。我們可以在屬性對(duì)話框中編輯控件的屬性同時(shí)也需要指定控件ID,如圖4,如果在選擇對(duì)話框本身的屬性那么你可以選擇對(duì)話框的一些屬性,包括字體,外觀,是否有系統(tǒng)菜單等等。最后我們編輯圖片控件的屬性,如圖5,我們?cè)O(shè)置控件的屬性為顯示圖標(biāo)并指明一個(gè)圖標(biāo)ID。
接下來(lái)我們添加一些其他的控件,最后的效果如圖6。按下Ctrl-T可以測(cè)試該對(duì)話框。此外在對(duì)話框中還有一個(gè)有用的特性,就是可以利用Tab鍵讓輸入焦點(diǎn)在各個(gè)控件間移動(dòng),要達(dá)到這一點(diǎn)首先需要為控件設(shè)置在Tab鍵按下時(shí)可以接受焦點(diǎn)移動(dòng)的屬性Tab Stop,如果某一個(gè)控件不打算利用這一特性,你需要清除這一屬性。然后從菜單“Layout”選擇Tab Order來(lái)確定焦點(diǎn)移動(dòng)順序,如圖7。使用鼠標(biāo)依此點(diǎn)擊控件就可以重新規(guī)定焦點(diǎn)移動(dòng)次序。最后按下Ctrl-T進(jìn)行測(cè)試。
最后我們需要為對(duì)話框產(chǎn)生新的類,ClassWizard可以替我們完成大部分的工作,我們只需要填寫幾個(gè)參數(shù)就可以了。在編輯好的對(duì)話框上雙擊,然后系統(tǒng)回詢問(wèn)是否添加新的對(duì)話框,選擇是并在接下來(lái)的對(duì)話框中輸入類名就可以了。ClassWizard會(huì)為你產(chǎn)生所需要的頭文件和CPP文件。然后在需要使用的地方包含相應(yīng)的頭文件,對(duì)于有模式對(duì)話框使用DoModal()產(chǎn)生,對(duì)于無(wú)模式對(duì)話框使用Create()產(chǎn)生。相關(guān)代碼如下;
void CMy51_s1View::OnCreateDlg()
{//產(chǎn)生無(wú)模式對(duì)話框
CTestDlg *dlg=new CTestDlg;
dlg->Create(IDD_TEST_DLG);
dlg->ShowWindow(SW_SHOW);
}
void CMy51_s1View::OnDoModal()
{//產(chǎn)生有模式對(duì)話框
CTestDlg dlg;
int iRet=dlg.DoModal();
TRACE("dlg return %d\n",iRet);
}
下載例子。如果你在調(diào)試這個(gè)程序時(shí)你會(huì)發(fā)現(xiàn)程序在退出后會(huì)有內(nèi)存泄漏,這是因?yàn)槲覜](méi)有釋放無(wú)模式對(duì)話框所使用的內(nèi)存,這一問(wèn)題會(huì)在以后的章節(jié)5.3 創(chuàng)建無(wú)模式對(duì)話框中專門講述。
關(guān)于在使用對(duì)話框時(shí)Enter鍵和Escape鍵的處理:在使用對(duì)話框是你會(huì)發(fā)現(xiàn)當(dāng)你按下Enter鍵或Escape鍵都會(huì)退出對(duì)話框,這是因?yàn)镋nter鍵會(huì)引起CDialog::OnOK()的調(diào)用,而Escape鍵會(huì)引起CDialog::OnCancel()的調(diào)用。而這兩個(gè)調(diào)用都會(huì)引起對(duì)話框的退出。在MFC中這兩個(gè)成員函數(shù)都是虛擬函數(shù),所以我們需要進(jìn)行重載,如果我們不希望退出對(duì)話框那么我們可以在函數(shù)中什么都不做,如果需要進(jìn)行檢查則可以添加檢查代碼,然后調(diào)用父類的OnOK()或OnCancel()。相關(guān)代碼如下;
void CTestDlg::OnOK()
{
AfxMessageBox("你選擇確定");
CDialog::OnOK();
}
void CTestDlg::OnCancel()
{
AfxMessageBox("你選擇取消");
CDialog::OnCancel();
}
5.2 創(chuàng)建有模式對(duì)話框
使用有模式對(duì)話框時(shí)在對(duì)話框彈出后調(diào)用函數(shù)不會(huì)立即返回,而是等到對(duì)話框銷毀后才會(huì)返回(請(qǐng)注意在對(duì)話框彈出后其他窗口的消息依然會(huì)被傳遞)。所以在使用對(duì)話框時(shí)其他窗口都不能接收用戶輸入。創(chuàng)建有模式對(duì)話框的方法是調(diào)用CDialog::DoModal()。下面的代碼演示了這種用法:
CYourView::OnOpenDlg()
{
CYourDlg dlg;
int iRet=dlg.DoModal();
}
CDialog::DoModal()的返回值為IDOK,IDCANCEL。表明操作者在對(duì)話框上選擇“確認(rèn)”或是“取消”。由于在對(duì)話框銷毀前DoModal不會(huì)返回,所以可以使用局部變量來(lái)引用對(duì)象。在退出函數(shù)體后對(duì)象同時(shí)也會(huì)被銷毀。而對(duì)于無(wú)模式對(duì)話框則不能這樣使用,下節(jié)5.3 創(chuàng)建無(wú)模式對(duì)話框中會(huì)詳細(xì)講解。
你需要根據(jù)DoModal()的返回值來(lái)決定你下一步的動(dòng)作,而得到返回值也是使用有模式對(duì)話框的一個(gè)很大原因。
使用有模式對(duì)話框需要注意一些問(wèn)題,比如說(shuō)不要在一些反復(fù)出現(xiàn)的事件處理過(guò)程中生成有模式對(duì)話框,比如說(shuō)在定時(shí)器中產(chǎn)生有模式對(duì)話框,因?yàn)樵谏弦粋€(gè)對(duì)話框還未退出時(shí),定時(shí)器消息又會(huì)引起下一個(gè)對(duì)話框的彈出。
同樣的在你的對(duì)話框類中為了向調(diào)用者返回不同的值可以調(diào)用CDialog::OnOK()或是CDialog::OnCancel()以返回IDOK或IDCANCEL,如果你希望返回其他的值,你需要調(diào)用
CDialog::EndDialog( int nResult );其中nResult會(huì)作為DoModal()調(diào)用的返回值。
下面的代碼演示了如何使用自己的函數(shù)來(lái)退出對(duì)話框:
void CMy52_s1View::OnLButtonDown(UINT nFlags, CPoint point)
{//創(chuàng)建對(duì)話框并得到返回值
CView::OnLButtonDown(nFlags, point);
CTestDlg dlg;
int iRet=dlg.DoModal();
CString szOut;
szOut.Format("return value %d",iRet);
AfxMessageBox(szOut);
}
//重載OnOK,OnCancel
void CTestDlg::OnOK()
{//什么也不做
}
void CTestDlg::OnCancel()
{//什么也不做
}
//在對(duì)話框中對(duì)三個(gè)按鈕消息進(jìn)行映射
void CTestDlg::OnExit1()
{
CDialog::OnOK();
}
void CTestDlg::OnExit2()
{
CDialog::OnCancel();
}
void CTestDlg::OnExit3()
{
CDialog::EndDialog(0XFF);
}
由于重載了OnOK和OnCancel所以在對(duì)話框中按下Enter鍵或Escape鍵時(shí)都不會(huì)退出,只有按下三個(gè)按鈕中的其中一個(gè)才會(huì)返回。
此外在對(duì)話框被生成是會(huì)自動(dòng)調(diào)用BOOL CDialog::OnInitDialog(),你如果需要在對(duì)話框顯示前對(duì)其中的控件進(jìn)行初始化,你需要重載這個(gè)函數(shù),并在其中填入相關(guān)的初始化代碼。利用ClassWizard可以方便的產(chǎn)生一些默認(rèn)代碼,首先打開(kāi)ClassWizard,選擇相應(yīng)的對(duì)話框類,在右邊的消息列表中選擇WM_INITDIALOG并雙擊,如圖,ClassWizard會(huì)自動(dòng)產(chǎn)生相關(guān)代碼,代碼如下:
BOOL CTestDlg::OnInitDialog()
{
/*先調(diào)用父類的同名函數(shù)*/
CDialog::OnInitDialog();
/*填寫你的初始化代碼*/
return TRUE;
}
有關(guān)對(duì)對(duì)話框中控件進(jìn)行初始化會(huì)在5.4 在對(duì)話框中進(jìn)行消息映射中進(jìn)行更詳細(xì)的講解。
5.3 創(chuàng)建無(wú)模式對(duì)話框
無(wú)模式對(duì)話框與有模式對(duì)話框不同的是在創(chuàng)建后其他窗口都可以繼續(xù)接收用戶輸入,因此無(wú)模式對(duì)話框有些類似一個(gè)彈出窗口。創(chuàng)建無(wú)模式對(duì)話框需要調(diào)用BOOL CDialog::Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );之后還需要調(diào)用 BOOL CDialog::ShowWindow( SW_SHOW);進(jìn)行顯示,否則無(wú)模式對(duì)話框?qū)⑹遣豢梢?jiàn)的。相關(guān)代碼如下:
void CYourView::OnOpenDlg(void)
{
/*假設(shè)IDD_TEST_DLG為已經(jīng)定義的對(duì)話框資源的ID號(hào)*/
CTestDlg *dlg=new CTestDlg;
dlg->Create(IDD_TEST_DLG,NULL);
dlg->ShowWindows(SW_SHOW);
/*不要調(diào)用 delete dlg;*/
}
在上面的代碼中我們新生成了一個(gè)對(duì)話框?qū)ο螅以谕顺龊瘮?shù)時(shí)并沒(méi)有銷毀該對(duì)象。因?yàn)槿绻藭r(shí)銷毀該對(duì)象(對(duì)象被銷毀時(shí)窗口同時(shí)被銷毀),而此時(shí)對(duì)話框還在顯示就會(huì)出現(xiàn)錯(cuò)誤。那么這就提出了一個(gè)問(wèn)題:什么時(shí)候銷毀該對(duì)象。我時(shí)常使用的方法有兩個(gè):
在對(duì)話框退出時(shí)銷毀自己:在對(duì)話框中重載OnOK與OnCancel在函數(shù)中調(diào)用父類的同名函數(shù),然后調(diào)用DestroyWindow()強(qiáng)制銷毀窗口,在對(duì)話框中映射WM_DESTROY消息,在消息處理函數(shù)中調(diào)用delete this;強(qiáng)行刪除自身對(duì)象。相關(guān)代碼如下:
void CTestDlg1::OnOK()
{
CDialog::OnOK();
DestroyWindow();
}
void CTestDlg1::OnCancel()
{
CDialog::OnCancel();
DestroyWindow();
}
void CTestDlg1::OnDestroy()
{
CDialog::OnDestroy();
AfxMessageBox("call delete this");
delete this;
}
這種方法的要點(diǎn)是在窗口被銷毀的時(shí)候,刪除自身對(duì)象。所以你可以在任何時(shí)候調(diào)用DestroyWindow()以達(dá)到徹底銷毀自身對(duì)象的作用。(DestroyWindow()的調(diào)用會(huì)引起OnDestroy()的調(diào)用)
通過(guò)向父親窗口發(fā)送消息,要求其他窗口對(duì)其進(jìn)行銷毀:首先需要定義一個(gè)消息用于進(jìn)行通知,然后在對(duì)話框中映射WM_DESTROY消息,在消息處理函數(shù)中調(diào)用消息發(fā)送函數(shù)通知其他窗口。在接收消息的窗口中利用ON_MESSAGE映射處理消息的函數(shù),并在消息處理函數(shù)中刪除對(duì)話框?qū)ο蟆O嚓P(guān)代碼如下:
/*更改對(duì)話框的有關(guān)文件*/
CTestDlg2::CTestDlg2(CWnd* pParent /*=NULL*/)
: CDialog(CTestDlg2::IDD, pParent)
{/*m_pParent為一成員變量,用于保存通知窗口的指針,
所以該指針不能是一個(gè)臨時(shí)指針*/
ASSERT(pParent);
m_pParent=pParent;
//{{AFX_DATA_INIT(CTestDlg2)
// NOTE: the ClassWizard will add member
initialization here
//}}AFX_DATA_INIT
}
void CTestDlg2::OnOK()
{
CDialog::OnOK();
DestroyWindow();
}
void CTestDlg2::OnCancel()
{
CDialog::OnCancel();
DestroyWindow();
}
void CTestDlg2::OnDestroy()
{
CDialog::OnDestroy();
/*向其他窗口發(fā)送消息,將自身指針作為一個(gè)參數(shù)發(fā)送*/
m_pParent->PostMessage(WM_DELETE_DLG,
(WPARAM)this);
}
/*在消息接收窗口中添加消息映射*/
/*在頭文件中添加函數(shù)定義*/
afx_msg LONG OnDelDlgMsg(WPARAM wP,
LPARAM lP);
/*添加消息映射代碼*/
ON_MESSAGE(WM_DELETE_DLG,OnDelDlgMsg)
END_MESSAGE_MAP()
/*實(shí)現(xiàn)消息處理函數(shù)*/
LONG CMy53_s1View::OnDelDlgMsg(WPARAM wP,LPARAM lP)
{
delete (CTestDlg2*)wP;
return 0;
}
/*創(chuàng)建對(duì)話框*/
void CMy53_s1View::OnTest2()
{
CTestDlg2 *dlg=new CTestDlg2(this);
dlg->Create(IDD_TEST_DLG_2);
dlg->ShowWindow(SW_SHOW);
}
在這種方法中我們利用消息來(lái)進(jìn)行通知,在Window系統(tǒng)中利用消息進(jìn)行通知和傳遞數(shù)據(jù)的用法是很多的。
同樣無(wú)模式對(duì)話框的另一個(gè)作用還可以用來(lái)在用戶在對(duì)話框中的輸入改變時(shí)可以及時(shí)的反映到其他窗口。下面的代碼演示了在對(duì)話框中輸入一段文字,然后將其更新到視圖的顯示區(qū)域中,這同樣也是利用了消息進(jìn)行通知和數(shù)據(jù)傳遞。
/*在對(duì)話框中取出數(shù)據(jù),并向其他窗口發(fā)送消息和數(shù)據(jù),
將數(shù)據(jù)指針作為一個(gè)參數(shù)發(fā)送*/
void CTestDlg2::OnCommBtn()
{
char szOut[30];
GetDlgItemText(IDC_OUT,szOut,30);
m_pParent->SendMessage(WM_DLG_NOTIFY,
(WPARAM)szOut);
}
/*在消息接收窗口中*/
/*映射消息處理函數(shù)*/
ON_MESSAGE(WM_DLG_NOTIFY,OnDlgNotifyMsg)
/*在視圖中繪制出字符串 m_szOut*/
void CMy53_s1View::OnDraw(CDC* pDC)
{
CMy53_s1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
pDC->TextOut(0,0,"Display String");
pDC->TextOut(0,20,m_szOut);
}
/*處理通知消息,保存信息并更新顯示*/
LONG CMy53_s1View::OnDlgNotifyMsg(WPARAM wP,LPARAM lP)
{
m_szOut=(char*)wP;
Invalidate();
return 0;
}
此外這種用法利用消息傳遞數(shù)據(jù)的方法對(duì)有模式對(duì)話框和其他的窗口間通信也一樣有效。
5.4 在對(duì)話框中進(jìn)行消息映射
利用對(duì)話框的一個(gè)好處是可以利用ClassWizard對(duì)對(duì)話框中各個(gè)控件產(chǎn)生的消息進(jìn)行映射,ClassWizrd可以列出各種控件可以使用的消息,并能自動(dòng)產(chǎn)生代碼。在本節(jié)中我們以一個(gè)例子來(lái)講解如何在對(duì)話框中對(duì)子窗口消息進(jìn)行映射同時(shí)還講解如何對(duì)對(duì)話框中的子窗口進(jìn)行初始化。
首先我們產(chǎn)生編輯好一個(gè)對(duì)話框,如圖,在對(duì)話框中使用的控件和ID號(hào)如下表:
ID 類型
IDC_RADIO_TEST_1 圓形按鈕
IDC_RADIO_TEST_2 圓形按鈕
IDC_BUTTON_TEST 按鈕
IDC_CHECK_TEST 檢查按鈕
IDC_TREE_TEST 樹(shù)形控件
IDC_LIST_CTRL List Ctrl
IDC_TAB_CTRL Tab Ctrl
IDC_LIST_TEST 列表框
IDC_COMBO_TEST 組合框
IDC_EDIT_TEST 輸入框
首先我們需要在對(duì)話框的OnInitDialog()函數(shù)中對(duì)各個(gè)控件進(jìn)行初始化,這里我們使用CWnd* GetDlgItem( int nID )來(lái)通過(guò)ID號(hào)得到子窗口指針。(類似的函數(shù)還有UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) 通過(guò)ID號(hào)得到子窗口中輸入的數(shù)字,int GetDlgItemText( int nID, CString& rString ) 通過(guò)ID號(hào)得到子窗口中輸入的文字)。代碼如下:
BOOL CMy54_s1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
/*添加初始化代碼*/
//初始化輸入框
((CEdit*)GetDlgItem(IDC_EDIT_TEST))->SetWindowText("this is a edit box");
//初始化列表框
CListBox* pListB=(CListBox*)GetDlgItem(IDC_LIST_TEST);
pListB->AddString("item 1");
pListB->AddString("item 2");
pListB->AddString("item 3");
//初始化組合框
CComboBox* pCB=(CComboBox*)GetDlgItem(IDC_COMBO_TEST);
pCB->AddString("item 1");
pCB->AddString("item 2");
pCB->AddString("item 3");
//初始化Tab Ctrl
CTabCtrl* pTab=(CTabCtrl*)GetDlgItem(IDC_TAB_TEST);
pTab->InsertItem(0,"Tab Page1");
pTab->InsertItem(1,"Tab Page2");
pTab->InsertItem(2,"Tab Page3");
//初始化ListCtrl
CListCtrl* pList=(CListCtrl*)GetDlgItem(IDC_LIST_CTRL);
pList->InsertColumn(0,"Column 1",LVCFMT_LEFT,100);
pList->InsertItem(0,"Item 1");
pList->InsertItem(1,"Item 2");
pList->InsertItem(2,"Item 3");
//初始化TreeCtrl
CTreeCtrl* pTree=(CTreeCtrl*)GetDlgItem(IDC_TREE_TEST);
pTree->InsertItem("Node1",0,0);
HTREEITEM hNode=pTree->InsertItem("Node2",0,0);
pTree->InsertItem("Node2-1",0,0,hNode);
pTree->InsertItem("Node2-2",0,0,hNode);
pTree->Expand(hNode,TVE_EXPAND);
return TRUE; // return TRUE unless you set the focus to a control
}
接下來(lái)我們需要利用ClassWizard對(duì)控件所產(chǎn)生的消息進(jìn)行映射,打開(kāi)ClassWizard對(duì)話框,選中相關(guān)控件的ID,在右邊的列表中就會(huì)顯示出可用的消息。如我們對(duì)按鈕的消息進(jìn)行映射,在選中按鈕ID(IDC_BUTTON_TEST)后,會(huì)看到兩個(gè)消息,如圖,一個(gè)是BN_CLICKED,一個(gè)是BN_DOUBLECLICKED。雙擊BN_CLICKED后在彈出的對(duì)話框中輸入函數(shù)名,ClassWizard會(huì)產(chǎn)生按鈕被按的消息映射。
然后我們看看對(duì)TabCtrl的TCN_SELCHANGE消息進(jìn)行映射,如圖,在TabCtrl的當(dāng)前頁(yè)選擇發(fā)生改變時(shí)這個(gè)消息會(huì)被發(fā)送,所以通過(guò)映射該消息可以在當(dāng)前頁(yè)改變時(shí)及時(shí)得到通知。
最后我們對(duì)輸入框的EN_CHANGE消息進(jìn)行映射,如圖,在輸入框中的文本改變后該消息會(huì)被發(fā)送。相關(guān)的代碼如下:
//頭文件中相關(guān)的消息處理函數(shù)定義
afx_msg void OnButtonTest();
afx_msg void OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnChangeEditTest();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
//CPP文件中消息映射代碼
ON_BN_CLICKED(IDC_BUTTON_TEST, OnButtonTest)
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_TEST, OnSelchangeTabTest)
ON_EN_CHANGE(IDC_EDIT_TEST, OnChangeEditTest)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//消息處理函數(shù)
void CMy54_s1Dlg::OnButtonTest()
{
AfxMessageBox("you pressed a button");
}
void CMy54_s1Dlg::OnSelchangeTabTest(NMHDR* pNMHDR, LRESULT* pResult)
{
TRACE("Tab Select changed\n");
*pResult = 0;
}
void CMy54_s1Dlg::OnChangeEditTest()
{
TRACE("edit_box text changed\n");
}
對(duì)于其他的控件都可以采取類似的方法進(jìn)行消息映射。此外如果你對(duì)各種控件可以使用的消息不熟悉,你可以通過(guò)使用對(duì)話框,然后利用ClassWizard產(chǎn)生相關(guān)代碼的方法來(lái)進(jìn)行學(xué)習(xí),你也可以將ClassWizard產(chǎn)生的代碼直接拷貝到其他需要的地方(不瞞你說(shuō),我最開(kāi)始就是這樣學(xué)的 :-D 這也算一個(gè)小竅門)。
5.5 在對(duì)話框中進(jìn)行數(shù)據(jù)交換和數(shù)據(jù)檢查
MFC提供兩種方法在對(duì)話框中進(jìn)行數(shù)據(jù)交換和數(shù)據(jù)檢查(Dialog data exchange/Dialog data validation),數(shù)據(jù)交換和數(shù)據(jù)檢查的思想是將某一變量和對(duì)話框中的一個(gè)子窗口進(jìn)行關(guān)聯(lián),然后通過(guò)調(diào)用BOOL UpdateData( BOOL bSaveAndValidate = TRUE )來(lái)指示MFC將變量中數(shù)據(jù)放入子窗口還是將子窗口中數(shù)據(jù)取到變量中并進(jìn)行合法性檢查。
在進(jìn)行數(shù)據(jù)交換時(shí)一個(gè)子窗口可以和兩種類型的變量相關(guān)聯(lián),一種是控件(Control)對(duì)象,比如說(shuō)按鈕子窗口可以和一個(gè)CButton對(duì)象相關(guān)聯(lián),這種情況下你可以通過(guò)該對(duì)象直接控制子窗口,而不需要象上節(jié)中講的一樣使用GetDlgItem(IDC_CONTROL_ID)來(lái)得到窗口指針;一種是內(nèi)容對(duì)象,比如說(shuō)輸入框可以和一個(gè)CString對(duì)象關(guān)聯(lián),也可以和一個(gè)UINT類型變量關(guān)聯(lián),這種情況下你可以直接設(shè)置/獲取窗口中的輸入內(nèi)容。
而數(shù)據(jù)檢查是在一個(gè)子窗口和一個(gè)內(nèi)容對(duì)象相關(guān)聯(lián)時(shí)在存取內(nèi)容時(shí)對(duì)內(nèi)容進(jìn)行合法性檢查,比如說(shuō)當(dāng)一個(gè)輸入框和一個(gè)CString對(duì)象關(guān)聯(lián)時(shí),你可以設(shè)置檢查CString的對(duì)象的最長(zhǎng)/最小長(zhǎng)度,當(dāng)輸入框和一個(gè)UINT變量相關(guān)聯(lián)時(shí)你可以設(shè)置檢查UINT變量的最大/最小值。在BOOL UpdateData( BOOL bSaveAndValidate = TRUE )被調(diào)用后,合法性檢查會(huì)自動(dòng)進(jìn)行,如果無(wú)法通過(guò)檢查MFC會(huì)彈出消息框進(jìn)行提示,并返回FALSE。
設(shè)置DDX/DDV在VC中非常簡(jiǎn)單,ClassWizard可以替你完成所有的工作,你只需要打開(kāi)ClassWizard并選中Member Variables頁(yè),如圖,你可以看到所有可以進(jìn)行關(guān)聯(lián)的子窗口ID列表,雙擊一個(gè)ID會(huì)彈出一個(gè)添加變量的對(duì)話框,如圖,填寫相關(guān)的信息后按下確定按鈕就可以了。然后選中你剛才添加的變量在底部的輸入框中輸入檢查條件,如圖。
下面我們看一個(gè)例子,對(duì)話框上的子窗口如圖設(shè)置,各子窗口的ID和關(guān)聯(lián)的變量如下表:
ID 關(guān)聯(lián)的變量 作用
IDC_CHECK_TEST BOOL m_fCheck 檢查框是否被選中
IDC_RADOI_TEST_1 int m_iSel 當(dāng)前選擇的圓形按鈕的索引
IDC_COMBO_TEST CString m_szCombo 組合框中選中的文本或是輸入的文本
IDC_EDIT_TEST CString m_szEdit 輸入框中輸入的文本,最大長(zhǎng)度為5
IDC_LIST_TEST CListBox m_lbTest 列表框?qū)ο?nbsp;
這時(shí)候ClassWizard會(huì)自動(dòng)生成變量定義和相關(guān)代碼,在對(duì)話框的構(gòu)造函數(shù)中可以對(duì)變量的初始值進(jìn)行設(shè)置,此外在BOOL CDialog::OnInitDialog()中會(huì)調(diào)用UpdateData(FALSE),即會(huì)將變量中的數(shù)據(jù)放入窗口中 。相關(guān)代碼如下:
//頭文件中的變量定義,ClassWizard自動(dòng)產(chǎn)生
// Dialog Data
//{{AFX_DATA(CMy55_s1Dlg)
enum { IDD = IDD_MY55_S1_DIALOG };
CListBox m_lbTest;
int m_iSel;
CString m_szEdit;
CString m_szCombo;
BOOL m_fCheck;
//}}AFX_DATA
//構(gòu)造函數(shù)中賦初值
CMy55_s1Dlg::CMy55_s1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CMy55_s1Dlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CMy55_s1Dlg)
m_iSel = -1;
m_szEdit = _T("");
m_szCombo = _T("");
m_fCheck = FALSE;
//}}AFX_DATA_INIT
.....
}
//ClassWizard產(chǎn)生的關(guān)聯(lián)和檢查代碼
void CMy55_s1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMy55_s1Dlg)
DDX_Control(pDX, IDC_LIST_TEST, m_lbTest);
DDX_Radio(pDX, IDC_RADIO_TEST_1, m_iSel);
DDX_Text(pDX, IDC_EDIT_TEST, m_szEdit);
DDV_MaxChars(pDX, m_szEdit, 5);
DDX_CBString(pDX, IDC_COMBO_TEST, m_szCombo);
DDX_Check(pDX, IDC_CHECK_TEST, m_fCheck);
//}}AFX_DATA_MAP
}
//在OnInitDialog中利用已經(jīng)關(guān)聯(lián)過(guò)的變量m_lbTest
BOOL CMy55_s1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
...
// TODO: Add extra initialization here
//設(shè)置列表框中數(shù)據(jù)
m_lbTest.AddString("String 1");
m_lbTest.AddString("String 2");
m_lbTest.AddString("String 3");
m_lbTest.AddString("String 4");
return TRUE; // return TRUE unless you set the focus to a control
}
//對(duì)兩個(gè)按鈕消息處理
//通過(guò)UpdateData(TRUE)得到窗口中數(shù)據(jù)
void CMy55_s1Dlg::OnGet()
{
if(UpdateData(TRUE))
{
//數(shù)據(jù)合法性檢查通過(guò),可以使用變量中存放的數(shù)據(jù)
CString szOut;
szOut.Format("radio =%d \ncheck is %d\nedit input is %s\ncomboBox input is %s\n", m_iSel,m_fCheck,m_szEdit,m_szCombo); AfxMessageBox(szOut);
}
//else 未通過(guò)檢查
}
//通過(guò)UpdateData(FALSE)將數(shù)據(jù)放入窗口
void CMy55_s1Dlg::OnPut()
{
m_szEdit="onPut test";
m_szCombo="onPut test";
UpdateData(FALSE);
}
在上面的例子中我們看到利用DDX/DDV和UpdateData(BOOL)調(diào)用我們可以很方便的存取數(shù)據(jù),而且也可以同時(shí)進(jìn)行合法性檢查。
5.6 使用屬性對(duì)話框
屬性對(duì)話框不同于普通對(duì)話框的是它能同時(shí)提供多個(gè)選項(xiàng)頁(yè),而每頁(yè)都可以由資源編輯器以編輯對(duì)話框的方式進(jìn)行編輯,這樣給界面開(kāi)發(fā)帶來(lái)了方便。同時(shí)使用上也遵守普通對(duì)話框的規(guī)則,所以學(xué)習(xí)起來(lái)很方便。屬性對(duì)話框由兩部分構(gòu)成:多個(gè)屬性頁(yè)(CPropertyPage)和屬性對(duì)話框(CPropertySheet)。
首先需要編輯屬性頁(yè),在資源編輯器中選擇插入,并且選擇屬性對(duì)話框后就可以插入一個(gè)屬性頁(yè),如圖,或者選擇插入一個(gè)對(duì)話框,然后將其屬性中的Style設(shè)置為Child,Border設(shè)置為Thin也可以,如圖。然后根據(jù)這個(gè)對(duì)話框資源生成一個(gè)新類,在選擇基類時(shí)選擇CPropertyPage,ClassWizard會(huì)自動(dòng)生成相關(guān)的代碼。
而對(duì)于CPropertySheet也需要生成新類,并且將所有需要加入的屬性頁(yè)對(duì)象都作為成員變量。屬性對(duì)話框也分為有模式和無(wú)模式兩種,有模式屬性對(duì)話框使用DoModal()創(chuàng)建,無(wú)模式屬性對(duì)話框使用Create()創(chuàng)建。下面的代碼演示了如何創(chuàng)建屬性對(duì)話框并添加屬性頁(yè):
//修改CPropertySheet派生類的構(gòu)造函數(shù)為如下形式
CSheet::CSheet()
:CPropertySheet("test sheet", NULL, 0)
{
m_page1.Construct(IDD_PAGE_1);
m_page2.Construct(IDD_PAGE_2);
AddPage(&m_page1);
AddPage(&m_page2);
}
//創(chuàng)建有模式屬性對(duì)話框
void CMy56_s1Dlg::OnMod()
{
CSheet sheet;
sheet.DoModal();
}
//創(chuàng)建無(wú)模式屬性對(duì)話框
void CMy56_s1Dlg::OnUnm()
{
CSheet *sheet=new CSheet;
sheet->Create();
}
對(duì)于屬性對(duì)話框可以使用下面的一些成員函數(shù):
CPropertyPage* CPropertySheet::GetActivePage( )得到當(dāng)前活動(dòng)頁(yè)的指針。
BOOL CPropertySheet::SetActivePage( int nPage )用于設(shè)置當(dāng)前活動(dòng)頁(yè)。
int CPropertySheet::GetPageCount()用于得到當(dāng)前頁(yè)總數(shù)。
void CPropertySheet::RemovePage( int nPage )用于刪除一頁(yè)。
而對(duì)于屬性頁(yè)來(lái)將主要通過(guò)重載一些函數(shù)來(lái)達(dá)到控制的目的:
void CPropertyPage::OnOK() 在屬性對(duì)話框上按下“確定”按鈕后被調(diào)用
void CPropertyPage::OnCancel() 在屬性對(duì)話框上按下“取消”按鈕后被調(diào)用
void CPropertyPage::OnApply() 在屬性對(duì)話框上按下“應(yīng)用”按鈕后被調(diào)用
void CPropertyPage::SetModified( BOOL bChanged = TRUE ) 設(shè)置當(dāng)前頁(yè)面上的數(shù)據(jù)被修改標(biāo)記,這個(gè)調(diào)用可以使“應(yīng)用”按鈕為允許狀態(tài)。
此外利用屬性對(duì)話框你可以生成向?qū)?duì)話框,向?qū)?duì)話框同樣擁有多個(gè)屬性頁(yè),但同時(shí)只有一頁(yè)被顯示,而且對(duì)話框上顯示的按鈕為“上一步”,“下一步”/“完成”,向?qū)?duì)話框會(huì)按照你添加頁(yè)面的順序依次顯示所有的頁(yè)。在顯示屬性對(duì)話框前你需要調(diào)用void CPropertySheet::SetWizardMode()。使用向?qū)?duì)話框時(shí)需要對(duì)屬性頁(yè)的BOOL CPropertyPage::OnSetActive( )進(jìn)行重載,并在其中調(diào)用void CPropertySheet::SetWizardButtons( DWORD dwFlags )來(lái)設(shè)置向?qū)?duì)話框上顯示的按鈕。dwFlags的取值可為以下值的“或”操作:
PSWIZB_BACK 顯示“上一步”按鈕
PSWIZB_NEXT 顯示“下一步”按鈕
PSWIZB_FINISH 顯示“完成”按鈕
PSWIZB_DISABLEDFINISH 顯示禁止的“完成”按鈕
void CPropertySheet::SetWizardButtons( DWORD dwFlags )也可以在其他地方調(diào)用,比如說(shuō)在顯示最后一頁(yè)時(shí)先顯示禁止的“完成”按鈕,在完成某些操作后再顯示允許的“完成”按鈕。
在使用向?qū)?duì)話框時(shí)可以通過(guò)重載一些函數(shù)來(lái)達(dá)到控制的目的:
void CPropertyPage::OnWizardBack() 按下了“上一步”按鈕。返回0表示有系統(tǒng)決定需要顯示的頁(yè)面,-1表示禁止頁(yè)面轉(zhuǎn)換,如果希望顯示一個(gè)特定的頁(yè)面需要返回該頁(yè)面的ID號(hào)。
void CPropertyPage::OnOnWizardNext() 按下了“下一步”按鈕。返回值含義與void CPropertyPage::OnWizardBack()相同。
void CPropertyPage::OnWizardFinish() 按下了“完成”按鈕。返回FALSE表示不允許繼續(xù),否則返回TRUE向?qū)?duì)話框?qū)⒈唤Y(jié)束。
在向?qū)?duì)話框的DoModal()返回值為ID_WIZFINISH或IDCANCEL。下面的代碼演示了如何創(chuàng)建并使用向?qū)?duì)話框:
//創(chuàng)建有模式向?qū)?duì)話框
void CMy56_s1Dlg::OnWiz()
{
CSheet sheet;
sheet.SetWizardMode();
int iRet=sheet.DoModal();//返回ID_WIZFINISH或IDCANCEL
}
//重載BOOL CPropertyPage::OnSetActive( )來(lái)控制顯示的按鈕
BOOL CPage1::OnSetActive()
{
((CPropertySheet*)GetParent())->SetWizardButt
ons(PSWIZB_BACK|PSWIZB_NEXT);
return CPropertyPage::OnSetActive();
}
BOOL CPage2::OnSetActive()
{
((CPropertySheet*)GetParent())->SetWizardButt
ons(PSWIZB_BACK|PSWIZB_FINISH);
return CPropertyPage::OnSetActive();
}
5.7 使用通用對(duì)話框
在Windows系統(tǒng)中提供了一些通用對(duì)話框如:文件選擇對(duì)話框如圖,顏色選擇對(duì)話框如圖,字體選擇對(duì)話框如圖。在MFC中使用CFileDialog,CColorDialog,CFontDialog來(lái)表示。一般來(lái)講你不需要派生新的類,因?yàn)榛愐呀?jīng)提供了常用的功能。而且在創(chuàng)建并等待對(duì)話框結(jié)束后你可以通過(guò)成員函數(shù)得到用戶在對(duì)話框中的選擇。
CFileDialog文件選擇對(duì)話框的使用:首先構(gòu)造一個(gè)對(duì)象并提供相應(yīng)的參數(shù),構(gòu)造函數(shù)原型如下:CFileDialog::CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );參數(shù)意義如下:
bOpenFileDialog 為TRUE則顯示打開(kāi)對(duì)話框,為FALSE則顯示保存對(duì)話文件對(duì)話框。
lpszDefExt 指定默認(rèn)的文件擴(kuò)展名。
lpszFileName 指定默認(rèn)的文件名。
dwFlags 指明一些特定風(fēng)格。
lpszFilter 是最重要的一個(gè)參數(shù),它指明可供選擇的文件類型和相應(yīng)的擴(kuò)展名。參數(shù)格式如: "Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||";文件類型說(shuō)明和擴(kuò)展名間用 | 分隔,同種類型文件的擴(kuò)展名間可以用 ; 分割,每種文件類型間用 | 分隔,末尾用 || 指明。
pParentWnd 為父窗口指針。
創(chuàng)建文件對(duì)話框可以使用DoModal(),在返回后可以利用下面的函數(shù)得到用戶選擇:
CString CFileDialog::GetPathName( ) 得到完整的文件名,包括目錄名和擴(kuò)展名如:c:\test\test1.txt
CString CFileDialog::GetFileName( ) 得到完整的文件名,包括擴(kuò)展名如:test1.txt
CString CFileDialog::GetExtName( ) 得到完整的文件擴(kuò)展名,如:txt
CString CFileDialog::GetFileTitle ( ) 得到完整的文件名,不包括目錄名和擴(kuò)展名如:test1
POSITION CFileDialog::GetStartPosition( ) 對(duì)于選擇了多個(gè)文件的情況得到第一個(gè)文件位置。
CString CFileDialog::GetNextPathName( POSITION& pos ) 對(duì)于選擇了多個(gè)文件的情況得到下一個(gè)文件位置,并同時(shí)返回當(dāng)前文件名。但必須已經(jīng)調(diào)用過(guò)POSITION CFileDialog::GetStartPosition( )來(lái)得到最初的POSITION變量。
CColorDialog顏色選擇對(duì)話框的使用:首先通過(guò)CColorDialog::CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL )構(gòu)造一個(gè)對(duì)象,其中clrInit為初始顏色。通過(guò)調(diào)用DoModal()創(chuàng)建對(duì)話框,在返回后調(diào)用COLORREF CColorDialog::GetColor( )得到用戶選擇的顏色值。
CFontDialog字體選擇對(duì)話框的使用:首先構(gòu)造一個(gè)對(duì)象并提供相應(yīng)的參數(shù),構(gòu)造函數(shù)原型如下: CFontDialog::CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );構(gòu)造一個(gè)對(duì)象,其中參數(shù)lplfInitial指向一個(gè)LOGFONG結(jié)構(gòu)(該結(jié)構(gòu)介紹請(qǐng)見(jiàn)2.2 在窗口中輸出文字),如果該參數(shù)設(shè)置為NULL表示不設(shè)置初始字體。pdcPrinter指向一個(gè)代表打印機(jī)設(shè)備環(huán)境的DC對(duì)象,若設(shè)置該參數(shù)則選擇的字體就為打印機(jī)所用。pParentWnd用于指定父窗口。通過(guò)調(diào)用DoModal()創(chuàng)建對(duì)話框,在返回后通過(guò)調(diào)用以下函數(shù)來(lái)得到用戶選擇:
void CFontDialog::GetCurrentFont( LPLOGFONT lplf );用來(lái)獲得所選字體的屬性。該函數(shù)有一個(gè)參數(shù),該參數(shù)是指向LOGFONT結(jié)構(gòu)的指針,函數(shù)將所選字體的各種屬性寫入這個(gè)LOGFONT結(jié)構(gòu)中。
CString CFontDialog::GetFaceName( ) 得到所選字體名字。
int CFontDialog::GetSize( ) 得到所選字體的尺寸(以10個(gè)象素為單位)。
COLORREF CFontDialog::GetColor( ) 得到所選字體的顏色。
BOOL CFontDialog::IsStrikeOut( )BOOL CFontDialog::IsUnderline( )BOOL CFontDialog::IsBold( )BOOL CFontDialog::IsItalic( )得到所選字體的其他屬性,是否有刪除線,是否有下劃線,是否為粗體,是否為斜體。
5.8 建立以對(duì)話框?yàn)榛A(chǔ)的應(yīng)用
我認(rèn)為初學(xué)者使用以對(duì)話框?yàn)榛A(chǔ)的應(yīng)用是一個(gè)比較好的選擇,因?yàn)檫@樣一來(lái)可以擺脫一些開(kāi)發(fā)界面的麻煩,此外也可以利用ClassWizard自動(dòng)的添加消息映射。
在VC中提供了生成“以對(duì)話框?yàn)榛A(chǔ)的應(yīng)用”的功能,你所需要選擇的是在使用AppWizard的第一步選擇“對(duì)話框?yàn)榛A(chǔ)的應(yīng)用”,如圖。VC會(huì)生成包含有應(yīng)用派生類和對(duì)話框派生類的代碼。在應(yīng)用類的InitInstance()成員函數(shù)中可以看到如下的代碼:
BOOL CMy58_s1App::InitInstance()
{
CMy58_s1Dlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
return FALSE;
}
這是產(chǎn)生一個(gè)有模式對(duì)話框并創(chuàng)建它,在對(duì)話框返回后通過(guò)返回FALSE來(lái)直接退出。在設(shè)計(jì)時(shí)通過(guò)編輯對(duì)話框資源你可以設(shè)計(jì)好界面,然后通過(guò)ClassWizard映射消息來(lái)處理客戶的輸入,由于前幾節(jié)已經(jīng)講過(guò)本節(jié)也就不再重復(fù)。
同樣基于對(duì)話框的應(yīng)用也同樣可以使用屬性對(duì)話框做為界面,或者是通過(guò)使用經(jīng)過(guò)派生的通用對(duì)話框作為界面。
提示:當(dāng)你使用有模式對(duì)話框時(shí)最開(kāi)始是無(wú)法隱藏窗口的,而只能在對(duì)話框顯示后再隱藏窗口,所以這會(huì)造成屏幕的閃爍。一個(gè)解決辦法就是采用無(wú)模式的對(duì)話框,無(wú)模式的對(duì)話框在創(chuàng)建后是隱藏的,直到你調(diào)用ShowWindow(SW_SHOW)才會(huì)顯示。相關(guān)代碼如下:
BOOL CMy58_s1App::InitInstance()
{
//必須新生成一個(gè)對(duì)象,而不能使用局部變量
CMy58_s1Dlg* pdlg=new CMy58_s1Dlg;
m_pMainWnd = pdlg;
pdlg->Create();
return TRUE;
}
5.9 使用對(duì)話框作為子窗口
使用對(duì)話框作為子窗口是一種很常用的技術(shù),這樣可以使界面設(shè)計(jì)簡(jiǎn)化而且修改起來(lái)更加容易。
簡(jiǎn)單的說(shuō)這種技術(shù)的關(guān)鍵就在于創(chuàng)建一個(gè)無(wú)模式的對(duì)話框,并在編輯對(duì)話框資源時(shí)指明Child風(fēng)格和無(wú)邊框風(fēng)格,如圖。接下來(lái)利用產(chǎn)生一個(gè)CDialog的派生類,并進(jìn)行相關(guān)的消息映射。在創(chuàng)建子窗口時(shí)需要利用下面的代碼:
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
//創(chuàng)建子窗口
m_dlgChild.Create(IDD_CHILD_DLG,this);
//重新定位
m_dlgChild.MoveWindow(0,0,400,200);
//顯示窗口
m_dlgChild.ShowWindow(SW_SHOW);
return 0;
}
此外還有一中類似的技術(shù)是利用CFormView派生類作為子窗口,在編輯對(duì)話框資源時(shí)也需要指明Child風(fēng)格和無(wú)邊框風(fēng)格。然后利用ClassWizard產(chǎn)生以CFormView為基類的派生類,但是由于該類的成員函數(shù)都是受保護(hù)的,所以需要對(duì)產(chǎn)生的頭文件進(jìn)行如下修改:
class CTestForm : public CFormView
{
//將構(gòu)造函數(shù)和構(gòu)析函數(shù)改為共有函數(shù)
public:
CTestForm();
virtual ~CTestForm();
DECLARE_DYNCREATE(CTestForm)
...
}
有關(guān)創(chuàng)建子窗口的代碼如下:
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
//對(duì)于CFormView派生類必須新生成對(duì)象而不能使用成員對(duì)象
m_pformChild = new CTestForm;
//由于CFormView的成員受保護(hù),所以必須對(duì)指針進(jìn)行強(qiáng)制轉(zhuǎn)換
CWnd* pWnd=m_pformChild;
pWnd->Create(NULL,NULL,WS_CHILD|WS_VISIBLE,CRect(0,210,400,400)
,this,1001,NULL);
return 0;
}
最后你會(huì)看到如圖的窗口界面,上方的對(duì)話框子窗口和下方的FormView子窗口都可以通過(guò)資源編輯器預(yù)先編輯好。
提示:對(duì)于CFormView派生類必須新生成對(duì)象而不能使用成員對(duì)象,因?yàn)樵贑View的OnDestroy()中會(huì)有如下代碼:delete this;所以使用成員對(duì)象的結(jié)果會(huì)造成對(duì)象的二次刪除而引發(fā)異常。
****************************************************
6.1 WinSock介紹
Windows下網(wǎng)絡(luò)編程的規(guī)范-Windows Sockets是Windows下得到廣泛應(yīng)用的、開(kāi)放的、支持多種協(xié)議的網(wǎng)絡(luò)編程接口。從1991年的1.0版到1995年的2.0.8版,經(jīng)過(guò)不斷完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成為Windows網(wǎng)絡(luò)編程的事實(shí)上的標(biāo)準(zhǔn)。
Windows Sockets規(guī)范以U.C. Berkeley大學(xué)BSD UNIX中流行的Socket接口為范例定義了一套Micosoft Windows下網(wǎng)絡(luò)編程接口。它不僅包含了人們所熟悉的Berkeley Socket風(fēng)格的庫(kù)函數(shù);也包含了一組針對(duì)Windows的擴(kuò)展庫(kù)函數(shù),以使程序員能充分地利用Windows消息驅(qū)動(dòng)機(jī)制進(jìn)行編程。Windows Sockets規(guī)范本意在于提供給應(yīng)用程序開(kāi)發(fā)者一套簡(jiǎn)單的API,并讓各家網(wǎng)絡(luò)軟件供應(yīng)商共同遵守。此外,在一個(gè)特定版本W(wǎng)indows的基礎(chǔ)上,Windows Sockets也定義了一個(gè)二進(jìn)制接口(ABI),以此來(lái)保證應(yīng)用Windows Sockets API的應(yīng)用程序能夠在任何網(wǎng)絡(luò)軟件供應(yīng)商的符合Windows Sockets協(xié)議的實(shí)現(xiàn)上工作。因此這份規(guī)范定義了應(yīng)用程序開(kāi)發(fā)者能夠使用,并且網(wǎng)絡(luò)軟件供應(yīng)商能夠?qū)崿F(xiàn)的一套庫(kù)函數(shù)調(diào)用和相關(guān)語(yǔ)義。遵守這套Windows Sockets規(guī)范的網(wǎng)絡(luò)軟件,我們稱之為Windows Sockets兼容的,而Windows Sockets兼容實(shí)現(xiàn)的提供者,我們稱之為Windows Sockets提供者。一個(gè)網(wǎng)絡(luò)軟件供應(yīng)商必須百分之百地實(shí)現(xiàn)Windows Sockets規(guī)范才能做到現(xiàn)Windows Sockets兼容。任何能夠與Windows Sockets兼容實(shí)現(xiàn)協(xié)同工作的應(yīng)用程序就被認(rèn)為是具有Windows Sockets接口。我們稱這種應(yīng)用程序?yàn)閃indows Sockets應(yīng)用程序。Windows Sockets規(guī)范定義并記錄了如何使用API與Internet協(xié)議族(IPS,通常我們指的是TCP/IP)連接,尤其要指出的是所有的Windows Sockets實(shí)現(xiàn)都支持流套接口和數(shù)據(jù)報(bào)套接口.應(yīng)用程序調(diào)用Windows Sockets的API實(shí)現(xiàn)相互之間的通訊。Windows Sockets又利用下層的網(wǎng)絡(luò)通訊協(xié)議功能和操作系統(tǒng)調(diào)用實(shí)現(xiàn)實(shí)際的通訊工作。它們之間的關(guān)系如圖
通信的基礎(chǔ)是套接口(Socket),一個(gè)套接口是通訊的一端。在這一端上你可以找到與其對(duì)應(yīng)的一個(gè)名字。一個(gè)正在被使用的套接口都有它的類型和與其相關(guān)的進(jìn)程。套接口存在于通訊域中。通訊域是為了處理一般的線程通過(guò)套接口通訊而引進(jìn)的一種抽象概念。套接口通常和同一個(gè)域中的套接口交換數(shù)據(jù)(數(shù)據(jù)交換也可能穿越域的界限,但這時(shí)一定要執(zhí)行某種解釋程序)。Windows Sockets規(guī)范支持單一的通訊域,即Internet域。各種進(jìn)程使用這個(gè)域互相之間用Internet協(xié)議族來(lái)進(jìn)行通訊(Windows Sockets 1.1以上的版本支持其他的域,例如Windows Sockets 2)。套接口可以根據(jù)通訊性質(zhì)分類;這種性質(zhì)對(duì)于用戶是可見(jiàn)的。應(yīng)用程序一般僅在同一類的套接口間通訊。不過(guò)只要底層的通訊協(xié)議允許,不同類型的套接口間也照樣可以通訊。用戶目前可以使用兩種套接口,即流套接口和數(shù)據(jù)報(bào)套接口。流套接口提供了雙向的,有序的,無(wú)重復(fù)并且無(wú)記錄邊界的數(shù)據(jù)流服務(wù)。數(shù)據(jù)報(bào)套接口支持雙向的數(shù)據(jù)流,但并不保證是可靠,有序,無(wú)重復(fù)的。也就是說(shuō),一個(gè)從數(shù)據(jù)報(bào)套接口接收信息的進(jìn)程有可能發(fā)現(xiàn)信息重復(fù)了,或者和發(fā)出時(shí)的順序不同。數(shù)據(jù)報(bào)套接口的一個(gè)重要特點(diǎn)是它保留了記錄邊界。對(duì)于這一特點(diǎn),數(shù)據(jù)報(bào)套接口采用了與現(xiàn)在許多包交換網(wǎng)絡(luò)(例如以太網(wǎng))非常類似的模型。
一個(gè)在建立分布式應(yīng)用時(shí)最常用的范例便是客戶機(jī)/服務(wù)器模型。在這種方案中客戶應(yīng)用程序向服務(wù)器程序請(qǐng)求服務(wù)。這種方式隱含了在建立客戶機(jī)/服務(wù)器間通訊時(shí)的非對(duì)稱性。客戶機(jī)/服務(wù)器模型工作時(shí)要求有一套為客戶機(jī)和服務(wù)器所共識(shí)的慣例來(lái)保證服務(wù)能夠被提供(或被接受)。這一套慣例包含了一套協(xié)議。它必須在通訊的兩頭都被實(shí)現(xiàn)。根據(jù)不同的實(shí)際情況,協(xié)議可能是對(duì)稱的或是非對(duì)稱的。在對(duì)稱的協(xié)議中,每一方都有可能扮演主從角色;在非對(duì)稱協(xié)議中,一方被不可改變地認(rèn)為是主機(jī),而另一方則是從機(jī)。一個(gè)對(duì)稱協(xié)議的例子是Internet中用于終端仿真的TELNET。而非對(duì)稱協(xié)議的例子是Internet中的FTP。無(wú)論具體的協(xié)議是對(duì)稱的或是非對(duì)稱的,當(dāng)服務(wù)被提供時(shí)必然存在"客戶進(jìn)程"和"服務(wù)進(jìn)程"。一個(gè)服務(wù)程序通常在一個(gè)眾所周知的地址監(jiān)聽(tīng)對(duì)服務(wù)的請(qǐng)求,也就是說(shuō),服務(wù)進(jìn)程一直處于休眠狀態(tài),直到一個(gè)客戶對(duì)這個(gè)服務(wù)的地址提出了連接請(qǐng)求。在這個(gè)時(shí)刻,服務(wù)程序被"驚醒"并且為客戶提供服務(wù)-對(duì)客戶的請(qǐng)求作出適當(dāng)?shù)姆磻?yīng)。這一請(qǐng)求/相應(yīng)的過(guò)程可以簡(jiǎn)單的用圖表示。雖然基于連接的服務(wù)是設(shè)計(jì)客戶機(jī)/服務(wù)器應(yīng)用程序時(shí)的標(biāo)準(zhǔn),但有些服務(wù)也是可以通過(guò)數(shù)據(jù)報(bào)套接口提供的。
數(shù)據(jù)報(bào)套接口可以用來(lái)向許多系統(tǒng)支持的網(wǎng)絡(luò)發(fā)送廣播數(shù)據(jù)包。要實(shí)現(xiàn)這種功能,網(wǎng)絡(luò)本身必須支持廣播功能,因?yàn)橄到y(tǒng)軟件并不提供對(duì)廣播功能的任何模擬。廣播信息將會(huì)給網(wǎng)絡(luò)造成極重的負(fù)擔(dān),因?yàn)樗鼈円缶W(wǎng)絡(luò)上的每臺(tái)主機(jī)都為它們服務(wù),所以發(fā)送廣播數(shù)據(jù)包的能力被限制于那些用顯式標(biāo)記了允許廣播的套接口中。廣播通常是為了如下兩個(gè)原因而使用的:1. 一個(gè)應(yīng)用程序希望在本地網(wǎng)絡(luò)中找到一個(gè)資源,而應(yīng)用程序?qū)υ撡Y源的地址又沒(méi)有任何先驗(yàn)的知識(shí)。2. 一些重要的功能,例如路由要求把它們的信息發(fā)送給所有可以找到的鄰機(jī)。被廣播信息的目的地址取決于這一信息將在何種網(wǎng)絡(luò)上廣播。Internet域中支持一個(gè)速記地址用于廣播-INADDR_BROADCAST。由于使用廣播以前必須捆綁一個(gè)數(shù)據(jù)報(bào)套接口,所以所有收到的廣播消息都帶有發(fā)送者的地址和端口。
Intel處理器的字節(jié)順序是和DEC VAX處理器的字節(jié)順序一致的。因此它與68000型處理器以及Internet的順序是不同的,所以用戶在使用時(shí)要特別小心以保證正確的順序。任何從Windows Sockets函數(shù)對(duì)IP地址和端口號(hào)的引用和傳送給Windows Sockets函數(shù)的IP地址和端口號(hào)均是按照網(wǎng)絡(luò)順序組織的,這也包括了sockaddr_in結(jié)構(gòu)這一數(shù)據(jù)類型中的IP地址域和端口域(但不包括sin_family域)。考慮到一個(gè)應(yīng)用程序通常用與"時(shí)間"服務(wù)對(duì)應(yīng)的端口來(lái)和服務(wù)器連接,而服務(wù)器提供某種機(jī)制來(lái)通知用戶使用另一端口。因此getservbyname()函數(shù)返回的端口號(hào)已經(jīng)是網(wǎng)絡(luò)順序了,可以直接用來(lái)組成一個(gè)地址,而不需要進(jìn)行轉(zhuǎn)換。然而如果用戶輸入一個(gè)數(shù),而且指定使用這一端口號(hào),應(yīng)用程序則必須在使用它建立地址以前,把它從主機(jī)順序轉(zhuǎn)換成網(wǎng)絡(luò)順序(使用htons()函數(shù))。相應(yīng)地,如果應(yīng)用程序希望顯示包含于某一地址中的端口號(hào)(例如從getpeername()函數(shù)中返回的),這一端口號(hào)就必須在被顯示前從網(wǎng)絡(luò)順序轉(zhuǎn)換到主機(jī)順序(使用ntohs()函數(shù))。由于Intel處理器和Internet的字節(jié)順序是不同的,上述的轉(zhuǎn)換是無(wú)法避免的,應(yīng)用程序的編寫者應(yīng)該使用作為Windows Sockets API一部分的標(biāo)準(zhǔn)的轉(zhuǎn)換函數(shù),而不要使用自己的轉(zhuǎn)換函數(shù)代碼。因?yàn)閷?lái)的Windows Sockets實(shí)現(xiàn)有可能在主機(jī)字節(jié)順序與網(wǎng)絡(luò)字節(jié)順序相同的機(jī)器上運(yùn)行。因此只有使用標(biāo)準(zhǔn)的轉(zhuǎn)換函數(shù)的應(yīng)用程序是可移植的。
在MFC中MS為套接口提供了相應(yīng)的類CAsyncSocket和CSocket,CAsyncSocket提供基于異步通信的套接口封裝功能,CSocket則是由CAsyncSocket派生,提供更加高層次的功能,例如可以將套接口上發(fā)送和接收的數(shù)據(jù)和一個(gè)文件對(duì)象(CSocketFile)關(guān)聯(lián)起來(lái),通過(guò)讀寫文件來(lái)達(dá)到發(fā)送和接收數(shù)據(jù)的目的,此外CSocket提供的通信為同步通信,數(shù)據(jù)未接收到或是未發(fā)送完之前調(diào)用不會(huì)返回。此外通過(guò)MFC類開(kāi)發(fā)者可以不考慮網(wǎng)絡(luò)字節(jié)順序和忽略掉更多的通信細(xì)節(jié)。
在一次網(wǎng)絡(luò)通信/連接中有以下幾個(gè)參數(shù)需要被設(shè)置:本地IP地址 - 本地端口號(hào) - 對(duì)方端口號(hào) - 對(duì)方IP地址。左邊兩部分稱為一個(gè)半關(guān)聯(lián),當(dāng)與右邊兩部分建立連接后就稱為一個(gè)全關(guān)聯(lián)。在這個(gè)全關(guān)聯(lián)的套接口上可以雙向的交換數(shù)據(jù)。如果是使用無(wú)連接的通信則只需要建立一個(gè)半關(guān)聯(lián),在發(fā)送和接收時(shí)指明另一半的參數(shù)就可以了,所以可以說(shuō)無(wú)連接的通信是將數(shù)據(jù)發(fā)送到另一臺(tái)主機(jī)的指定端口。此外不論是有連接還是無(wú)連接的通信都不需要雙方的端口號(hào)相同。
在創(chuàng)建CAsyncSocket對(duì)象時(shí)通過(guò)調(diào)用
BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL )通過(guò)指明lEvent所包含的標(biāo)記來(lái)確定需要異步處理的事件,對(duì)于指明的相關(guān)事件的相關(guān)函數(shù)調(diào)用都不需要等待完成后才返回,函數(shù)會(huì)馬上返回然后在完成任務(wù)后發(fā)送事件通知,并利用重載以下成員函數(shù)來(lái)處理各種網(wǎng)絡(luò)事件: 標(biāo)記 事件 需要重載的函數(shù)
FD_READ 有數(shù)據(jù)到達(dá)時(shí)發(fā)生 void OnReceive( int nErrorCode );
FD_WRITE 有數(shù)據(jù)發(fā)送時(shí)產(chǎn)生 void OnSend( int nErrorCode );
FD_OOB 收到外帶數(shù)據(jù)時(shí)發(fā)生 void OnOutOfBandData( int nErrorCode );
FD_ACCEPT 作為服務(wù)端等待連接成功時(shí)發(fā)生 void OnAccept( int nErrorCode );
FD_CONNECT 作為客戶端連接成功時(shí)發(fā)生 void OnConnect( int nErrorCode );
FD_CLOSE 套接口關(guān)閉時(shí)發(fā)生 void OnClose( int nErrorCode );
我們看到重載的函數(shù)中都有一個(gè)參數(shù)nErrorCode,為零則表示正常完成,非零則表示錯(cuò)誤。通過(guò)int CAsyncSocket::GetLastError()可以得到錯(cuò)誤值。
下面我們看看套接口類所提供的一些功能,通過(guò)這些功能我們可以方便的建立網(wǎng)絡(luò)連接和發(fā)送數(shù)據(jù)。
BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );用于創(chuàng)建一個(gè)本地套接口,其中nSocketPort為使用的端口號(hào),為零則表示由系統(tǒng)自動(dòng)選擇,通常在客戶端都使用這個(gè)選擇。nSocketType為使用的協(xié)議族,SOCK_STREAM表明使用有連接的服務(wù),SOCK_DGRAM表明使用無(wú)連接的數(shù)據(jù)報(bào)服務(wù)。lpszSocketAddress為本地的IP地址,可以使用點(diǎn)分法表示如10.1.1.3。
BOOL CAsyncSocket::Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )作為等待連接方時(shí)產(chǎn)生一個(gè)網(wǎng)絡(luò)半關(guān)聯(lián),或者是使用UDP協(xié)議時(shí)產(chǎn)生一個(gè)網(wǎng)絡(luò)半關(guān)聯(lián)。
BOOL CAsyncSocket::Listen( int nConnectionBacklog = 5 )作為等待連接方時(shí)指明同時(shí)可以接受的連接數(shù),請(qǐng)注意不是總共可以接受的連接數(shù)。
BOOL CAsyncSocket::Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )作為等待連接方將等待連接建立,當(dāng)連接建立后一個(gè)新的套接口將被創(chuàng)建,該套接口將會(huì)被用于通信。
BOOL CAsyncSocket::Connect( LPCTSTR lpszHostAddress, UINT nHostPort );作為連接方發(fā)起與等待連接方的連接,需要指明對(duì)方的IP地址和端口號(hào)。
void CAsyncSocket::Close( );關(guān)閉套接口。
int CAsyncSocket::Send( const void* lpBuf, int nBufLen, int nFlags = 0 )
int CAsyncSocket::Receive( void* lpBuf, int nBufLen, int nFlags = 0 );在建立連接后發(fā)送和接收數(shù)據(jù),nFlags為標(biāo)記位,雙方需要指明相同的標(biāo)記。
int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )
int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 );對(duì)于無(wú)連接通信發(fā)送和接收數(shù)據(jù),需要指明對(duì)方的IP地址和端口號(hào),nFlags為標(biāo)記位,雙方需要指明相同的標(biāo)記。
我們可以看到大多數(shù)的函數(shù)都返回一個(gè)布爾值表明是否成功。如果發(fā)生錯(cuò)誤可以通過(guò)int CAsyncSocket::GetLastError()得到錯(cuò)誤值。
由于CSocket由CAsyncSocket派生所以擁有CAsyncSocket的所有功能,此外你可以通過(guò)BOOL CSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL )來(lái)創(chuàng)建套接口,這樣創(chuàng)建的套接口沒(méi)有辦法異步處理事件,所有的調(diào)用都必需完成后才會(huì)返回。
在上面的介紹中我們看到MFC提供的套接口類屏蔽了大多數(shù)的細(xì)節(jié),我們只需要做很少的工作就可以開(kāi)發(fā)出利用網(wǎng)絡(luò)進(jìn)行通信的軟件。
6.2 利用WinSock進(jìn)行無(wú)連接的通信
WinSock提供了對(duì)UDP(用戶數(shù)據(jù)報(bào)協(xié)議)的支持,通過(guò)UDP協(xié)議我們可以向指定IP地址的主機(jī)發(fā)送數(shù)據(jù),同時(shí)也可以從指定IP地址的主機(jī)接收數(shù)據(jù),發(fā)送和接收方處于相同的地位沒(méi)有主次之分。利用CSocket操縱無(wú)連接的數(shù)據(jù)發(fā)送很簡(jiǎn)單,首先生成一個(gè)本地套接口(需要指明SOCK_DGRAM標(biāo)記),然后利用
int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )發(fā)送數(shù)據(jù),
int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 )接收數(shù)據(jù)。函數(shù)調(diào)用順序如圖。
利用UDP協(xié)議發(fā)送和接收都可以是雙向的,就是說(shuō)任何一個(gè)主機(jī)都可以發(fā)送和接收數(shù)據(jù)。但是UDP協(xié)議是無(wú)連接的,所以發(fā)送的數(shù)據(jù)不一定能被接收,此外接收的順序也有可能與發(fā)送順序不一致。下面是相關(guān)代碼:
/*
發(fā)送方在端口6800上向接收方端口6801發(fā)送數(shù)據(jù)
*/
//發(fā)送方代碼:
BOOL CMy62_s1_clientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//創(chuàng)建本地套接口
m_sockSend.Create(6800,SOCK_DGRAM,NULL);
//綁定本地套接口
m_sockSend.Bind(6800,"127.0.0.1");
//創(chuàng)建一個(gè)定時(shí)器定時(shí)發(fā)送
SetTimer(1,3000,NULL);
...
}
void CMy62_s1_clientDlg::OnTimer(UINT nIDEvent)
{
static iIndex=0;
char szSend[20];
sprintf(szSend,"%010d",iIndex++);
//發(fā)送UDP數(shù)據(jù)
int iSend= m_sockSend.SendTo(szSend,10,6801,"127.0.0.1",0);
TRACE("sent %d byte\n",iSend);
...
}
//接收方代碼
BOOL CMy62_s1_serverDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//創(chuàng)建本地套接口
m_sockRecv.Create(6801,SOCK_DGRAM,"127.0.0.1");
//綁定本地套接口
m_sockRecv.Bind(6801,"127.0.0.1");
//創(chuàng)建一個(gè)定時(shí)器定時(shí)讀取
SetTimer(1,3000,NULL);
...
}
void CMy62_s1_serverDlg::OnTimer(UINT nIDEvent)
{
char szRecv[20];
CString szIP("127.0.0.1");
UINT uPort=6800;
//接收UDP數(shù)據(jù)
int iRecv =m_sockRecv.ReceiveFrom(szRecv,10,szIP,uPort,0);
TRACE("received %d byte\n",iRecv);
...
}
/*
接收方采用同步讀取數(shù)據(jù)的方式,所以沒(méi)有讀到數(shù)據(jù)函數(shù)調(diào)用將不會(huì)返回
*/
下載例子代碼,62_s1_client工程為發(fā)送方,62_s1_server工程為接收方
6.3 利用WinSock進(jìn)行有連接的通信
WinSock提供了對(duì)TCP(傳輸控制協(xié)議)的支持,通過(guò)TCP協(xié)議我們可以與指定IP地址的主機(jī)建立,同時(shí)利用建立的連接可以雙向的交換數(shù)據(jù)。利用CSocket操縱有連接數(shù)據(jù)交換很簡(jiǎn)單,但是在有連接的通信中必需有一方扮演服務(wù)器的角色等待另一方(客戶方)的連接請(qǐng)求,所以服務(wù)器方需要建立一個(gè)監(jiān)聽(tīng)套接口,然后在此套接口上等待連接。當(dāng)連接建立后會(huì)產(chǎn)生一個(gè)新的套接口用于通信。而客戶方在創(chuàng)建套接口后只需要簡(jiǎn)單的調(diào)用連接函數(shù)就可以創(chuàng)建連接。對(duì)于有連接的通信不論是數(shù)據(jù)的發(fā)送還是發(fā)送與接收的順序都是有保證的。雙方的函數(shù)調(diào)用順序如圖。
下面的代碼演示了如何建立連接和發(fā)送/接收數(shù)據(jù):
/*
服務(wù)器方在端口6802上等待連接,當(dāng)連接建立后關(guān)閉監(jiān)聽(tīng)套接口
客戶方向服務(wù)器端口6802發(fā)起連接請(qǐng)求
*/
BOOL CMy63_s1_serverDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CSocket sockListen;
//創(chuàng)建本地套接口
sockListen.Create(6802,SOCK_STREAM,"127.0.0.1");
//綁定參數(shù)
sockListen.Bind(6802,"127.0.0.1");
sockListen.Listen(5);
//等待連接請(qǐng)求,m_sockSend為成員變量,用于通信
sockListen.Accept(m_sockSend);
//關(guān)閉監(jiān)聽(tīng)套接口
sockListen.Close();
//啟動(dòng)定時(shí)器,定時(shí)發(fā)送數(shù)據(jù)
SetTimer(1,3000,NULL);
...
}
void CMy63_s1_serverDlg::OnTimer(UINT nIDEvent)
{
static iIndex=0;
char szSend[20];
sprintf(szSend,"%010d",iIndex++);
//發(fā)送TCP數(shù)據(jù)
int iSend= m_sockSend.Send(szSend,10,0);
...
}
BOOL CMy63_s1_clientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//創(chuàng)建本地套接口
m_sockRecv.Create();
//發(fā)起連接請(qǐng)求
BOOL fC=m_sockRecv.Connect("127.0.0.1",6802);
TRACE("connect is %s\n",(fC)?"OK":"Error");
//啟動(dòng)定時(shí)器,定時(shí)接收數(shù)據(jù)
SetTimer(1,3000,NULL);
...
}
void CMy63_s1_clientDlg::OnTimer(UINT nIDEvent)
{
char szRecv[20];
//接收TCP數(shù)據(jù)
int iRecv =m_sockRecv.Receive(szRecv,10,0);
TRACE("received %d byte\n",iRecv);
if(iRecv>=0)
{
szRecv[iRecv]='\0';
m_szRecv=szRecv;
UpdateData(FALSE);
}
...
}
下載例子代碼,63_s1_client工程為客戶,63_s1_server工程為服務(wù)器方。