Visual C++/MFC入門教程

jsjliuyun發表於2014-04-14

轉自http://www.cppblog.com/tim/articles/21924.html,感覺總結的還可以,和大家共享一下哈!

Visual C++/MFC入門教程

VC開發指南

  講述Visual C++/MFC開發的基本知識,文件/視結構,窗體控制元件的使用和一些基本的網路開發知識。同時指出一些在開發中容易犯的錯誤和一些注意事項。本教程主要側重於講解MFC中各個類的使用和函式功能,同時在重要內容上都帶有例程。

整個教程分為六章,分別為

+-- 第一章 VC入門 
|------ 1.1 如何學好VC 
|------ 1.2 理解Windows訊息機制 
|------ 1.3 利用Visual C++/MFC開發Windows程式的優勢 
|------ 1.4 利用MFC進行開發的通用方法介紹
|------ 1.5 MFC中常用類,巨集,函式介紹
+-- 第二章 圖形輸出 
|------ 2.1 和GUI有關的各種物件 
|------ 2.2 在視窗中輸出文字 
|------ 2.3 使用點,刷子,筆進行繪圖 
|------ 2.4 在視窗中繪製裝置相關點陣圖,圖示,裝置無關點陣圖
|------ 2.5 使用各種對映方式 
|------ 2.6 多邊形和剪貼區域
+-- 第三章 文件視結構 
|------ 3.1 文件 檢視 框架視窗間的關係和訊息傳送規律
|------ 3.2 接收使用者輸入 
|------ 3.3 使用選單 
|------ 3.4 文件,視,框架之 間相互作用 
|------ 3.5 利用序列化進行檔案讀寫
|------ 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建立並使用ToolBar StatusBar Dialog Bar 
|------ 4.E General Window
+-- 第五章 對話方塊 
|------ 5.1 使用資源編輯器編輯對話方塊
|------ 5.2 建立有模式對話方塊 
|------ 5.3 建立無模式對話方塊
|------ 5.4 在對話方塊中進行訊息對映 
|------ 5.5 在對話方塊中進行資料交換和資料檢查
|------ 5.6 使用屬性對話方塊 
|------ 5.7 使用通用對話方塊 
|------ 5.8 建立以對話方塊為基礎的應用
|------ 5.9 使用對話方塊作為子視窗
+-- 第六章 網路通訊開發 
|------ 6.1 WinSock介紹
|------ 6.2 利用WinSock進行無連線的通訊 
+------ 6.3 利用WinSock建立有連線的通訊

 

 


1.1 如何學好VC

  這個問題很多朋友都問過我,當然流汗是必須的,但同時如果按照某種思路進行有計劃的學習就會起到更好的效果。萬事開頭難,為了幫助朋友們更快的掌握VC開發,下面我將自己的一點體會講一下:

  1、需要有好的C/C++基礎。正所謂“磨刀不誤砍柴工”,最開始接觸VC時不要急於開始Windows程式開發,而是應該進行一些字元介面程式的編寫。這樣做的目的主要是增加對語言的熟悉程度,同時也訓練自己的思維和熟悉一些在程式設計中常犯的錯誤。更重要的是理解並能運用C++的各種特性,這些在以後的開發中都會有很大的幫助,特別是利用MFC進行開發的朋友對C++一定要能熟練運用。

  2、理解Windows的訊息機制,視窗控制程式碼和其他GUI控制程式碼的含義和用途。瞭解和MFC各個類功能相近的API函式。

  3、一定要理解MFC中訊息對映的作用。

  4、訓練自己在編寫程式碼時不使用參考書而是使用Help Online。

  5、記住一些常用的訊息名稱和引數的意義。

  6、學會看別人的程式碼。

  7、多看書,少買書,買書前一定要慎重。

  8、閒下來的時候就看參考書。

  9、多來我的主頁。^O^

  後面幾條是我個人的一點意見,你可以根據需要和自身的情況選用適用於自己的方法。

  此外我將一些我在選擇參考書時的原則:

  對於初學者:應該選擇一些內容比較全面的書籍,並且書籍中的內容應該以合理的方式安排,在使用該書時可以達到循序漸進的效果,書中的程式碼要有詳細的講解。儘量買翻譯的書,因為這些書一般都比較易懂,而且語言比較輕鬆。買書前一定要慎重如果買到不好用的書可能會對自己的學習積極性產生打擊。

  對於已經掌握了VC的朋友:這種程度的開發者應該加深自己對系統原理,技術要點的認識。需要選擇一些對原理講解的比較透徹的書籍,這樣一來才會對新技術有更多的瞭解,最好書中對技術的應用有一定的闡述。儘量選擇示範程式碼必較精簡的書,可以節約銀子。

  此外最好涉獵一些輔助性的書籍。
1.2 理解Windows訊息機制

Windows系統是一個訊息驅動的OS,什麼是訊息呢?我很難說得清楚,也很難下一個定義(誰在噓我),我下面從不同的幾個方面講解一下,希望大家看了後有一點了解。

1、訊息的組成:一個訊息由一個訊息名稱(UINT),和兩個引數(WPARAM,LPARAM)。當使用者進行了輸入或是視窗的狀態發生改變時系統都會傳送訊息到某一個視窗。例如當選單轉中之後會有WM_COMMAND訊息傳送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對選單來講就是選單ID。當然使用者也可以定義自己的訊息名稱,也可以利用自定義訊息來傳送通知和傳送資料。

2、誰將收到訊息:一個訊息必須由一個視窗接收。在視窗的過程(WNDPROC)中可以對訊息進行分析,對自己感興趣的訊息進行處理。例如你希望對選單選擇進行處理那麼你可以定義對WM_COMMAND進行處理的程式碼,如果希望在視窗中進行圖形輸出就必須對WM_PAINT進行處理。

3、未處理的訊息到那裡去了:M$為視窗編寫了預設的視窗過程,這個視窗過程將負責處理那些你不處理訊息。正因為有了這個預設視窗過程我們才可以利用Windows的視窗進行開發而不必過多關注視窗各種訊息的處理。例如視窗在被拖動時會有很多訊息傳送,而我們都可以不予理睬讓系統自己去處理。

4、視窗控制程式碼:說到訊息就不能不說視窗控制程式碼,系統通過視窗控制程式碼來在整個系統中唯一標識一個視窗,傳送一個訊息時必須指定一個視窗控制程式碼表明該訊息由那個視窗接收。而每個視窗都會有自己的視窗過程,所以使用者的輸入就會被正確的處理。例如有兩個視窗共用一個視窗過程程式碼,你在視窗一上按下滑鼠時訊息就會通過視窗一的控制程式碼被髮送到視窗一而不是視窗二。

5、示例:下面有一段虛擬碼演示如何在視窗過程中處理訊息

LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
 switch(uMessageType)
 {//使用SWITCH語句將各種訊息分開
  case(WM_PAINT):
   doYourWindow(...);//在視窗需要重新繪製時進行輸出
  break;
  case(WM_LBUTTONDOWN):
   doYourWork(...);//在滑鼠左鍵被按下時進行處理
  break;
  default:
   callDefaultWndProc(...);//對於其它情況就讓系統自己處理
  break;
 }
}


接下來談談什麼是訊息機制:系統將會維護一個或多個訊息佇列,所有產生的訊息都回被放入或是插入佇列中。系統會在佇列中取出每一條訊息,根據訊息的接收控制程式碼而將該訊息傳送給擁有該視窗的程式的訊息迴圈。每一個執行的程式都有自己的訊息迴圈,在迴圈中得到屬於自己的訊息並根據接收視窗的控制程式碼呼叫相應的視窗過程。而在沒有訊息時訊息迴圈就將控制權交給系統所以Windows可以同時進行多個任務。下面的虛擬碼演示了訊息迴圈的用法:

while(1)
{
 id=getMessage(...);
 if(id == quit)
  break;
 translateMessage(...);
}

當該程式沒有訊息通知時getMessage就不會返回,也就不會佔用系統的CPU時間。 圖示訊息投遞模式

在16位的系統中系統中只有一個訊息佇列,所以系統必須等待當前任務處理訊息後才可以傳送下一訊息到相應程式,如果一個程式陷如死迴圈或是耗時操作時系統就會得不到控制權。這種多工系統也就稱為協同式的多工系統。Windows3.X就是這種系統。

而32位的系統中每一執行的程式都會有一個訊息佇列,所以系統可以在多個訊息佇列中轉換而不必等待當前程式完成訊息處理就可以得到控制權。這種多工系統就稱為搶先式的多工系統。Windows95/NT就是這種系統。

1.3 利用Visual C++/MFC開發Windows程式的優勢

MFC藉助C++的優勢為Windows開發開闢了一片新天地,同時也藉助ApplicationWizzard使開發者擺脫離了那些每次都必寫基本程式碼,藉助ClassWizard和訊息對映使開發者擺脫了定義訊息處理時那種混亂和冗長的程式碼段。更令人興奮的是利用C++的封裝功能使開發者擺脫Windows中各種控制程式碼的困擾,只需要面對C++中的物件,這樣一來使開發更接近開發語言而遠離系統。(但我個人認為了解系統原理對開發很有幫助)

正因為MFC是建立在C++的基礎上,所以我強調C/C++語言基礎對開發的重要性。利用C++的封裝性開發者可以更容易理解和操作各種視窗物件;利用C++的派生性開發者可以減少開發自定義視窗的時間和創造出可重用的程式碼;利用虛擬性可以在必要時更好的控制視窗的活動。而且C++本身所具備的超越C語言的特性都可以使開發者編寫出更易用,更靈活的程式碼。

在MFC中對訊息的處理利用了訊息對映的方法,該方法的基礎是巨集定義實現,通過巨集定義將訊息分派到不同的成員函式進行處理。下面簡單講述一下這種方法的實現方法:

程式碼如下
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) 
//{{AFX_MSG_MAP(CMainFrame)
 ON_WM_CREATE() 
//}}AFX_MSG_MAP
 ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
END_MESSAGE_MAP()
經過編譯後,程式碼被替換為如下形式(這只是作講解,實際情況比這複雜得多):
//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就是視窗過程只要是該類的例項生成的視窗都使用該視窗過程。

所以瞭解了Windows的訊息機制在加上對訊息對映的理解就很容易瞭解MFC開發的基本思路了。

1.4 利用MFC進行開發的通用方法介紹


以下是我在最初學習VC時所常用的開發思路和方法,希望能對初學VC的朋友有所幫助和啟發。

1、開發需要讀寫檔案的應用程式並且有簡單的輸入和輸出可以利用單文件視結構。

2、開發注重互動的簡單應用程式可以使用對話方塊為基礎的視窗,如果檔案讀寫簡單這可利用CFile進行。

3、開發注重互動並且檔案讀寫複雜的的簡單應用程式可以利用以CFormView為基礎視的單文件視結構。

4、利用對話方塊得到使用者輸入的資料,在等級提高後可使用就地輸入。

5、在對多文件要求不強烈時儘量避免多文件視結構,可以利用分隔條產生單文件多視結構。

6、在要求在多個文件間傳遞資料時使用多文件視結構。

7、學會利用子視窗,並在自定義的子視窗包含多個控制元件達到封裝功能的目的。

8、儘量避免使用多文件多視結構。

9、不要使用多重繼承並儘量減少一個類中封裝過多的功能。


1.5 MFC中常用類,巨集,函式介紹

常用類

CRect:用來表示矩形的類,擁有四個成員變數:top left bottom right。分別表是左上角和右下角的座標。可以通過以下的方法構造:

CRect( int l, int t, int r, int b ); 指明四個座標

CRect( const RECT& srcRect ); 由RECT結構構造

CRect( LPCRECT lpSrcRect ); 由RECT結構構造

CRect( POINT point, SIZE size ); 有左上角座標和尺寸構造

CRect( POINT topLeft, POINT bottomRight ); 有兩點座標構造

下面介紹幾個成員函式:

int Width( ) const; 得到寬度 
int Height( ) const; 得到高度 
CSize Size( ) const; 得到尺寸 
CPoint& TopLeft( ); 得到左上角座標 
CPoint& BottomRight( ); 得到右下角座標 
CPoint CenterPoint( ) const; 得當中心座標 
此外矩形可以和點(CPoint)相加進行位移,和另一個矩形相加得到“並”操作後的矩形。

CPoint:用來表示一個點的座標,有兩個成員變數:x y。 可以和另一個點相加。

CString:用來表示可變長度的字串。使用CString可不指明記憶體大小,CString會根據需要自行分配。下面介紹幾個成員函式:

GetLength 得到字串長度 
GetAt 得到指定位置處的字元 
operator + 相當於strcat 
void Format( LPCTSTR lpszFormat, ... ); 相當於sprintf 
Find 查詢指定字元,字串 
Compare 比較 
CompareNoCase 不區分大小寫比較 
MakeUpper 改為小寫 
MakeLower 改為大寫

CStringArray:用來表示可變長度的字串陣列。陣列中每一個元素為CString物件的例項。下面介紹幾個成員函式:

Add 增加CString 
RemoveAt 刪除指定位置CString物件 
RemoveAll 刪除陣列中所有CString物件 
GetAt 得到指定位置的CString物件 
SetAt 修改指定位置的CString物件 
InsertAt 在某一位置插入CString物件

常用巨集

RGB

TRACE

ASSERT

VERIFY


常用函式

CWindApp* AfxGetApp();

HINSTANCE AfxGetInstanceHandle( );

HINSTANCE AfxGetResourceHandle( );

int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用於彈出一個訊息框

 

************************    
2.1 和GUI有關的各種物件

在Windows中有各種GUI物件(不要和C++物件混淆),當你在進行繪圖就需要利用這些物件。而各種物件都擁有各種屬性,下面分別講述各種GUI物件和擁有的屬性。

字型物件CFont用於輸出文字時選用不同風格和大小的字型。可選擇的風格包括:是否為斜體,是否為粗體,字型名稱,是否有下劃線等。顏色和背景色不屬於字型的屬性。關於如何建立和使用字型在2.2 在視窗中輸出文字中會詳細講解。

刷子CBrush物件決定填充區域時所採用的顏色或模板。對於一個固定色的刷子來講它的屬性為顏色,是否採用網格和網格的型別如水平的,垂直的,交叉的等。你也可以利用8*8的點陣圖來建立一個自定義模板的刷子,在使用這種刷子填充時系統會利用點陣圖逐步填充區域。關於如何建立和使用刷子在2.3 使用刷子,筆進行繪圖中會詳細講解。

畫筆CPen物件在畫點和畫線時有用。它的屬性包括顏色,寬度,線的風格,如虛線,實線,點劃線等。關於如何建立和使用畫筆在2.3 使用刷子,筆進行繪圖中會詳細講解。

點陣圖CBitmap物件可以包含一幅影像,可以儲存在資源中。關於如何使用點陣圖在2.4 在視窗中繪製裝置相關點陣圖,圖示,裝置無關點陣圖中會詳細講解。

還有一種特殊的GUI物件是多邊形,利用多邊形可以很好的限制作圖區域或是改變視窗外型。關於如何建立和使用多邊形在2.6 多邊形和剪貼區域中會詳細講解。

在Windows中使用GUI物件必須遵守一定的規則。首先需要建立一個合法的物件,不同的物件建立方法不同。然後需要將該GUI物件選入DC中,同時儲存DC中原來的GUI物件。如果選入一個非法的物件將會引起異常。在使用完後應該恢復原來的物件,這一點特別重要,如果儲存一個臨時物件在DC中,而在臨時物件被銷燬後可能引起異常。有一點必須注意,每一個物件在重新建立前必須銷燬,下面的程式碼演示了這一種安全的使用方法:

OnDraw(CDC* pDC)
{
 CPen pen1,pen2;
 pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//建立物件
 pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//建立物件
 CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//選擇物件進DC
 drawWithPen1...
 (CPen*)pDC->SelectObject(&pen2);//選擇物件進DC
 drawWithPen2...
 pen1.DeleteObject();//再次建立前先銷燬
 pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次建立物件
 (CPen*)pDC->SelectObject(&pen1);//選擇物件進DC
 drawWithPen1...
 pDC->SelectObject(pOldPen);//恢復
}


此外系統中還擁有一些庫存GUI物件,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )選入這些物件,它們包括一些固定顏色的刷子,畫筆和一些基本字型。

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. 
這些物件留在DC中是安全的,所以你可以利用選入庫存物件來作為恢復DC中GUI物件。

大家可能都注意到了繪圖時都需要一個DC物件,DC(Device Context裝置環境)物件是一個抽象的作圖環境,可能是對應螢幕,也可能是對應印表機或其它。這個環境是裝置無關的,所以你在對不同的裝置輸出時只需要使用不同的裝置環境就行了,而作圖方式可以完全不變。這也就是Windows耀眼的一點裝置無關性。如同你將對一幅畫使用照相機或影印機將會產生不同的輸出,而不需要對畫進行任何調整。DC的使用會穿插在本章中進行介紹。

******************   
2.2 在視窗中輸出文字

在這裡我假定讀者已經利用ApplicationWizard生成了一個SDI介面的程式程式碼。接下來的你只需要在CView派生類的OnDraw成員函式中加入繪圖程式碼就可以了。在這裡我需要解釋一下OnDraw函式的作用,OnDraw函式會在視窗需要重繪時自動被呼叫,傳入的引數CDC* pDC對應的就是DC環境。使用OnDraw的優點就在於在你使用列印功能的時候傳入OnDraw的DC環境將會是印表機繪圖環境,使用列印預覽時傳入的是一個稱為CPreviewDC的繪圖環境,所以你只需要一份程式碼就可以完成視窗/列印預覽/印表機繪圖三重功能。利用Windows的裝置無關性和M$為列印預覽所編寫的上千行程式碼你可以很容易的完成一個具有所見即所得的軟體。

輸出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )兩個函式,對TextOut來講只能輸出單行的文字,而DrawText可以指定在一個矩形中輸出單行或多行文字,並且可以規定對齊方式和使用何種風格。nFormat可以是多種以下標記的組合(利用位或操作)以達到選擇輸出風格的目的。 
DT_BOTTOM底部對齊   Specifies bottom-justified text. This value must be combined with DT_SINGLELINE.
DT_CALCRECT計算指定文字時所需要矩形尺寸   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中部對齊   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左對齊   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右對齊   Aligns text flush-right.
DT_SINGLELINE單行輸出   Specifies single line only. Carriage returns and linefeeds do not break the line.
DT_TABSTOP設定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定部對齊   Specifies top-justified text (single line only).
DT_VCENTER中部對齊   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. 
在輸出文字時如果希望改變文字的顏色,你可以利用CDC::SetTextColor( COLORREF crColor )進行設定,如果你希望改變背景色就利用CDC::SetBkColor( COLORREF crColor ),很多時候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )設定,可接受的引數有 
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. 
接下來講講如何建立字型,你可以建立的字型有兩種:庫存字型CDC::CreateStockObject( int nIndex )和自定義字型。在建立非庫存字型時需要填充一個LOGFONT結構並使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont )(可以參考文章在同一系統中顯示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 )其中的引數和LOGFONT中的分量有一定的對應關係。下面分別講解引數的意義:
nHeight 字型高度(邏輯單位)等於零為預設高度,否則取絕對值並和可用的字型高度進行匹配。nWidth 寬度(邏輯單位)如果為零則使用可用的橫縱比進行匹配。nEscapement 出口向量與X軸間的角度nOrientation 字型基線與X軸間的角度nWeight 字型粗細,可取以下值
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 輸出質量,可取以下值
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 字型間的間距lpszFacename 指定字型名稱,為了得到系統所擁有的字型可以利用EmunFontFamiliesEx。(可以參考文章在同一系統中顯示GB字元和BIG5字元)
此外可以利用CFontDialog來得到使用者選擇的字型的LOGFONT資料。
最後我講一下文字座標的計算,利用CDC::GetTextExtent( const CString& str )可以得到字串的在輸出時所佔用的寬度和高度,這樣就可以在手工輸出多行文字時使用正確的行距。另外如果需要更精確的對字型高度和寬度進行計算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 該函式將會填充TEXTMETRIC結構,該結構中的分量可以非常精確的描述字型的各種屬性。


2.3 使用點,刷子,筆進行繪圖

在Windows中畫點的方法很簡單,只需要呼叫COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定點畫上指定顏色,同時返回原來的顏色。COLORREF CDC::GetPixel( int x, int y)可以得到指定點的顏色。在Windows中應該少使用畫點的函式,因為這樣做的執行效率比較低。

刷子和畫筆在Windows作圖中是使用最多的GUI物件,本節在講解刷子和畫筆使用方法的同時也講述一寫基本作圖函式。

在畫點或畫線時系統使用當前DC中的畫筆,所以在建立畫筆後必須將其選入DC才會在繪圖時產生效果。畫筆可以通過CPen物件來產生,通過呼叫CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )來建立。其中nPenStyle指名畫筆的風格,可取如下值:

PS_SOLID 實線   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 點線,寬度必須為一   Creates a dotted pen. Valid only when the pen width is 1 or less, in device units.


PS_DASHDOT 點劃線,寬度必須為一   Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units.


PS_DASHDOTDOT 雙點劃線,寬度必須為一   Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units.


PS_NULL 空線,使用時什麼也不會產生   Creates a null pen.


PS_ENDCAP_ROUND 結束處為圓形   End caps are round.


PS_ENDCAP_SQUARE 結束處為方形   End caps are square.


nWidth和crColor為線的寬度和顏色。

刷子是在畫封閉曲線時用來填充的顏色,例如當你畫圓形或方形時系統會用當前的刷子對內部進行填充。刷子可利用CBrush物件產生。通過以下幾種函式建立刷子:

BOOL CreateSolidBrush( COLORREF crColor ); 建立一種固定顏色的刷子 
BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 建立指定顏色和網格的刷子,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 ); 建立以8*8點陣圖為模板的刷子

在選擇了畫筆和刷子後就可以利用Windows的作圖函式進行作圖了,基本的畫線函式有以下幾種

CDC::MoveTo( int x, int y ); 改變當前點的位置 
CDC::LineTo( int x, int y ); 畫一條由當前點到引數指定點的線 
CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 畫弧線 
CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 將多條線依次序連線 
基本的作圖函式有以下幾種: 
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 ); 多邊形 
對於矩形,圓形或類似的封閉曲線,系統會使用畫筆繪製邊緣,使用刷子填充內部。如果你不希望填充或是畫出邊緣,你可以選入空刷子(NULL_PEN)或是(NULL_BRUSH)空筆。

下面的程式碼建立一條兩象素寬的實線並選入DC。並進行簡單的作圖: 
{
...
 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 在視窗中繪製裝置相關點陣圖,圖示,裝置無關點陣圖

在Windows中可以將預先準備好的影像複製到顯示區域中,這種記憶體拷貝執行起來是非常快的。在Windows中提供了兩種使用圖形拷貝的方法:通過裝置相關點陣圖(DDB)和裝置無關點陣圖(DIB)。

DDB可以用MFC中的CBitmap來表示,而DDB一般是儲存在資原始檔中,在載入時只需要通過資源ID號就可以將圖形裝入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以裝入指定DDB,但是在繪製時必須藉助另一個和當前繪圖DC相容的記憶體DC來進行。通過CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )繪製圖形,同時指定光柵操作的型別。BitBlt可以將源DC中點陣圖複製到目的DC中,其中前四個引數為目的區域的座標,接下來是源DC指標,然後是源DC中的起始座標,由於BitBlt為等比例複製,所以不需要再次指定長寬,(StretchBlt可以進行縮放)最後一個引數為光柵操作的型別,可取以下值:

BLACKNESS 輸出區域為黑色   Turns all output black.


DSTINVERT 反色輸出區域   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 將反色後的源拷貝到目的區   Copies the inverted source bitmap to the destination.


PATINVERT 源和目的間進行XOR操作   Combines the destination bitmap with the pattern using the Boolean XOR operator.


SRCAND 源和目的間進行AND操作   Combines pixels of the destination and source bitmaps using the Boolean AND operator.


SRCCOPY 複製源到目的區   Copies the source bitmap to the destination bitmap.


SRCINVERT 源和目的間進行XOR操作   Combines pixels of the destination and source bitmaps using the Boolean XOR operator.


SRCPAINT 源和目的間進行OR操作   Combines pixels of the destination and source bitmaps using the Boolean OR operator.


WHITENESS 輸出區域為白色   Turns all output white. 
下面用程式碼演示這種方法: 
CYourView::OnDraw(CDC* pDC)
{
 CDC memDC;//定義一個相容DC
 memDC.CreateCompatibleDC(pDC);//建立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)
複製到目的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)中區域進行AND操作
 memDC.SelectObject(pbmpOld);//選入原DDB
}


(圖示並不是一個GDI物件,所以不需要選入DC)在MFC中沒有一個專門的圖示類,因為圖示的操作比較簡單,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 裝入後就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )繪製。由於在圖示中可以指定透明區域,所以在某些需要使用非規則圖形而且面積不大的時候使用圖示會比較簡單。下面給出簡單的程式碼:

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也沒有提供一個DIB的類,所以在使用DIB點陣圖時我們需要自己讀取點陣圖檔案中的頭資訊, 並讀入資料,並利用API函式StretchDIBits繪製。點陣圖檔案以BITMAPFILEHEADER結構開始,然後是BITMAPINFOHEADER 結構和調色版資訊和資料,其實點陣圖格式是圖形格式中最簡單的一種,而且也是Windows可以理解的一種。我不詳細 講解DIB點陣圖的結構,提供一個CDib類供大家使用,這個類包含了基本的功能如:Load,Save,Draw。


2.5 使用各種對映方式

所謂的對映方式簡單點講就是座標的安排方式,系統預設的對映方式為MM_TEXT即X座標向右增加,Y座標向下增加,(0,0)在螢幕左上方,DC中的每一點就是螢幕上的一個象素。也許你會認為這種方式下是最好理解的,但是一個點和象素對應的關係在螢幕上看來是正常的,但到了印表機上就會很不正常。因為我們作圖是以點為單位並且印表機的解析度遠遠比顯示器高(800DPI 800點每英寸)所以在印表機上圖形看起來就會很小。這樣就需要為列印另做一套程式碼而加大了工作量。如果每個點對應0.1毫米那麼在螢幕上的圖形就會和列印出來的圖形一樣大小。

通過int CDC::SetMapMode( int nMapMode )可以指定對映方式,可用的有以下幾種:

MM_HIENGLISH 每點對應0.001英寸   Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up.


MM_HIMETRIC 每點對應0.001毫米   Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up.


MM_LOENGLISH 每點對應0.01英寸   Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up.


MM_LOMETRIC 每點對應0.001毫米   Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up.


MM_TEXT 象素對應   Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down.


以上幾種對映預設的原點在螢幕左上方。除MM_TEXT外都為X座標向右增加,Y座標向上增加,和自然座標是一致的。所以在作圖是要注意什麼時候應該使用負座標。而且以上的對映都是X-Y等比例的,即相同的長度在X,Y軸上顯示的長度都是相同的。

另外的一種對映方式為MM_ANISOTROPIC,這種方式可以規定不同的長寬比例。在設定這中對映方式後必須呼叫CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )來設定長寬比例。系統會根據兩次設定的長寬的比值來確定長寬比例。下面給出一段程式碼比較對映前後的長寬比例:

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));
}

上面程式碼在對映後畫出的圖形將是一個長方形。

最後講講視原點(viewport origin),你可以通過呼叫CPoint CDC::SetViewportOrg( POINT point )重新設定原點的位置,這就相對於對座標進行了位移。例如你將原點設定在(20,20)那麼原來的(0,0)就變成了(-20,-20)。

2.6 多邊形和剪貼區域

多邊形也是一個GDI物件,同樣遵守其他GDI物件的規則,只是通常都不將其選入DC中。在MFC中多邊形有CRgn表示。多邊形用來表示一個不同與矩形的區域,和矩形具有相似的操作。如:檢測某點是否在內部,並操作等。此外還得到一個包含此多邊形的最小矩形。下面介紹一下多邊形類的成員函式:

CreateRectRgn 由矩形建立一個多邊形 
CreateEllipticRgn 由橢圓建立一個多邊形 
CreatePolygonRgn 建立一個有多個點圍成的多邊形 
PtInRegion 某點是否在內部 
CombineRgn 兩個多邊形相併 
EqualRgn 兩個多邊形是否相等

在本節中講演多邊形的意義在於重新在視窗中作圖時提高效率。因為引發視窗重繪的原因是某個區域失效,而失效的區域用多邊形來表示。假設視窗大小為500*400當上方的另一個視窗從(0,0,10,10)移動到(20,20,30,30)這時(0,0,10,10)區域就失效了,而你只需要重繪這部分割槽域而不是所有區域,這樣你程式的執行效率就會提高。

通過呼叫API函式int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效區域,但是一般用不著那麼精確而只需得到包含該區域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成這一功能。

3.1 文件 檢視 框架視窗間的關係和訊息傳送規律

在MFC中M$引入了文件-視結構的概念,文件相當於資料容器,視相當於檢視資料的視窗或是和資料發生互動的視窗。(這一結構在MFC中的OLE,ODBC開發時又得到更多的擴充)因此一個完整的應用一般由四個類組成:CWinApp應用類,CFrameWnd視窗框架類,CDocument文件類,CView視類。(VC6中支援建立不帶文件-視的應用)

在程式執行時CWinApp將建立一個CFrameWnd框架視窗例項,而框架視窗將建立文件模板,然後有文件模板建立文件例項和視例項,並將兩者關聯。一般來講我們只需對文件和視進行操作,框架的各種行為已經被MFC安排好了而不需人為干預,這也是M$設計文件-視結構的本意,讓我們將注意力放在完成任務上而從介面編寫中解放出來。

在應用中一個視對應一個文件,但一個文件可以包含多個視。一個應用中只用一個框架視窗,對多文件介面來講可能有多個MDI子視窗。每一個視都是一個子視窗,在單文件介面中父視窗即是框架視窗,在多文件介面中父視窗為MDI子視窗。一個多文件應用中可以包含多個文件模板,一個模板定義了一個文件和一個或多個視之間的對應關係。同一個文件可以屬於多個模板,但一個模板中只允許定義一個文件。同樣一個視也可以屬於多個文件模板。(不知道我說清楚沒有)

接下來看看如何在程式中得到各種物件的指標:

全域性函式AfxGetApp可以得到CWinApp應用類指標 
AfxGetApp()->m_pMainWnd為框架視窗指標 
在框架視窗中:CFrameWnd::GetActiveDocument得到當前活動文件指標 
在框架視窗中:CFrameWnd::GetActiveView得到當前活動視指標 
在視中:CView::GetDocument得到對應的文件指標 
在文件中:CDocument::GetFirstViewPosition,CDocument::GetNextView用來遍歷所有和文件關聯的視。 
在文件中:CDocument::GetDocTemplate得到文件模板指標 
在多文件介面中:CMDIFrameWnd::MDIGetActive得到當前活動的MDI子視窗 
一般來講使用者輸入訊息(如選單選擇,滑鼠,鍵盤等)會先發往視,如果視未處理則會發往框架視窗。所以定義訊息對映時定義在視中就可以了,如果一個應用同時擁有多個視而當前活動視沒有對訊息進行處理則訊息會發往框架視窗。


3.2 接收使用者輸入

在視中接收滑鼠輸入:

滑鼠訊息是我們常需要處理的訊息,訊息分為:滑鼠移動,按鈕按下/鬆開,雙擊。利用ClassWizard可以輕鬆的新增這幾種訊息對映,下面分別講解每種訊息的處理。

WM_MOUSEMOVE對應的函式為OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了當前一些按鍵的訊息,你可以通過“位與”操作進行檢測。

MK_CONTROL Ctrl鍵是否被按下   Set if the CTRL key is down.
MK_LBUTTON 滑鼠左鍵是否被按下   Set if the left mouse button is down.
MK_MBUTTON 滑鼠中間鍵是否被按下   Set if the middle mouse button is down.
MK_RBUTTON 滑鼠右鍵是否被按下   Set if the right mouse button is down.
MK_SHIFT Shift鍵是否被按下   Set if the SHIFT key is down
point表示當前滑鼠的裝置座標,座標原點對應視左上角。

WM_LBUTTONDOWN/WM_RBUTTONDOWN(滑鼠左/右鍵按下)對應的函式為OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )引數意義和OnMouseMove相同。

WM_LBUTTONUP/WM_RBUTTONUP(滑鼠左/右鍵鬆開)對應的函式為OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )引數意義和OnMouseMove相同。

WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(滑鼠左/右鍵雙擊)對應的函式為OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )引數意義和OnMouseMove相同。

下面我用一段虛擬碼來講解一下這些訊息的用法:

程式碼的作用是用滑鼠拉出一個矩形
global BOOL fDowned;//是否在拉動
global CPoint ptDown;//按下位置
global CPoint ptUp;//鬆開位置

OnLButtonDown(UINT nFlags, CPoint point)
{
 fDowned=TRUE;
 ptUp=ptDown=point;
 DrawRect();
...
}

OnMouseMove(UINT nFlags, CPoint point)
{
 if(fDowned)
 {
  DrawRect();//恢復上次所畫的矩形
  ptUp=point;
  DrawRect();//畫新矩形
 }
}
OnLButtonUp(UINT nFlags, CPoint point)
{
 if(fDowned)
 {
  DrawRect();//恢復上次所畫的矩形
  ptUp=point;
  DrawRect();//畫新矩形
  fDowned=FALSE;
 }
}

DrawRect()
{//以反色螢幕的方法畫出ptDown,ptUp標記的矩形
 CClientDC dc(this);
 MakeRect(ptDown,ptUp);
 SetROP(NOT);
 Rect();
}


座標間轉換:在以上的函式中point引數對應的都是視窗的裝置座標,我們應該將裝置座標和邏輯座標相區別,在圖32_g1由於視窗使用了滾動條,所以傳入的裝置座標是對應於當前視窗左上角的座標,沒有考慮是否滾動,而邏輯座標必須考慮滾動後對應的座標,所以我以黃線虛擬的表達一個邏輯座標的區域。可以看得出同一點在滾動後的座標值是不同的,這一規則同樣適用於改變了對映方式的視窗,假設你將對映方式設定為每點為0.01毫米,那麼裝置座標所對應的邏輯座標也需要重新計算。進行這種轉換需要寫一段程式碼,所幸的是系統提供了進行轉換的功能DC的DPtoLP,LPtoDP,下面給出程式碼完成由裝置座標到邏輯座標的轉換。

CPoint CYourView::FromDP(CPoint point)
{
 CClientDC dc(this);
 CPoint ptRet=point;
 dc.PrepareDC();//必須先準備DC,這在使用滾動時讓DC重新計算座標

 //如果你作圖設定了不同的對映方式,則在下面需要設定
 dc.SetMapMode(...)
 //
 dc.DPtoLP(&ptRet);//DP->LP進行轉換
 return ptRet;
}


在圖32_g1中以藍線標記的是螢幕區域,紅線標記的客戶區域。利用ScreenToClient,ClientToScreen可以將座標在這兩個區域間轉換。

在視中接收鍵盤輸入:

鍵盤訊息有三個:鍵盤被按下/鬆開,輸入字元。其中輸入字元相當於直接得到使用者輸入的字元這在不需要處理按鍵細節時使用,而鍵盤被按下/鬆開在按鍵狀態改變時傳送。

WM_CHAR對應的函式為OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar為被按下的字元,nRepCnt表明在長時間為鬆開時相當於的按鍵次數,nFlags中的不同位代表不同的含義,在這裡一般不使用。

WM_KEYDOWN/WM_KEYUP所對應的函式為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 使用選單

利用選單接受使用者命令是一中很簡單的互動方法,同時也是一種很有效的方法。通常選單作為一中資源儲存在檔案中,因此我們可以在設計時就利用資源編輯器設計好一個選單。關於使用VC設計選單我就不再多講了,但你在編寫選單時應該儘量在屬性對話方塊的底部提示(Prompt)處輸入文字,這雖然不是必要的,但MFC在有狀態列和工具條的情況下會使用該文字,文字的格式為“狀態列出說明\n工具條提示”。圖33_g1

我們要面臨的任務是如何知道使用者何時選擇了選單,他選的是什麼選單項。當使用者選擇了一個有效的選單項時系統會嚮應用傳送一個WM_COMMAND訊息,在訊息的引數中表明來源。在MFC中我們只需要進行一次對映,將某一選單ID對映到一處理函式,圖33_g2。在這裡我們在CView的派生類中處理選單訊息,同時我對同一ID設定兩個訊息對映,接下來將這兩種對映的作用。

ON_COMMAND 對映的作用為在使用者選擇該選單時呼叫指定的處理函式。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)會使選單被選擇時呼叫OnCommand1成員函式。

ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 對映的作用是在選單被顯示時通過呼叫指定的函式來進行確定其狀態。在這個處理函式中你可以設定選單的允許/禁止狀態,其顯示字串是什麼,是否在前面打鉤。函式的引數為CCmdUI* pCmdUI,CCmdUI是MFC專門為更新命令提供的一個類,你可以呼叫

Enable 設定允許/禁止狀態 
SetCheck 設定是否在前面打鉤 
SetText 設定文字

下面我講解一個例子:我在CView派生類中有一個變數m_fSelected,並且在視中處理兩個選單的訊息,當IDM_COMMAND1被選時,對m_fSelected進行邏輯非操作,當IDM_COMMAND2被選中時出一提示;同時IDM_COMMAND1根據m_fSelected決定選單顯示的文字和是否在前面打上檢查符號,IDM_COMMAND2根據m_fSelected的值決定選單的允許/禁止狀態。下面是程式碼和說明:

void CMenuDView::OnCommand1() 
{
 m_fSelected=!m_fSelected;
 TRACE("command1 selected\n");
}

void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI) 
{
 pCmdUI->SetCheck(m_fSelected);//決定檢查狀態
 pCmdUI->SetText(m_fSelected?"當前被選中":"當前未被選中");//決定所顯示的文字
}


void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI) 
{//決定是否為允許
 pCmdUI->Enable(m_fSelected);
}

void CMenuDView::OnCommand2() 
{//選中時給出提示
 AfxMessageBox("你選了command2");
}


接下來再講一些通過程式碼操縱選單的方法,在MFC中有一個類CMenu用來處理和選單有關的功能。在生成一個CMenu物件時你需要從資源中裝如選單,通過呼叫BOOL CMenu::LoadMenu( UINT nIDResource )進行裝入,然後你就可以對選單進行動態的修改,所涉及到的函式有: 
  CMenu* GetSubMenu( int nPos ) 一位置得到子選單的指標,因為一個CMenu物件只能表示一個彈出選單,如果選單中的某一項也為彈出選單,就需要通過該函式獲取指標。 
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾新增一項,nFlag為MF_SEPARATOR表示增加一個分隔條,這樣其他兩個引數將會被忽略;為MF_STRING表示新增一個選單項uIDNewItem為該選單的ID命令值;為MF_POPUP表示新增一個彈出選單項,這時uIDNewItem為另一選單的控制程式碼HMENU。lpszNewItem為選單文字說明。 
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用於在指定位置插入一選單,位置由變數nPosition指明。如果nFlags包含MF_BYPOSITION則表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID為nPosition的選單處。 
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用於修改某一位置的選單,如果nFlags包含MF_BYPOSITION則表明修改nPosition位置的選單,如果包含MF_BYCOMMAND表示修改命令ID為nPosition處的選單。 
BOOL RemoveMenu( UINT nPosition, UINT nFlags )用於刪除某一位置的選單。如果nFlags包含MF_BYPOSITION則表明刪除nPosition位置的選單,如果包含MF_BYCOMMAND表示刪除命令ID為nPosition處的選單。 
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以新增一點陣圖選單,但這樣的選單在選中時只是反色顯示,並不美觀。(關於使用自繪OwnerDraw選單請參考我翻譯的一篇文章自繪選單類) 
檢視中是沒有選單的,在框架視窗中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到應用的選單指標。

最後我講一下如何在程式中彈出一個選單,你必須先裝入一個選單資源,你必需得到一個彈出選單的指標然後呼叫BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )彈出選單,你需要指定(x,y)為選單彈出的位置,pWnd為接收命令訊息的視窗指標。下面有一段程式碼說明方法,當然為了處理訊息你應該在pWnd指明的視窗中對選單命令訊息進行對映。

CMenu menu;
menu.LoadMenu(IDR_POPUP);
CMenu* pM=menu.GetSubMenu(0);
CPoint pt;
GetCursorPos(&pt);
pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this);


另一種做法是通過CMenu::CreatePopupMenu()建立一個彈出選單,然後使用TrackPopupMenu彈出選單。使用CreatePopupMenu建立的選單也可以將其作為一個彈出項新增另一個選單中。下面的虛擬碼演示瞭如何建立一個彈出選單並進行修改後彈出:

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 文件,視,框架之間相互作用

一般來說使用者的輸入/輸出基本都是通過視進行,但一些例外的情況下可能需要和框架直接發生作用,而在多視的情況下如何在視之間傳遞資料。

在使用選單時大家會發現當一個選單沒有進行對映處理時為禁止狀態,在多視的情況下選單的狀態和處理對映是和當前活動視相聯絡的,這樣MFC可以保證視能正確的接收到各種訊息,但有時候也會產生不便。有一個解決辦法就是在框架中對訊息進行處理,這樣也可以保證當前文件可以通過框架得到當前訊息。

在使用者進行輸入後如何使視的狀態得到更新?這個問題在一個文件對應一個檢視時是不存在的,但是現在有一個文件對應了兩個檢視,當在一個視上進行了輸入時如何保證另一個視也得到通知呢?MFC的做法是利用文件來處理的,因為文件管理著當前和它聯絡的視,由它來通知各個視是最合適的。讓我們同時看兩個函式:

void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ) 
void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ) 
當文件的UpdateAllViews被呼叫時和此文件相關的所有視的OnUpdate都會被呼叫,而引數lHint和pHint都會被傳遞。這樣一來發生改變視就可以通知其他的兄弟了。那麼還有一個問題:如何在OnUpdate中知道是那個檢視發生了改變呢,這就可以利用pHint引數,只要呼叫者將this指標賦值給引數就可以了,當然完全可以利用該引數傳遞更復雜的結構。

視的初始化,當一個文件被開啟或是新建一個文件時檢視的CView::OnInitialUpdate()會被呼叫,你可以通過過載該函式對視進行初始化,並在結束前呼叫父類的OnInitialUpdate,因為這樣可以保證OnUpdate會被呼叫。

文件中內容的清除,當文件被關閉時(比如退出或是新建前上一個文件清除)void CDocument::DeleteContents ()會被呼叫,你可以通過過載該函式來進行清理工作。

在單文件結構中上面兩點尤其重要,因為軟體執行文件物件和視物件只會被產生並刪除一次。所以應該將上面兩點和C++物件構造和構析分清楚。

最後將一下文件模板(DocTemplate)的作用,文件模板分為兩類單文件模板和多文件模板,分別由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在於記錄文件,視,框架之間的對應關係。還有一點就是模板可以記錄應用程式可以開啟的檔案的型別,當開啟檔案時會根據文件模板中的資訊選擇正確的文件和視。模板是一個比較抽想的概念,一般來說是不需要我們直接進行操作的。

當使用者通過視修改了資料時,應該呼叫GetDocument()->SetModifiedFlag(TRUE)通知文件資料已經被更新,這樣在關閉文件時會自動詢問使用者是否儲存資料。

好象這一節講的有些亂,大家看後有什麼想法和問題請在VCHelp論壇上留言,我會盡快回復並且會對本節內容重新整理和修改。


3.5 利用序列化進行檔案讀寫

在很多應用中我們需要對資料進行儲存,或是從介質上讀取資料,這就涉及到檔案的操作。我們可以利用各種檔案存取方法完成這些工作,但MFC中也提供了一種讀寫檔案的簡單方法——“序列化”。序列化機制通過更高層次的介面功能向開發者提供了更利於使用和透明於位元組流的檔案操縱方法,舉一個例來講你可以將一個字串寫入檔案而不需要理會具體長度,讀出時也是一樣。你甚至可以對字串陣列進行操作。在MFC提供的可自動分配記憶體的類的支援下你可以更輕鬆的讀/寫資料。你也可以根據需要編寫你自己的具有序列化功能的類。

序列化在最低的層次上應該被需要序列化的類支援,也就是說如果你需要對一個類進行序列化,那麼這個類必須支援序列化。當通過序列化進行檔案讀寫時你只需要該類的序列化函式就可以了。

怎樣使類具有序列化功能呢?你需要以下的工作: 
該類從CObject派生。 
在類宣告中包括DECLARE_SERIAL巨集定義。 
提供一個預設的建構函式。 
在類中實現Serialze函式 
使用IMPLEMENT_SERIAL指明類名和版本號 
下面的程式碼建立了一個簡單身份證記錄的類,同時也能夠支援序列化。

in H
struct strPID
{
 char szName[10];
 char szID[16];
 struct strPID* pNext;
};
class CAllPID : public CObject
{
public:
 DECLARE_SERIAL(CAllPID)
 CAllPID();
 ~CAllPID();
 
public:// 序列化相關 
 struct strPID* pHead;
 //其他的成員函式
 void Serialize(CArchive& ar);
};

in CPP
IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用於讀資料時的檢測
void CAllPID::Serialize(CArchive& ar)
{
 int iTotal;
 if(ar.IsStoring())
 {//儲存資料
  iTotal=GetTotalID();//得到連結串列中的記錄數量
  arr<26;i++)
    ar<<&(((BYTE*)pItem)+i);//寫一個strPID中所有的資料
  }
 }
 else
 {//讀資料
  ar>>iTotal;
  for(int i=0;i26;j++)
    ar>>*(((BYTE*)pID)+j);//讀一個strPID中所有的資料
   //修改連結串列
  }
 }
}

當然上面的程式碼很不完整,但已經可以說明問題。這樣CAllPID就是一個可以支援序列化的類,並且可以根據記錄的數量動態分配記憶體。在序列化中我們使用了CArchive類,該類用於在序列化時提供讀寫支援,它過載了<<和>>運算子號,並且提供Read和Write函式對資料進行讀寫。

下面看看如何在文件中使用序列化功能,你只需要修改文件類的Serialize(CArchive& ar)函式,並呼叫各個進行序列化的類的Serial進行資料讀寫就可以了。當然你也可以在文件類的內部進行資料讀寫,下面的程式碼利用序列化功能讀寫資料:

class CYourDoc : public CDocument
{
 void Serialize(CArchive& ar);
 CString m_szDesc;
 CAllPID m_allPID;
......
}

void CYourDoc::Serialize(CArchive& ar)
{
 if (ar.IsStoring())
 {//由於CString對CArchive定義了<<和>>操作符號,所以可以直接利用>>和<<
  ar<>m_szDesc;
 }
 m_allPID.Serialize(ar);//呼叫資料類的序列化函式
}

3.6 MFC中所提供的各種視類介紹

MFC中提供了豐富的視類供開發者使用,下面對各個類進行介紹:

CView類是最基本的視類只支援最基本的操作。

CScrollView類提供了滾動的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )設定滾動尺寸,和座標對映模式。但是在繪圖和接收使用者輸入時需要對座標進行轉換。請參見3.2 接收使用者輸入。

CFormView類提供使用者在資原始檔中定義介面的能力,並可以將子視窗和變數進行繫結。通過UpdateData函式讓資料在變數和子視窗間交換。

CTreeView類利用TreeCtrl介面作為視介面,通過呼叫CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。

CListView類利用ListCtrl介面作為視介面,通過呼叫CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。

CEditView類利用Edit接收使用者輸入,它具有輸入框的一切功能。通過呼叫CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以設定列印字型。

CRichEditView類作為Rich Text Edit(富文字輸入)的視類,提供了可以按照格式顯示文字的能力,在使用時需要CRichEditDoc的支援。

 


*****************************   


4.1 Button

按鈕視窗(控制元件)在MFC中使用CButton表示,CButton包含了三種樣式的按鈕,Push Button,Check Box,Radio Box。所以在利用CButton物件生成按鈕視窗時需要指明按鈕的風格。

建立按鈕:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按鈕上顯示的文字,dwStyle為按鈕風格,除了Windows風格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)還有按鈕專用的一些風格。

BS_AUTOCHECKBOX 檢查框,按鈕的狀態會自動改變   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 圓形選擇按鈕,按鈕的狀態會自動改變   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 允許按鈕有三種狀態即:選中,未選中,未定   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 預設普通按鈕   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 左對齊文字   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 允許按鈕有三種狀態即:選中,未選中,未定   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為視窗所佔據的矩形區域,pParentWnd為父視窗指標,nID為該視窗的ID值。

獲取/改變按鈕狀態:對於檢查按鈕和圓形按鈕可能有兩種狀態,選中和未選中,如果設定了BS_3STATE或BS_AUTO3STATE風格就可能出現第三種狀態:未定,這時按鈕顯示灰色。通過呼叫int CButton::GetCheck( ) 得到當前是否被選中,返回0:未選中,1:選中,2:未定。呼叫void CButton::SetCheck( int nCheck );設定當前選中狀態。

處理按鈕訊息:要處理按鈕訊息需要在父視窗中進行訊息對映,對映巨集為ON_BN_CLICKED( id, memberFxn )id為按鈕的ID值,就是建立時指定的nID值。處理函式原型為afx_msg void memberFxn( );


4.2 Static Box

靜態文字控制元件的功能比較簡單,可作為顯示字串,圖示,點陣圖用。建立一個視窗可以使用成員函式: 
BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 
其中dwStyle將指明該視窗的風格,除了子視窗常用的風格WS_CHILD,WS_VISIBLE外,你可以針對靜態控制元件指明專門的風格。

SS_CENTER,SS_LEFT,SS_RIGHT 指明字元顯示的對齊方式。 
SS_GRAYRECT 顯示一個灰色的矩形 
SS_NOPREFIX 如果指明該風格,對於字元&將直接顯示,否則&將作為轉義符,&將不顯示而在其後的字元將有下劃線,如果需要直接顯示&必須使用&&表示。 
SS_BITMAP 顯示點陣圖 
SS_ICON 顯示圖示 
SS_CENTERIMAGE 圖象居中顯示

控制顯示的文字利用成員函式SetWindowText/GetWindowText用於設定/得到當前顯示的文字。

控制顯示的圖示利用成員函式SetIcon/GetIcon用於設定/得到當前顯示的圖示。

控制顯示的點陣圖利用成員函式SetBitmap/GetBitmap用於設定/得到當前顯示的點陣圖。下面一段程式碼演示如何建立一個顯示點陣圖的靜態視窗並設定點陣圖

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視窗是用來接收使用者輸入最常用的一個控制元件。建立一個輸入視窗可以使用成員函式: 
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 
其中dwStyle將指明該視窗的風格,除了子視窗常用的風格WS_CHILD,WS_VISIBLE外,你可以針對輸入控制元件指明專門的風格。

ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明輸入文字超出顯示範圍時自動滾動。 
ES_CENTER,ES_LEFT,ES_RIGHT 指定對齊方式 
ES_MULTILINE 是否允許多行輸入 
ES_PASSWORD 是否為密碼輸入框,如果指明該風格則輸入的文字顯示為* 
ES_READONLY 是否為只讀 
ES_UPPERCASE,ES_LOWERCASE 顯示大寫/小寫字元 
控制顯示的文字利用成員函式SetWindowText/GetWindowText用於設定/得到當前顯示的文字。

通過GetLimitText/SetLimitText可以得到/設定在輸入框中輸入的字元數量。

由於在輸入時使用者可能選擇某一段文字,所以通過void CEdit::GetSel( int& nStartChar, int& nEndChar )得到使用者選擇的字元範圍,通過呼叫void CEdit::SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE )可以設定當前選擇的文字範圍,如果指定nStartChar=0 nEndChar=-1則表示選中所有的文字。void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE )可以將選中的文字替換為指定的文字。

此外輸入框還有一些和剪貼簿有關的功能,void Clear( );刪除選中的文字,void Copy( );可將選中的文字送入剪貼簿,void Paste( );將剪貼簿中內容插入到當前輸入框中游標位置,void Cut( );相當於Copy和Clear結合使用。

最後介紹一下輸入框幾種常用的訊息對映巨集:

ON_EN_CHANGE 輸入框中文字更新後產生 
ON_EN_ERRSPACE 輸入框無法分配記憶體時產生 
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點時產生 
使用以上幾種訊息對映的方法為定義原型如:afx_msg void memberFxn( );的函式,並且定義形式如ON_Notification( id, memberFxn )的訊息對映。如果在對話方塊中使用輸入框,Class Wizard會自動列出相關的訊息,並能自動產生訊息對映程式碼。

4.4 Scroll Bar
Scroll Bar一般不會單獨使用,因為SpinCtrl可以取代滾動條的一部分作用,但是如果你需要自己生成派生視窗,滾動條還是會派上一些用場。建立一個滾動條可以使用成員函式: : BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle將指明該視窗的風格,除了子視窗常用的風格WS_CHILD,WS_VISIBLE外,你可以針對滾動條指明專門的風格。 
SBS_VERT 風格將建立一個垂直的滾動條。 
SBS_HORZ 風格將建立一個水平的滾動條。 
在建立滾動條後需要呼叫void SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE )設定滾動範圍, int GetScrollPos( )/int SetScrollPos( )用來得到和設定當前滾動條的位置。
void ShowScrollBar( BOOL bShow = TRUE );用來顯示/隱藏滾動條。
BOOL EnableScrollBar( UINT nArrowFlags = ESB_ENABLE_BOTH )用來設定滾動條上箭頭是否為允許狀態。nArrowFlags可取以下值: 
ESB_ENABLE_BOTH 兩個箭頭都為允許狀態 
ESB_DISABLE_LTUP 上/左箭頭為禁止狀態 
ESB_DISABLE_RTDN 下/右箭頭為禁止狀態 
ESB_DISABLE_BOTH 兩個箭頭都為禁止狀態 
如果需要在滾動條位置被改變時得到通知,需要在父視窗中定義對訊息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( ),在對映巨集中不需要指明滾動條的ID,因為所有滾動條的滾動訊息都由同樣的函式處理。在OnHScroll/OnVScroll的第三個引數會指明當前滾動條的指標。第一個參數列示滾動條上發生的動作,可取以下值: 
SB_TOP/SB_BOTTOM 已滾動到頂/底部 
SB_LINEUP/SB_LINEDOWN 向上/下滾動一行 
SB_PAGEDOWN/SB_PAGEUP 向上/下滾動一頁 
SB_THUMBPOSITION/SB_THUMBTRACK 滾動條拖動到某一位置,引數nPos指明當前位置(引數nPos在其它的情況下是無效的) 
SB_ENDSCROLL 滾動條拖動完成(使用者鬆開滑鼠)  
4.5 List Box/Check List Box

ListBox視窗用來列出一系列的文字,每條文字佔一行。建立一個列表視窗可以使用成員函式: 
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 
其中dwStyle將指明該視窗的風格,除了子視窗常用的風格WS_CHILD,WS_VISIBLE外,你可以針對列表控制元件指明專門的風格。

LBS_MULTIPLESEL 指明列表框可以同時選擇多行 
LBS_EXTENDEDSEL 可以通過按下Shift/Ctrl鍵選擇多行 
LBS_SORT 所有的行按照字母順序進行排序

在列表框生成後需要向其中加入或是刪除行,可以利用: 
int AddString( LPCTSTR lpszItem )新增行, 
int DeleteString( UINT nIndex )刪除指定行, 
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。 
void ResetContent( )可以刪除列表框中所有行。 
通過呼叫int GetCount( )得到當前列表框中行的數量。

如果需要得到/設定當前被選中的行,可以呼叫int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明瞭選擇多行的風格,你就需要先呼叫int GetSelCount( )得到被選中的行的數量,然後int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有選中的行,引數rgIndex為存放被選中行的陣列。通過呼叫int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內指定行的字串。

此外通過呼叫int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當前所有行中查詢指定的字元傳的位置,nStartAfter指明從那一行開始進行查詢。 
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字串的行。

在MFC 4.2版本中新增了CCheckListBox類,該類是由CListBox派生並擁有CListBox的所有功能,不同的是可以在每行前加上一個檢查框。必須注意的是在建立時必須指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE風格。

通過void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以設定/得到檢查框的風格,關於檢查框風格可以參考4.1 Button中介紹。通過void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以設定和得到某行的檢查狀態,關於檢查框狀態可以參考4.1 Button中介紹。

最後介紹一下列表框幾種常用的訊息對映巨集: 
ON_LBN_DBLCLK 滑鼠雙擊 
ON_EN_ERRSPACE 輸入框無法分配記憶體時產生 
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點時產生 
ON_LBN_SELCHANGE 選擇的行發生改變 
使用以上幾種訊息對映的方法為定義原型如:afx_msg void memberFxn( );的函式,並且定義形式如ON_Notification( id, memberFxn )的訊息對映。如果在對話方塊中使用列表框,Class Wizard會自動列出相關的訊息,並能自動產生訊息對映程式碼。

4.6 Combo Box

組合視窗是由一個輸入框和一個列表框組成。建立一個組合視窗可以使用成員函式: 
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 
其中dwStyle將指明該視窗的風格,除了子視窗常用的風格WS_CHILD,WS_VISIBLE外,你可以針對列表控制元件指明專門的風格。

CBS_DROPDOWN 下拉式組合框 
CBS_DROPDOWNLIST 下拉式組合框,但是輸入框內不能進行輸入 
CBS_SIMPLE 輸入框和列表框同時被顯示 
LBS_SORT 所有的行按照字母順序進行排序

由於組合框內包含了列表框,所以列表框的功能都能夠使用,如可以利用: 
int AddString( LPCTSTR lpszItem )新增行, 
int DeleteString( UINT nIndex )刪除指定行, 
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。 
void ResetContent( )可以刪除列表框中所有行。 
通過呼叫int GetCount( )得到當前列表框中行的數量。

如果需要得到/設定當前被選中的行的位置,可以呼叫int GetCurSel( )/int SetCurSel(int iIndex)。通過呼叫int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內指定行的字串。

此外通過呼叫int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當前所有行中查詢指定的字元傳的位置,nStartAfter指明從那一行開始進行查詢。 
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字串的行。

此外輸入框的功能都能夠使用,如可以利用: 
DWORD GetEditSel( ) /BOOL SetEditSel( int nStartChar, int nEndChar )得到或設定輸入框中被選中的字元位置。 
BOOL LimitText( int nMaxChars )設定輸入框中可輸入的最大字元數。 
輸入框的剪貼簿功能Copy,Clear,Cut,Paste動可以使用。

最後介紹一下列表框幾種常用的訊息對映巨集:

ON_CBN_DBLCLK 滑鼠雙擊 
ON_CBN_DROPDOWN 列表框被彈出 
ON_CBN_KILLFOCUS / ON_CBN_SETFOCUS 在輸入框失去/得到輸入焦點時產生 
ON_CBN_SELCHANGE 列表框中選擇的行發生改變 
ON_CBN_EDITUPDATE 輸入框中內容被更新 
使用以上幾種訊息對映的方法為定義原型如:afx_msg void memberFxn( );的函式,並且定義形式如ON_Notification( id, memberFxn )的訊息對映。如果在對話方塊中使用組合框,Class Wizard會自動列出相關的訊息,並能自動產生訊息對映程式碼。

4.7 Tree Ctrl

樹形控制元件TreeCtrl和下節要講的列表控制元件 ListCtrl在系統中大量被使用,例如Windows資源管理器就是一個典型的例子。

樹形控制元件可以用於樹形的結構,其中有一個根接點(Root)然後下面有許多子結點,而每個子結點上有允許有一個或多個或沒有子結點。MFC中使用CTreeCtrl類來封裝樹形控制元件的各種操作。通過呼叫
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );建立一個視窗,dwStyle中可以使用以下一些樹形控制元件的專用風格:

TVS_HASLINES 在父/子結點之間繪製連線 
TVS_LINESATROOT 在根/子結點之間繪製連線 
TVS_HASBUTTONS 在每一個結點前新增一個按鈕,用於表示當前結點是否已被展開 
TVS_EDITLABELS 結點的顯示字元可以被編輯 
TVS_SHOWSELALWAYS 在失去焦點時也顯示當前選中的結點 
TVS_DISABLEDRAGDROP 不允許Drag/Drop 
TVS_NOTOOLTIPS 不使用ToolTip顯示結點的顯示字元 
在樹形控制元件中每一個結點都有一個控制程式碼(HTREEITEM),同時新增結點時必須提供的引數是該結點的父結點控制程式碼,(其中根Root結點只有一個,既不可以新增也不可以刪除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以新增一個結點,pszItem為顯示的字元,hParent代表父結點的控制程式碼,當前新增的結點會排在hInsertAfter表示的結點的後面,返回值為當前建立的結點的控制程式碼。下面的程式碼會建立一個如下形式的樹形結構: +--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假設m_tree為一個CTreeCtrl物件,而且該視窗已經建立*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根結點上新增Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上新增一個子結點 hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上新增一個子結點,排在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); 如果你希望在每個結點前新增一個小圖示,就必需先呼叫CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明當前所使用的ImageList,nImageListType為TVSIL_NORMAL。在呼叫完成後控制元件中使用圖片以設定的ImageList中圖片為準。然後呼叫
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);新增結點,nImage為結點沒被選中時所使用圖片序號,nSelectedImage為結點被選中時所使用圖片序號。下面的程式碼演示了ImageList的設定。 /*m_list 為CImageList物件 IDB_TREE 為16*(16*4)的點陣圖,每個圖片為16*16共4個圖示*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1);//新增, 選中時顯示圖示1,未選中時顯示圖示0

此外CTreeCtrl還提供了一些函式用於得到/修改控制元件的狀態。 
HTREEITEM GetSelectedItem( );將返回當前選中的結點的控制程式碼。BOOL SelectItem( HTREEITEM hItem );將選中指明結點。 
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用於得到/修改某結點所使用圖示索引。 
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用於得到/修改某一結點的顯示字元。 
BOOL DeleteItem( HTREEITEM hItem );用於刪除某一結點,BOOL DeleteAllItems( );將刪除所有結點。

此外如果想遍歷樹可以使用下面的函式: 
HTREEITEM GetRootItem( );得到根結點。 
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結點。 
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結點的上/下一個兄弟結點。 
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結點。

樹形控制元件的訊息對映使用ON_NOTIFY巨集,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知程式碼,id為產生該訊息的視窗ID,memberFxn為處理函式,函式的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一資料結構,在具體使用時需要轉換成其他型別的結構。對於樹形控制元件可能取值和對應的資料結構為:

TVN_SELCHANGED 在所選中的結點發生改變後傳送,所用結構:NMTREEVIEW 
TVN_ITEMEXPANDED 在某結點被展開後傳送,所用結構:NMTREEVIEW 
TVN_BEGINLABELEDIT 在開始編輯結點字元時傳送,所用結構:NMTVDISPINFO 
TVN_ENDLABELEDIT 在結束編輯結點字元時傳送,所用結構:NMTVDISPINFO 
TVN_GETDISPINFO 在需要得到某結點資訊時傳送,(如得到結點的顯示字元)所用結構:NMTVDISPINFO 
關於ON_NOTIFY有很多內容,將在以後的內容中進行詳細講解。

關於動態提供結點所顯示的字元:首先你在新增結點時需要指明lpszItem引數為:LPSTR_TEXTCALLBACK。在控制元件顯示該結點時會通過傳送TVN_GETDISPINFO來取得所需要的字元,在處理該訊息時先將引數pNMHDR轉換為LPNMTVDISPINFO,然後填充其中item.pszText。但是我們通過什麼來知道該結點所對應的資訊呢,我的做法是在新增結點後設定其lParam引數,然後在提供資訊時利用該引數來查詢所對應的資訊。下面的程式碼說明了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //新增結點 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];//通過lParam得到 需要顯示的字元在陣列中的位置 *pResult = 0; }


關於編輯結點的顯示字元:首先需要設定樹形控制元件的TVS_EDITLABELS風格,在開始編輯時該控制元件將會傳送TVN_BEGINLABELEDIT,你可以通過在處理函式中返回TRUE來取消接下來的編輯,在編輯完成後會傳送TVN_ENDLABELEDIT,在處理該訊息時需要將引數pNMHDR轉換為LPNMTVDISPINFO,然後通過其中的item.pszText得到編輯後的字元,並重置顯示字元。如果編輯在中途中取消該變數為NULL。下面的程式碼說明如何處理這些訊息: //處理訊息 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);//判斷是否已經取消取消編輯 m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字元 *pResult = 0; } 上面講述的方法所進行的訊息對映必須在父視窗中進行(同樣WM_NOTIFY的所有訊息都需要在父視窗中處理)。 
  
4.8 List Ctrl

列表控制元件可以看作是功能增強的ListBox,它提供了四種風格,而且可以同時顯示一列的多中屬性值。MFC中使用CListCtrl類來封裝列表控制元件的各種操作。通過呼叫
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );建立一個視窗,dwStyle中可以使用以下一些列表控制元件的專用風格:

LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 這四種風格決定控制元件的外觀,同時只可以選擇其中一種,分別對應:大圖示顯示,小圖示顯示,列表顯示,詳細報表顯示 
LVS_EDITLABELS 結點的顯示字元可以被編輯,對於報表風格來講可編輯的只為第一列。 
LVS_SHOWSELALWAYS 在失去焦點時也顯示當前選中的結點 
LVS_SINGLESEL 同時只能選中列表中一項 
首先你需要設定列表控制元件所使用的ImageList,如果你使用大圖示顯示風格,你就需要以如下形式呼叫: 
CImageList* SetImageList( CImageList* pImageList, LVSIL_NORMAL); 
如果使用其它三種風格顯示而不想顯示圖示你可以不進行任何設定,否則需要以如下形式呼叫: 
CImageList* SetImageList( CImageList* pImageList, LVSIL_SMALL);

通過呼叫int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控制元件中nItem指明位置插入一項,lpszItem為顯示字元。除LVS_REPORT風格外其他三種風格都只需要直接呼叫InsertItem就可以了,但如果使用報表風格就必須先設定列表控制元件中的列資訊。

通過呼叫int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol為列的位置,從零開始,lpszColumnHeading為顯示的列名,nFormat為顯示對齊方式,nWidth為顯示寬度,nSubItem為分配給該列的列索引。

在有多列的列表控制元件中就需要為每一項指明其在每一列中的顯示字元,通過呼叫 
BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以設定每列的顯示字元。nItem為設定的項的位置,nSubItem為列位置,lpszText為顯示字元。下面的程式碼演示瞭如何設定多列並插入資料: m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//設定ImageList m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//設定列 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");//設定該行的不同列的顯示字元 m_list.SetItemText(0,2,"Item 1_3");


此外CListCtrl還提供了一些函式用於得到/修改控制元件的狀態。 
COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用於得到/設定顯示的字元顏色。 
COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用於得到/設定顯示的背景顏色。 
void SetItemCount( int iCount );用於得到新增進列表中項的數量。 
BOOL DeleteItem(int nItem);用於刪除某一項,BOOL DeleteAllItems( );將刪除所有項。 
BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用於設定背景點陣圖。 
CString GetItemText( int nItem, int nSubItem );用於得到某項的顯示字元。

列表控制元件的訊息對映同樣使用ON_NOTIFY巨集,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知程式碼,id為產生該訊息的視窗ID,memberFxn為處理函式,函式的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一資料結構,在具體使用時需要轉換成其他型別的結構。對於列表控制元件可能取值和對應的資料結構為:

LVN_BEGINLABELEDIT 在開始某項編輯字元時傳送,所用結構:NMLVDISPINFO 
LVN_ENDLABELEDIT 在結束某項編輯字元時傳送,所用結構:NMLVDISPINFO 
LVN_GETDISPINFO 在需要得到某項資訊時傳送,(如得到某項的顯示字元)所用結構:NMLVDISPINFO 
關於ON_NOTIFY有很多內容,將在以後的內容中進行詳細講解。

關於動態提供結點所顯示的字元:首先你在項時需要指明lpszItem引數為:LPSTR_TEXTCALLBACK。在控制元件顯示該結點時會通過傳送TVN_GETDISPINFO來取得所需要的字元,在處理該訊息時先將引數pNMHDR轉換為LPNMLVDISPINFO,然後填充其中item.pszText。通過item中的iItem,iSubItem可以知道當前顯示的為那一項。下面的程式碼演示了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //新增結點 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];//通過iItem得到需要 顯示的字元在陣列中的位置 *pResult = 0; }


關於編輯某項的顯示字元:(在報表風格中只對第一列有效)首先需要設定列表控制元件的LVS_EDITLABELS風格,在開始編輯時該控制元件將會傳送LVN_BEGINLABELEDIT,你可以通過在處理函式中返回TRUE來取消接下來的編輯,在編輯完成後會傳送LVN_ENDLABELEDIT,在處理該訊息時需要將引數pNMHDR轉換為LPNMLVDISPINFO,然後通過其中的item.pszText得到編輯後的字元,並重置顯示字元。如果編輯在中途中取消該變數為NULL。下面的程式碼說明如何處理這些訊息: //處理訊息 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);//判斷是否已經取消取消編輯 m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText);//重置顯示字元 *pResult = 0; } 上面講述的方法所進行的訊息對映必須在父視窗中進行(同樣WM_NOTIFY的所有訊息都需要在父視窗中處理)。


如何得到當前選中項位置:在列表控制元件中沒有一個類似於ListBox中GetCurSel()的函式,但是可以通過呼叫GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到選中項位置。

4.9 Tab Ctrl
Tab屬性頁控制元件可以在一個視窗中新增不同的頁面,然後在頁選擇發生改變時得到通知。MFC中使用CTabCtrl類來封裝屬性頁控制元件的各種操作。通過呼叫BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );建立一個視窗,dwStyle中可以使用以下一些屬性頁控制元件的專用風格: 
TCS_BUTTONS 使用按鈕來表示頁選擇位置 
TCS_MULTILINE 分行顯示頁選擇位置 
TCS_SINGLELINE 只使用一行顯示頁選擇位置 
在控制元件建立後必需向其中新增頁面才可以使用,新增頁面的函式為: BOOL InsertItem( int nItem, LPCTSTR lpszItem );nItem為位置,從零開始,lpszItem為頁選擇位置上顯示的文字。如果你希望在頁選擇位置處顯示一個圖示,你可以呼叫 BOOL InsertItem( int nItem, LPCTSTR lpszItem, int nImage );nImage指明所使用的圖片位置。(在此之前必須呼叫CImageList * SetImageList( CImageList * pImageList );設定正確的ImageList) 
此外CTabCtrl還提供了一些函式用於得到/修改控制元件的狀態。 int GetCurSel( )/int SetCurSel( int nItem );用於得到/設定當前被選中的頁位置。 BOOL DeleteItem( int nItem )/BOOL DeleteAllItems( );用於刪除指定/所有頁面。 void RemoveImage( int nImage );用於刪除某頁選擇位置上的圖示。 
屬性頁控制元件的訊息對映同樣使用ON_NOTIFY巨集,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知程式碼,id為產生該訊息的視窗ID,memberFxn為處理函式,函式的原型如同void OnXXXTab(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一資料結構,在具體使用時需要轉換成其他型別的結構。對於列表控制元件可能取值和對應的資料結構為: 
TCN_SELCHANGE 在當前頁改變後傳送,所用結構:NMHDR 
TCN_SELCHANGING 在當前頁改變時傳送可以通過返回TRUE來禁止頁面的改變,所用結構:NMHDR 
一般來講在當前頁發生改變時需要隱藏當前的一些子視窗,並顯示其它的子視窗。下面的虛擬碼演示瞭如何使用屬性頁控制元件: 
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在屬性頁的第一頁
 static_box.ShowWindow(SW_HIDE); // static box在屬性頁的第二頁
}
void CParentWnd::OnSelectChangeTab(NMHDR* pNMHDR, LRESULT* pResult)
{//處理頁選擇改變後的訊息
 if(m_tab.GetCurSel()==0)
 {//根據當前頁顯示/隱藏不同的子視窗
  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類來封裝工具條控制元件的各種操作。通過呼叫
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_TOOLBAR );建立一個視窗,dwStyle中可以使用以下一些工具條控制元件的專用風格:

CBRS_TOP 工具條在父視窗的頂部 
TCBRS_BOTTOM 工具條在父視窗的底部 
CBRS_FLOATING 工具條是浮動的 
建立一個工具條的步驟如下:先使用Create建立視窗,然後使用BOOL LoadToolBar( LPCTSTR lpszResourceName );直接從資源中裝入工具條,或者通過裝入點陣圖並指明每個按鈕的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));//設定按鈕大尺寸
和按鈕上點陣圖的尺寸
m_toolbar.SetButtons(uID,5);

AppWizard在生成程式碼時也會同時生成工具條的程式碼,同時還可以支援停靠功能。所以一般是不需要直接操作工具條物件。

工具條上的按鈕被按下時傳送給父視窗的訊息和選單訊息相同,所以可以使用ON_COMMAND巨集進行對映,同樣工具條中的按鈕也支援ON_UPDATE_COMMAND_UI的相關操作,如SetCheck,Enable,你可以將按鈕的當作選單上的一個具有相同ID選單項。

在以後的章節4.D 利用AppWizard建立並使用ToolBar StatusBar Dialog Bar會給出使用的方法。 

4.B Status Bar

狀態條用於顯示一些提示字元。MFC中使用CStatusBar類來封裝狀態條控制元件的各種操作。通過呼叫
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );建立一個視窗,dwStyle中可以使用以下一些狀態條控制元件的專用風格:

CBRS_TOP 狀態條在父視窗的頂部 
TCBRS_BOTTOM 狀態條在父視窗的底部 
建立一個狀態條的步驟如下:先使用Create建立視窗,然後呼叫BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );設定狀態條上各部分的ID,具體程式碼如下:

UINT uID[2]={ID_SEPARATOR,ID_INDICATOR_CAPS};
m_stabar.Create(pParentWnd);
m_stabar.SetIndicators(uID,2);


通過CString GetPaneText( int nIndex )/BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )可以得到/設定狀態條上顯示的文字。

Tip:在建立狀態條時最好將狀態條中所有的部分ID(除MFC自定義的幾個用於狀態條的ID外)都設定為ID_SEPARATOR,在生成後呼叫
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );改變其風格,ID和寬度。

AppWizard在生成程式碼時也會同時生成狀態條的程式碼。所以一般是不需要直接建立狀態條物件。此外狀態條上會自動顯示選單上的命令提示(必須先在資源中定義),所以也不需要人為設定顯示文字。

狀態條支援ON_UPDATE_COMMAND_UI的相關操作,如SetText,Enable。

在以後的章節4.D 利用AppWizard建立並使用ToolBar StatusBar Dialog Bar會給出使用的方法。 

4.C Dialog Bar

Dialog Bar類似一個靜態的附在框架視窗上的對話方塊,由於Dialog Bar可以使用資源編輯器進行編輯所以使用起來就很方便,在設計時就可以對Dialog Bar上的子視窗進行定位。用於顯示一些提示字元。MFC中使用CDialogBar類來Dialog Bar控制元件的各種操作。通過呼叫
BOOL Create( CWnd* pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID );建立一個視窗,nIDTemplate為對話方塊資源,nID為該Dialog Bar對應的視窗ID,nStyle中可以使用以下一些狀態條控制元件的專用風格:

CBRS_TOP Dialog Bar在父視窗的頂部 
TCBRS_BOTTOM Dialog Bar在父視窗的底部 
CBRS_LEFT Dialog Bar在父視窗的左部 
CBRS_RIGHT Dialog Bar在父視窗的右部 
對於Dialog Bar的所產生訊息需要在父視窗中進行對映和處理,例如Dialog Bar上的按鈕,需要在父視窗中進行ON_BN_CLICKED或ON_COMMAND對映,Dialog Bar上的輸入框可以在父視窗中進行ON_EN_CHANGE,ON_EN_MAXTEXT等輸入框對應的對映。

Dialog Bar支援ON_UPDATE_COMMAND_UI的相關操作,如SetText,Enable。

在以後的章節4.D 利用AppWizard建立並使用ToolBar StatusBar Dialog Bar會給出使用的方法。 

4.D 利用AppWizard建立並使用ToolBar StatusBar Dialog Bar

執行時程式介面如介面圖,該程式擁有一個工具條用於顯示兩個命令按鈕,一個用於演示如何使按鈕處於檢查狀態,另一個根據第一個按鈕的狀態來禁止/允許自身。(設定檢查狀態和允許狀態都通過OnUpdateCommand實現)此外Dialog Bar上有一個輸入框和按鈕,這兩個子視窗的禁止/允許同樣是根據工具條上的按鈕狀態來確定,當按下Dialog Bar上的按鈕時將顯示輸入框中的文字內容。狀態條的第一部分用於顯示各種提示,第二部分用於利用OnUpdateCommand顯示當前時間。同時在程式中演示瞭如何設定選單項的命令解釋字元(將在狀態條的第一部分顯示)和如何設定工具條的提示字元(利用一個小的ToolTip視窗顯示)。


生成應用:利用AppWizard生成一個MFC工程,圖例,並設定為單文件介面圖例,最後選擇工具條,狀態條和ReBar支援,圖例

修改選單:利用資源編輯器刪除多餘的選單並新增一個新的彈出選單和三個子選單,圖例,分別是: 
名稱 ID 說明字元 
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前的字串將顯示在狀態條中作為命令解釋,\n後的部分將作為具有相同ID的工具條按鈕的提示顯示在ToolTip視窗中。 
修改Dialog Bar:在Dialog Bar中新增一個輸入框和按鈕,按鈕的ID為IDM_SHOW_TXT與一個選單項具有相同的ID,這樣可以利用對映選單訊息來處理按鈕訊息(當然使用不同ID值也可以利用ON_COMMAND來對映Dialog Bar上的按鈕訊息,但是ClassWizard沒有提供為Dialog Bar上按鈕進行對映的途徑,只能手工新增訊息對映程式碼)。圖例
修改工具條:在工具條中新增兩個按鈕,ID值為IDM_CHECK和IDM_DISABLE和其中兩個選單項具有相同的ID值。圖例
利用ClassWizard為三個選單項新增訊息對映和更新命令。圖例
修改MainFrm.h檔案 
//新增一個成員變數來記錄工具條上Check按鈕的檢查狀態。
protected:
 BOOL m_fCheck;
//手工新增狀態條第二部分用於顯示時間的更新命令,
和用於禁止/允許輸入框的更新命令
 //{{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自動產生的程式碼
 afx_msg void OnUpdateTime(CCmdUI* pCmdUI); //顯示時間
 afx_msg void OnUpdateInput(CCmdUI* pCmdUI); //禁止/允許輸入框
修改MainFrm.cpp檔案 
//修改狀態條上各部分ID
#define ID_TIME   0x705 //作為狀態條上第二部分ID
static UINT indicators[] =
{
 ID_SEPARATOR,           // status line indicator
 ID_SEPARATOR,   //先設定為ID_SEPARATOR,
在狀態條建立後再進行修改
};
//修改訊息對映
 //{{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自動生成程式碼
 ON_UPDATE_COMMAND_UI(ID_TIME, OnUpdateTime) ////顯示時間
 ON_UPDATE_COMMAND_UI(IDC_INPUT_TEST, OnUpdateInput) 
//禁止/允許輸入框
//修改OnCreate函式,重新設定狀態條第二部分ID值
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
....
 // by wenyy 修改狀態條上第二部分資訊
 m_wndStatusBar.SetPaneInfo(1,ID_TIME,SBPS_NORMAL,60);
//set the width
 return 0;
}
//修改經過對映的訊息處理函式程式碼
void CMainFrame::OnCheck() 
{
 //在Check按鈕被按下時改變並儲存狀態
 m_fCheck=!m_fCheck;
}

void CMainFrame::OnUpdateCheck(CCmdUI* pCmdUI) 
{
 //Check按鈕是否設定為檢查狀態
 pCmdUI->SetCheck(m_fCheck);
}

void CMainFrame::OnDisable() 
{
 //Disable按鈕被按下
 AfxMessageBox("you press disable test");
}

void CMainFrame::OnUpdateDisable(CCmdUI* pCmdUI) 
{
 //根據Check狀態決定自身禁止/允許狀態
 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上按鈕根據Check狀態決定自身禁止/允許狀態
 pCmdUI->Enable(m_fCheck);
}

void CMainFrame::OnUpdateInput(CCmdUI* pCmdUI) 
{
 //Dialog Bar上輸入框根據Check狀態決定自身禁止/允許狀態
 pCmdUI->Enable(m_fCheck);
}

void CMainFrame::OnUpdateTime(CCmdUI* pCmdUI) 
{
 //根據當前時間設定狀態條上第二部分文字
 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類派生圖中我們可以看出視窗的派生關係,派生圖,所有的視窗類都是由CWnd派生。所有CWnd的成員函式在其派生類中都可以使用。本節介紹一些常用的功能給大家。

改變視窗狀態:
BOOL EnableWindow( BOOL bEnable = TRUE );可以設定視窗的禁止/允許狀態。BOOL IsWindowEnabled( );可以查詢視窗的禁止/允許狀態。 
BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改視窗的風格,而不需要呼叫SetWindowLong 
BOOL IsWindowVisible( ) 可以檢查視窗是否被顯示。 
BOOL ShowWindow( int nCmdShow );將改變視窗的顯示狀態,nCmdShow可取如下值:

SW_HIDE 隱藏視窗 
SW_MINIMIZE SW_SHOWMAXIMIZED 最小化視窗 
SW_RESTORE 恢復視窗 
SW_SHOW 顯示視窗 
SW_SHOWMINIMIZED 最大化視窗

改變視窗位置:
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移動視窗。
void GetWindowRect( LPRECT lpRect ) ;可以得到視窗的矩形位置。
BOOL IsIconic( ) ;可以檢測視窗是否已經縮為圖示。
BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改變視窗的Z次序,此外還可以移動視窗位置。

使視窗失效,印發重繪:
void Invalidate( BOOL bErase = TRUE );使整個視窗失效,bErase將決定視窗是否產生重繪。
void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );將使指定的矩形/多邊形區域失效。

視窗查詢: 
static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以視窗的類名和視窗名查詢視窗。任一引數設定為NULL表對該引數代表的資料進行任意匹配。如FindWindow("MyWnd",NULL)表明查詢類名為MyWnd的所有視窗。 
BOOL IsChild( const CWnd* pWnd ) 檢測視窗是否為子視窗。 
CWnd* GetParent( ) 得到父視窗指標。 
CWnd* GetDlgItem( int nID ) 通過子視窗ID得到視窗指標。 
int GetDlgCtrlID( ) 得到視窗ID值。 
static CWnd* PASCAL WindowFromPoint( POINT point );將從螢幕上某點座標得到包含該點的視窗指標。 
static CWnd* PASCAL FromHandle( HWND hWnd );通過HWND構造一個CWnd*指標,但該指標在空閒時會被刪除,所以不能儲存供以後使用。

時鐘:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以建立一個時鐘,如果lpfnTimer回撥函式為NULL,視窗將會收到WM_TIMER訊息,並可以在afx_msg void OnTimer( UINT nIDEvent );中安排處理程式碼 
BOOL KillTimer( int nIDEvent );刪除一個指定時鐘。

可以利用過載來新增訊息處理的虛擬函式: 
afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );視窗被建立時被呼叫 
afx_msg void OnDestroy( );視窗被銷燬時被呼叫 
afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到視窗尺寸時被呼叫 
afx_msg void OnSize( UINT nType, int cx, int cy );視窗改變大小後被呼叫 
afx_msg void OnMove( int x, int y );視窗被移動後時被呼叫 
afx_msg void OnPaint( );視窗需要重繪時時被呼叫,你可以填如繪圖程式碼,對於檢視類不需要過載OnPaint,所有繪圖程式碼應該在OnDraw中進行 
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字元輸入時被呼叫 
afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );鍵盤上鍵被按下/放開時被呼叫 
afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );滑鼠左/右鍵按下時被呼叫 
afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );滑鼠左/右鍵放開時被呼叫 
afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );滑鼠左/右鍵雙擊時被呼叫 
afx_msg void OnMouseMove( UINT nFlags, CPoint point );滑鼠在視窗上移動時被呼叫
*********************************************    
5.1 使用資源編輯器編輯對話方塊

在Windows開發中彈出對話方塊是一種常用的輸入/輸出手段,同時編輯好的對話方塊可以儲存在資原始檔中。Visual C++提供了對話方塊編輯工具,利用編輯工具可以方便的新增各種控制元件到對話方塊中,而且利用ClassWizard可以方便的生成新的對話方塊類和對映訊息。

首先資源列表中按下右鍵,可以在彈出選單中選擇“插入對話方塊”,如圖1。然後再開啟該對話方塊進行編輯,你會在螢幕上看到一個控制元件板,如圖2。你可以將所需要新增的控制元件拖到對話方塊上,或是先選中後再在對話方塊上用滑鼠畫出所佔的區域。

接下來我們在對話方塊上產生一個輸入框,和一個用於顯示圖示的圖片框。之後我們使用滑鼠右鍵單擊產生的控制元件並選擇其屬性,如圖3。我們可以在屬性對話方塊中編輯控制元件的屬性同時也需要指定控制元件ID,如圖4,如果在選擇對話方塊本身的屬性那麼你可以選擇對話方塊的一些屬性,包括字型,外觀,是否有系統選單等等。最後我們編輯圖片控制元件的屬性,如圖5,我們設定控制元件的屬性為顯示圖示並指明一個圖示ID。

接下來我們新增一些其他的控制元件,最後的效果如圖6。按下Ctrl-T可以測試該對話方塊。此外在對話方塊中還有一個有用的特性,就是可以利用Tab鍵讓輸入焦點在各個控制元件間移動,要達到這一點首先需要為控制元件設定在Tab鍵按下時可以接受焦點移動的屬性Tab Stop,如果某一個控制元件不打算利用這一特性,你需要清除這一屬性。然後從選單“Layout”選擇Tab Order來確定焦點移動順序,如圖7。使用滑鼠依此點選控制元件就可以重新規定焦點移動次序。最後按下Ctrl-T進行測試。

最後我們需要為對話方塊產生新的類,ClassWizard可以替我們完成大部分的工作,我們只需要填寫幾個引數就可以了。在編輯好的對話方塊上雙擊,然後系統回詢問是否新增新的對話方塊,選擇是並在接下來的對話方塊中輸入類名就可以了。ClassWizard會為你產生所需要的標頭檔案和CPP檔案。然後在需要使用的地方包含相應的標頭檔案,對於有模式對話方塊使用DoModal()產生,對於無模式對話方塊使用Create()產生。相關程式碼如下;

void CMy51_s1View::OnCreateDlg() 
{//產生無模式對話方塊
 CTestDlg *dlg=new CTestDlg;
 dlg->Create(IDD_TEST_DLG);
 dlg->ShowWindow(SW_SHOW);
}

void CMy51_s1View::OnDoModal() 
{//產生有模式對話方塊
 CTestDlg dlg;
 int iRet=dlg.DoModal();
 TRACE("dlg return %d\n",iRet);
}

下載例子。如果你在除錯這個程式時你會發現程式在退出後會有記憶體洩漏,這是因為我沒有釋放無模式對話方塊所使用的記憶體,這一問題會在以後的章節5.3 建立無模式對話方塊中專門講述。

關於在使用對話方塊時Enter鍵和Escape鍵的處理:在使用對話方塊是你會發現當你按下Enter鍵或Escape鍵都會退出對話方塊,這是因為Enter鍵會引起CDialog::OnOK()的呼叫,而Escape鍵會引起CDialog::OnCancel()的呼叫。而這兩個呼叫都會引起對話方塊的退出。在MFC中這兩個成員函式都是虛擬函式,所以我們需要進行過載,如果我們不希望退出對話方塊那麼我們可以在函式中什麼都不做,如果需要進行檢查則可以新增檢查程式碼,然後呼叫父類的OnOK()或OnCancel()。相關程式碼如下;

void CTestDlg::OnOK()
{
 AfxMessageBox("你選擇確定");
 CDialog::OnOK();
}

void CTestDlg::OnCancel()
{
 AfxMessageBox("你選擇取消");
 CDialog::OnCancel();
}

5.2 建立有模式對話方塊

使用有模式對話方塊時在對話方塊彈出後呼叫函式不會立即返回,而是等到對話方塊銷燬後才會返回(請注意在對話方塊彈出後其他視窗的訊息依然會被傳遞)。所以在使用對話方塊時其他視窗都不能接收使用者輸入。建立有模式對話方塊的方法是呼叫CDialog::DoModal()。下面的程式碼演示了這種用法:

CYourView::OnOpenDlg()
{
 CYourDlg dlg;
 int iRet=dlg.DoModal();
}

CDialog::DoModal()的返回值為IDOK,IDCANCEL。表明操作者在對話方塊上選擇“確認”或是“取消”。由於在對話方塊銷燬前DoModal不會返回,所以可以使用區域性變數來引用物件。在退出函式體後物件同時也會被銷燬。而對於無模式對話方塊則不能這樣使用,下節5.3 建立無模式對話方塊中會詳細講解。

你需要根據DoModal()的返回值來決定你下一步的動作,而得到返回值也是使用有模式對話方塊的一個很大原因。

使用有模式對話方塊需要注意一些問題,比如說不要在一些反覆出現的事件處理過程中生成有模式對話方塊,比如說在定時器中產生有模式對話方塊,因為在上一個對話方塊還未退出時,定時器訊息又會引起下一個對話方塊的彈出。

同樣的在你的對話方塊類中為了向呼叫者返回不同的值可以呼叫CDialog::OnOK()或是CDialog::OnCancel()以返回IDOK或IDCANCEL,如果你希望返回其他的值,你需要呼叫 
CDialog::EndDialog( int nResult );其中nResult會作為DoModal()呼叫的返回值。

下面的程式碼演示瞭如何使用自己的函式來退出對話方塊:

void CMy52_s1View::OnLButtonDown(UINT nFlags, CPoint point) 
{//建立對話方塊並得到返回值
 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()
{//什麼也不做
}
//在對話方塊中對三個按鈕訊息進行對映
void CTestDlg::OnExit1() 
{
 CDialog::OnOK();
}
void CTestDlg::OnExit2() 
{
 CDialog::OnCancel();
}
void CTestDlg::OnExit3() 
{
 CDialog::EndDialog(0XFF);
}

由於過載了OnOK和OnCancel所以在對話方塊中按下Enter鍵或Escape鍵時都不會退出,只有按下三個按鈕中的其中一個才會返回。

此外在對話方塊被生成是會自動呼叫BOOL CDialog::OnInitDialog(),你如果需要在對話方塊顯示前對其中的控制元件進行初始化,你需要過載這個函式,並在其中填入相關的初始化程式碼。利用ClassWizard可以方便的產生一些預設程式碼,首先開啟ClassWizard,選擇相應的對話方塊類,在右邊的訊息列表中選擇WM_INITDIALOG並雙擊,如圖,ClassWizard會自動產生相關程式碼,程式碼如下:

BOOL CTestDlg::OnInitDialog() 
{
 /*先呼叫父類的同名函式*/
 CDialog::OnInitDialog();
 /*填寫你的初始化程式碼*/ 
 return TRUE;  
}

有關對對話方塊中控制元件進行初始化會在5.4 在對話方塊中進行訊息對映中進行更詳細的講解。

5.3 建立無模式對話方塊
無模式對話方塊與有模式對話方塊不同的是在建立後其他視窗都可以繼續接收使用者輸入,因此無模式對話方塊有些類似一個彈出視窗。建立無模式對話方塊需要呼叫BOOL CDialog::Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );之後還需要呼叫 BOOL CDialog::ShowWindow( SW_SHOW);進行顯示,否則無模式對話方塊將是不可見的。相關程式碼如下: 
void CYourView::OnOpenDlg(void)
{
 /*假設IDD_TEST_DLG為已經定義的對話方塊資源的ID號*/
 CTestDlg *dlg=new CTestDlg;
 dlg->Create(IDD_TEST_DLG,NULL);
 dlg->ShowWindows(SW_SHOW);
 /*不要呼叫 delete dlg;*/
}
在上面的程式碼中我們新生成了一個對話方塊物件,而且在退出函式時並沒有銷燬該物件。因為如果此時銷燬該物件(物件被銷燬時視窗同時被銷燬),而此時對話方塊還在顯示就會出現錯誤。那麼這就提出了一個問題:什麼時候銷燬該物件。我時常使用的方法有兩個: 
在對話方塊退出時銷燬自己:在對話方塊中過載OnOK與OnCancel在函式中呼叫父類的同名函式,然後呼叫DestroyWindow()強制銷燬視窗,在對話方塊中對映WM_DESTROY訊息,在訊息處理函式中呼叫delete this;強行刪除自身物件。相關程式碼如下: 
void CTestDlg1::OnOK()
{
 CDialog::OnOK();
 DestroyWindow();
}

void CTestDlg1::OnCancel()
{
 CDialog::OnCancel();
 DestroyWindow();
}

void CTestDlg1::OnDestroy() 
{
 CDialog::OnDestroy();
 AfxMessageBox("call delete this");
 delete this;
}
這種方法的要點是在視窗被銷燬的時候,刪除自身物件。所以你可以在任何時候呼叫DestroyWindow()以達到徹底銷燬自身物件的作用。(DestroyWindow()的呼叫會引起OnDestroy()的呼叫) 
通過向父親視窗傳送訊息,要求其他視窗對其進行銷燬:首先需要定義一個訊息用於進行通知,然後在對話方塊中對映WM_DESTROY訊息,在訊息處理函式中呼叫訊息傳送函式通知其他視窗。在接收訊息的視窗中利用ON_MESSAGE對映處理訊息的函式,並在訊息處理函式中刪除對話方塊物件。相關程式碼如下: 
/*更改對話方塊的有關檔案*/
CTestDlg2::CTestDlg2(CWnd* pParent /*=NULL*/)
 : CDialog(CTestDlg2::IDD, pParent)
{/*m_pParent為一成員變數,用於儲存通知視窗的指標,
所以該指標不能是一個臨時指標*/
 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();
 /*向其他視窗傳送訊息,將自身指標作為一個引數傳送*/
 m_pParent->PostMessage(WM_DELETE_DLG,
(WPARAM)this);
}

/*在訊息接收視窗中新增訊息對映*/
/*在標頭檔案中新增函式定義*/
 afx_msg LONG OnDelDlgMsg(WPARAM wP,
LPARAM lP);
/*新增訊息對映程式碼*/
 ON_MESSAGE(WM_DELETE_DLG,OnDelDlgMsg)
END_MESSAGE_MAP()
/*實現訊息處理函式*/
LONG CMy53_s1View::OnDelDlgMsg(WPARAM wP,LPARAM lP)
{
 delete (CTestDlg2*)wP;
 return 0;
}
/*建立對話方塊*/
void CMy53_s1View::OnTest2() 
{
 CTestDlg2 *dlg=new CTestDlg2(this);
 dlg->Create(IDD_TEST_DLG_2);
 dlg->ShowWindow(SW_SHOW);
}
在這種方法中我們利用訊息來進行通知,在Window系統中利用訊息進行通知和傳遞資料的用法是很多的。 
同樣無模式對話方塊的另一個作用還可以用來在使用者在對話方塊中的輸入改變時可以及時的反映到其他視窗。下面的程式碼演示了在對話方塊中輸入一段文字,然後將其更新到檢視的顯示區域中,這同樣也是利用了訊息進行通知和資料傳遞。 
/*在對話方塊中取出資料,並向其他視窗傳送訊息和資料,
將資料指標作為一個引數傳送*/
void CTestDlg2::OnCommBtn() 
{
 char szOut[30];
 GetDlgItemText(IDC_OUT,szOut,30);
 m_pParent->SendMessage(WM_DLG_NOTIFY,
(WPARAM)szOut);
}

/*在訊息接收視窗中*/
/*對映訊息處理函式*/
 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;
}

此外這種用法利用訊息傳遞資料的方法對有模式對話方塊和其他的視窗間通訊也一樣有效。 
 

5.4 在對話方塊中進行訊息對映
利用對話方塊的一個好處是可以利用ClassWizard對對話方塊中各個控制元件產生的訊息進行對映,ClassWizrd可以列出各種控制元件可以使用的訊息,並能自動產生程式碼。在本節中我們以一個例子來講解如何在對話方塊中對子視窗訊息進行對映同時還講解如何對對話方塊中的子視窗進行初始化。 
首先我們產生編輯好一個對話方塊,如圖,在對話方塊中使用的控制元件和ID號如下表: 
ID 型別 
IDC_RADIO_TEST_1 圓形按鈕 
IDC_RADIO_TEST_2 圓形按鈕 
IDC_BUTTON_TEST 按鈕 
IDC_CHECK_TEST 檢查按鈕 
IDC_TREE_TEST 樹形控制元件 
IDC_LIST_CTRL List Ctrl 
IDC_TAB_CTRL Tab Ctrl 
IDC_LIST_TEST 列表框 
IDC_COMBO_TEST 組合框 
IDC_EDIT_TEST 輸入框 
首先我們需要在對話方塊的OnInitDialog()函式中對各個控制元件進行初始化,這裡我們使用CWnd* GetDlgItem( int nID )來通過ID號得到子視窗指標。(類似的函式還有UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) 通過ID號得到子視窗中輸入的數字,int GetDlgItemText( int nID, CString& rString ) 通過ID號得到子視窗中輸入的文字)。程式碼如下: 
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
}
接下來我們需要利用ClassWizard對控制元件所產生的訊息進行對映,開啟ClassWizard對話方塊,選中相關控制元件的ID,在右邊的列表中就會顯示出可用的訊息。如我們對按鈕的訊息進行對映,在選中按鈕ID(IDC_BUTTON_TEST)後,會看到兩個訊息,如圖,一個是BN_CLICKED,一個是BN_DOUBLECLICKED。雙擊BN_CLICKED後在彈出的對話方塊中輸入函式名,ClassWizard會產生按鈕被按的訊息對映。 
然後我們看看對TabCtrl的TCN_SELCHANGE訊息進行對映,如圖,在TabCtrl的當前頁選擇發生改變時這個訊息會被髮送,所以通過對映該訊息可以在當前頁改變時及時得到通知。
最後我們對輸入框的EN_CHANGE訊息進行對映,如圖,在輸入框中的文字改變後該訊息會被髮送。相關的程式碼如下: 
//標頭檔案中相關的訊息處理函式定義
 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()

//訊息處理函式
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"); 
}
對於其他的控制元件都可以採取類似的方法進行訊息對映。此外如果你對各種控制元件可以使用的訊息不熟悉,你可以通過使用對話方塊,然後利用ClassWizard產生相關程式碼的方法來進行學習,你也可以將ClassWizard產生的程式碼直接拷貝到其他需要的地方(不瞞你說,我最開始就是這樣學的 :-D 這也算一個小竅門)。 


5.5 在對話方塊中進行資料交換和資料檢查

MFC提供兩種方法在對話方塊中進行資料交換和資料檢查(Dialog data exchange/Dialog data validation),資料交換和資料檢查的思想是將某一變數和對話方塊中的一個子視窗進行關聯,然後通過呼叫BOOL UpdateData( BOOL bSaveAndValidate = TRUE )來指示MFC將變數中資料放入子視窗還是將子視窗中資料取到變數中並進行合法性檢查。

在進行資料交換時一個子視窗可以和兩種型別的變數相關聯,一種是控制元件(Control)物件,比如說按鈕子視窗可以和一個CButton物件相關聯,這種情況下你可以通過該物件直接控制子視窗,而不需要象上節中講的一樣使用GetDlgItem(IDC_CONTROL_ID)來得到視窗指標;一種是內容物件,比如說輸入框可以和一個CString物件關聯,也可以和一個UINT型別變數關聯,這種情況下你可以直接設定/獲取視窗中的輸入內容。

而資料檢查是在一個子視窗和一個內容物件相關聯時在存取內容時對內容進行合法性檢查,比如說當一個輸入框和一個CString物件關聯時,你可以設定檢查CString的物件的最長/最小長度,當輸入框和一個UINT變數相關聯時你可以設定檢查UINT變數的最大/最小值。在BOOL UpdateData( BOOL bSaveAndValidate = TRUE )被呼叫後,合法性檢查會自動進行,如果無法通過檢查MFC會彈出訊息框進行提示,並返回FALSE。

設定DDX/DDV在VC中非常簡單,ClassWizard可以替你完成所有的工作,你只需要開啟ClassWizard並選中Member Variables頁,如圖,你可以看到所有可以進行關聯的子視窗ID列表,雙擊一個ID會彈出一個新增變數的對話方塊,如圖,填寫相關的資訊後按下確定按鈕就可以了。然後選中你剛才新增的變數在底部的輸入框中輸入檢查條件,如圖。

下面我們看一個例子,對話方塊上的子視窗如圖設定,各子視窗的ID和關聯的變數如下表: 
ID 關聯的變數 作用 
IDC_CHECK_TEST BOOL m_fCheck 檢查框是否被選中 
IDC_RADOI_TEST_1 int m_iSel 當前選擇的圓形按鈕的索引 
IDC_COMBO_TEST CString m_szCombo 組合框中選中的文字或是輸入的文字 
IDC_EDIT_TEST CString m_szEdit 輸入框中輸入的文字,最大長度為5 
IDC_LIST_TEST CListBox m_lbTest 列表框物件 
這時候ClassWizard會自動生成變數定義和相關程式碼,在對話方塊的建構函式中可以對變數的初始值進行設定,此外在BOOL CDialog::OnInitDialog()中會呼叫UpdateData(FALSE),即會將變數中的資料放入視窗中 。相關程式碼如下: 
//標頭檔案中的變數定義,ClassWizard自動產生
// 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
//建構函式中賦初值
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產生的關聯和檢查程式碼
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中利用已經關聯過的變數m_lbTest
BOOL CMy55_s1Dlg::OnInitDialog()
{
 CDialog::OnInitDialog();
... 
 // TODO: Add extra initialization here
 //設定列表框中資料
 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
}
//對兩個按鈕訊息處理
//通過UpdateData(TRUE)得到視窗中資料
void CMy55_s1Dlg::OnGet() 
{
 if(UpdateData(TRUE))
 {
  //資料合法性檢查通過,可以使用變數中存放的資料
  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 未通過檢查
}
//通過UpdateData(FALSE)將資料放入視窗
void CMy55_s1Dlg::OnPut() 
{
 m_szEdit="onPut test";
 m_szCombo="onPut test";
 UpdateData(FALSE);

在上面的例子中我們看到利用DDX/DDV和UpdateData(BOOL)呼叫我們可以很方便的存取資料,而且也可以同時進行合法性檢查。
5.6 使用屬性對話方塊
屬性對話方塊不同於普通對話方塊的是它能同時提供多個選項頁,而每頁都可以由資源編輯器以編輯對話方塊的方式進行編輯,這樣給介面開發帶來了方便。同時使用上也遵守普通對話方塊的規則,所以學習起來很方便。屬性對話方塊由兩部分構成:多個屬性頁(CPropertyPage)和屬性對話方塊(CPropertySheet)。
首先需要編輯屬性頁,在資源編輯器中選擇插入,並且選擇屬性對話方塊後就可以插入一個屬性頁,如圖,或者選擇插入一個對話方塊,然後將其屬性中的Style設定為Child,Border設定為Thin也可以,如圖。然後根據這個對話方塊資源生成一個新類,在選擇基類時選擇CPropertyPage,ClassWizard會自動生成相關的程式碼。
而對於CPropertySheet也需要生成新類,並且將所有需要加入的屬性頁物件都作為成員變數。屬性對話方塊也分為有模式和無模式兩種,有模式屬性對話方塊使用DoModal()建立,無模式屬性對話方塊使用Create()建立。下面的程式碼演示瞭如何建立屬性對話方塊並新增屬性頁: 
//修改CPropertySheet派生類的建構函式為如下形式
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);
}
//建立有模式屬性對話方塊
void CMy56_s1Dlg::OnMod() 
{
 CSheet sheet;
 sheet.DoModal();
}
//建立無模式屬性對話方塊
void CMy56_s1Dlg::OnUnm() 
{
 CSheet *sheet=new CSheet;
 sheet->Create();
}
對於屬性對話方塊可以使用下面的一些成員函式: 
CPropertyPage* CPropertySheet::GetActivePage( )得到當前活動頁的指標。 
BOOL CPropertySheet::SetActivePage( int nPage )用於設定當前活動頁。 
int CPropertySheet::GetPageCount()用於得到當前頁總數。 
void CPropertySheet::RemovePage( int nPage )用於刪除一頁。 
而對於屬性頁來將主要通過過載一些函式來達到控制的目的: 
void CPropertyPage::OnOK() 在屬性對話方塊上按下“確定”按鈕後被呼叫 
void CPropertyPage::OnCancel() 在屬性對話方塊上按下“取消”按鈕後被呼叫 
void CPropertyPage::OnApply() 在屬性對話方塊上按下“應用”按鈕後被呼叫 
void CPropertyPage::SetModified( BOOL bChanged = TRUE ) 設定當前頁面上的資料被修改標記,這個呼叫可以使“應用”按鈕為允許狀態。 
此外利用屬性對話方塊你可以生成嚮導對話方塊,嚮導對話方塊同樣擁有多個屬性頁,但同時只有一頁被顯示,而且對話方塊上顯示的按鈕為“上一步”,“下一步”/“完成”,嚮導對話方塊會按照你新增頁面的順序依次顯示所有的頁。在顯示屬性對話方塊前你需要呼叫void CPropertySheet::SetWizardMode()。使用嚮導對話方塊時需要對屬性頁的BOOL CPropertyPage::OnSetActive( )進行過載,並在其中呼叫void CPropertySheet::SetWizardButtons( DWORD dwFlags )來設定嚮導對話方塊上顯示的按鈕。dwFlags的取值可為以下值的“或”操作: 
PSWIZB_BACK 顯示“上一步”按鈕 
PSWIZB_NEXT 顯示“下一步”按鈕 
PSWIZB_FINISH 顯示“完成”按鈕 
PSWIZB_DISABLEDFINISH 顯示禁止的“完成”按鈕 
void CPropertySheet::SetWizardButtons( DWORD dwFlags )也可以在其他地方呼叫,比如說在顯示最後一頁時先顯示禁止的“完成”按鈕,在完成某些操作後再顯示允許的“完成”按鈕。 
在使用嚮導對話方塊時可以通過過載一些函式來達到控制的目的: 
void CPropertyPage::OnWizardBack() 按下了“上一步”按鈕。返回0表示有系統決定需要顯示的頁面,-1表示禁止頁面轉換,如果希望顯示一個特定的頁面需要返回該頁面的ID號。 
void CPropertyPage::OnOnWizardNext() 按下了“下一步”按鈕。返回值含義與void CPropertyPage::OnWizardBack()相同。 
void CPropertyPage::OnWizardFinish() 按下了“完成”按鈕。返回FALSE表示不允許繼續,否則返回TRUE嚮導對話方塊將被結束。 
在嚮導對話方塊的DoModal()返回值為ID_WIZFINISH或IDCANCEL。下面的程式碼演示瞭如何建立並使用嚮導對話方塊: 
//建立有模式嚮導對話方塊
void CMy56_s1Dlg::OnWiz() 
{
 CSheet sheet;
 sheet.SetWizardMode();
 int iRet=sheet.DoModal();//返回ID_WIZFINISH或IDCANCEL
}
//過載BOOL CPropertyPage::OnSetActive( )來控制顯示的按鈕
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 使用通用對話方塊
在Windows系統中提供了一些通用對話方塊如:檔案選擇對話方塊如圖,顏色選擇對話方塊如圖,字型選擇對話方塊如圖。在MFC中使用CFileDialog,CColorDialog,CFontDialog來表示。一般來講你不需要派生新的類,因為基類已經提供了常用的功能。而且在建立並等待對話方塊結束後你可以通過成員函式得到使用者在對話方塊中的選擇。 
CFileDialog檔案選擇對話方塊的使用:首先構造一個物件並提供相應的引數,建構函式原型如下:CFileDialog::CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );引數意義如下: 
bOpenFileDialog 為TRUE則顯示開啟對話方塊,為FALSE則顯示儲存對話檔案對話方塊。 
lpszDefExt 指定預設的副檔名。 
lpszFileName 指定預設的檔名。 
dwFlags 指明一些特定風格。 
lpszFilter 是最重要的一個引數,它指明可供選擇的檔案型別和相應的副檔名。引數格式如: "Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||";檔案型別說明和副檔名間用 | 分隔,同種型別檔案的副檔名間可以用 ; 分割,每種檔案型別間用 | 分隔,末尾用 || 指明。 
pParentWnd 為父視窗指標。 
建立檔案對話方塊可以使用DoModal(),在返回後可以利用下面的函式得到使用者選擇: 
CString CFileDialog::GetPathName( ) 得到完整的檔名,包括目錄名和副檔名如:c:\test\test1.txt 
CString CFileDialog::GetFileName( ) 得到完整的檔名,包括副檔名如:test1.txt 
CString CFileDialog::GetExtName( ) 得到完整的副檔名,如:txt 
CString CFileDialog::GetFileTitle ( ) 得到完整的檔名,不包括目錄名和副檔名如:test1 
POSITION CFileDialog::GetStartPosition( ) 對於選擇了多個檔案的情況得到第一個檔案位置。 
CString CFileDialog::GetNextPathName( POSITION& pos ) 對於選擇了多個檔案的情況得到下一個檔案位置,並同時返回當前檔名。但必須已經呼叫過POSITION CFileDialog::GetStartPosition( )來得到最初的POSITION變數。 
CColorDialog顏色選擇對話方塊的使用:首先通過CColorDialog::CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL )構造一個物件,其中clrInit為初始顏色。通過呼叫DoModal()建立對話方塊,在返回後呼叫COLORREF CColorDialog::GetColor( )得到使用者選擇的顏色值。 
CFontDialog字型選擇對話方塊的使用:首先構造一個物件並提供相應的引數,建構函式原型如下: CFontDialog::CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );構造一個物件,其中引數lplfInitial指向一個LOGFONG結構(該結構介紹請見2.2 在視窗中輸出文字),如果該引數設定為NULL表示不設定初始字型。pdcPrinter指向一個代表印表機裝置環境的DC物件,若設定該引數則選擇的字型就為印表機所用。pParentWnd用於指定父視窗。通過呼叫DoModal()建立對話方塊,在返回後通過呼叫以下函式來得到使用者選擇: 
void CFontDialog::GetCurrentFont( LPLOGFONT lplf );用來獲得所選字型的屬性。該函式有一個引數,該引數是指向LOGFONT結構的指標,函式將所選字型的各種屬性寫入這個LOGFONT結構中。 
CString CFontDialog::GetFaceName( ) 得到所選字型名字。 
int CFontDialog::GetSize( ) 得到所選字型的尺寸(以10個象素為單位)。 
COLORREF CFontDialog::GetColor( ) 得到所選字型的顏色。 
BOOL CFontDialog::IsStrikeOut( )BOOL CFontDialog::IsUnderline( )BOOL CFontDialog::IsBold( )BOOL CFontDialog::IsItalic( )得到所選字型的其他屬性,是否有刪除線,是否有下劃線,是否為粗體,是否為斜體。  
5.8 建立以對話方塊為基礎的應用

我認為初學者使用以對話方塊為基礎的應用是一個比較好的選擇,因為這樣一來可以擺脫一些開發介面的麻煩,此外也可以利用ClassWizard自動的新增訊息對映。

在VC中提供了生成“以對話方塊為基礎的應用”的功能,你所需要選擇的是在使用AppWizard的第一步選擇“對話方塊為基礎的應用”,如圖。VC會生成包含有應用派生類和對話方塊派生類的程式碼。在應用類的InitInstance()成員函式中可以看到如下的程式碼:

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;
}

這是產生一個有模式對話方塊並建立它,在對話方塊返回後通過返回FALSE來直接退出。在設計時通過編輯對話方塊資源你可以設計好介面,然後通過ClassWizard對映訊息來處理客戶的輸入,由於前幾節已經講過本節也就不再重複。

同樣基於對話方塊的應用也同樣可以使用屬性對話方塊做為介面,或者是通過使用經過派生的通用對話方塊作為介面。

提示:當你使用有模式對話方塊時最開始是無法隱藏視窗的,而只能在對話方塊顯示後再隱藏視窗,所以這會造成螢幕的閃爍。一個解決辦法就是採用無模式的對話方塊,無模式的對話方塊在建立後是隱藏的,直到你呼叫ShowWindow(SW_SHOW)才會顯示。相關程式碼如下:

BOOL CMy58_s1App::InitInstance()
{
 //必須新生成一個物件,而不能使用區域性變數
 CMy58_s1Dlg* pdlg=new CMy58_s1Dlg;
 m_pMainWnd = pdlg;
 pdlg->Create();
 return TRUE;
}

5.9 使用對話方塊作為子視窗
使用對話方塊作為子視窗是一種很常用的技術,這樣可以使介面設計簡化而且修改起來更加容易。 
簡單的說這種技術的關鍵就在於建立一個無模式的對話方塊,並在編輯對話方塊資源時指明Child風格和無邊框風格,如圖。接下來利用產生一個CDialog的派生類,並進行相關的訊息對映。在建立子視窗時需要利用下面的程式碼: 
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
 if (CView::OnCreate(lpCreateStruct) == -1)
  return -1;
 
 //建立子視窗
 m_dlgChild.Create(IDD_CHILD_DLG,this); 
 //重新定位
 m_dlgChild.MoveWindow(0,0,400,200);
 //顯示視窗
 m_dlgChild.ShowWindow(SW_SHOW);
 return 0;
}
此外還有一中類似的技術是利用CFormView派生類作為子視窗,在編輯對話方塊資源時也需要指明Child風格和無邊框風格。然後利用ClassWizard產生以CFormView為基類的派生類,但是由於該類的成員函式都是受保護的,所以需要對產生的標頭檔案進行如下修改: 
class CTestForm : public CFormView
{
//將建構函式和構析函式改為共有函式
public:
 CTestForm();
 virtual ~CTestForm();
 DECLARE_DYNCREATE(CTestForm)
...
}
有關建立子視窗的程式碼如下: 
int CMy59_s1View::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
 if (CView::OnCreate(lpCreateStruct) == -1)
  return -1;
 
 //對於CFormView派生類必須新生成物件而不能使用成員物件
 m_pformChild = new CTestForm;
 //由於CFormView的成員受保護,所以必須對指標進行強制轉換
 CWnd* pWnd=m_pformChild;
 pWnd->Create(NULL,NULL,WS_CHILD|WS_VISIBLE,CRect(0,210,400,400)
,this,1001,NULL);
 return 0;
}
最後你會看到如圖的視窗介面,上方的對話方塊子視窗和下方的FormView子視窗都可以通過資源編輯器預先編輯好。 
提示:對於CFormView派生類必須新生成物件而不能使用成員物件,因為在CView的OnDestroy()中會有如下程式碼:delete this;所以使用成員物件的結果會造成物件的二次刪除而引發異常。  


****************************************************   
6.1 WinSock介紹

Windows下網路程式設計的規範-Windows Sockets是Windows下得到廣泛應用的、開放的、支援多種協議的網路程式設計介面。從1991年的1.0版到1995年的2.0.8版,經過不斷完善並在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支援下,已成為Windows網路程式設計的事實上的標準。

Windows Sockets規範以U.C. Berkeley大學BSD UNIX中流行的Socket介面為範例定義了一套Micosoft Windows下網路程式設計介面。它不僅包含了人們所熟悉的Berkeley Socket風格的庫函式;也包含了一組針對Windows的擴充套件庫函式,以使程式設計師能充分地利用Windows訊息驅動機制進行程式設計。Windows Sockets規範本意在於提供給應用程式開發者一套簡單的API,並讓各家網路軟體供應商共同遵守。此外,在一個特定版本Windows的基礎上,Windows Sockets也定義了一個二進位制介面(ABI),以此來保證應用Windows Sockets API的應用程式能夠在任何網路軟體供應商的符合Windows Sockets協議的實現上工作。因此這份規範定義了應用程式開發者能夠使用,並且網路軟體供應商能夠實現的一套庫函式呼叫和相關語義。遵守這套Windows Sockets規範的網路軟體,我們稱之為Windows Sockets相容的,而Windows Sockets相容實現的提供者,我們稱之為Windows Sockets提供者。一個網路軟體供應商必須百分之百地實現Windows Sockets規範才能做到現Windows Sockets相容。任何能夠與Windows Sockets相容實現協同工作的應用程式就被認為是具有Windows Sockets介面。我們稱這種應用程式為Windows Sockets應用程式。Windows Sockets規範定義並記錄瞭如何使用API與Internet協議族(IPS,通常我們指的是TCP/IP)連線,尤其要指出的是所有的Windows Sockets實現都支援流套介面和資料包套介面.應用程式呼叫Windows Sockets的API實現相互之間的通訊。Windows Sockets又利用下層的網路通訊協議功能和作業系統呼叫實現實際的通訊工作。它們之間的關係如圖

通訊的基礎是套介面(Socket),一個套介面是通訊的一端。在這一端上你可以找到與其對應的一個名字。一個正在被使用的套介面都有它的型別和與其相關的程式。套介面存在於通訊域中。通訊域是為了處理一般的執行緒通過套介面通訊而引進的一種抽象概念。套介面通常和同一個域中的套介面交換資料(資料交換也可能穿越域的界限,但這時一定要執行某種解釋程式)。Windows Sockets規範支援單一的通訊域,即Internet域。各種程式使用這個域互相之間用Internet協議族來進行通訊(Windows Sockets 1.1以上的版本支援其他的域,例如Windows Sockets 2)。套介面可以根據通訊性質分類;這種性質對於使用者是可見的。應用程式一般僅在同一類的套介面間通訊。不過只要底層的通訊協議允許,不同型別的套介面間也照樣可以通訊。使用者目前可以使用兩種套介面,即流套介面和資料包套介面。流套介面提供了雙向的,有序的,無重複並且無記錄邊界的資料流服務。資料包套介面支援雙向的資料流,但並不保證是可靠,有序,無重複的。也就是說,一個從資料包套介面接收資訊的程式有可能發現資訊重複了,或者和發出時的順序不同。資料包套介面的一個重要特點是它保留了記錄邊界。對於這一特點,資料包套介面採用了與現在許多包交換網路(例如乙太網)非常類似的模型。

一個在建立分散式應用時最常用的範例便是客戶機/伺服器模型。在這種方案中客戶應用程式向伺服器程式請求服務。這種方式隱含了在建立客戶機/伺服器間通訊時的非對稱性。客戶機/伺服器模型工作時要求有一套為客戶機和伺服器所共識的慣例來保證服務能夠被提供(或被接受)。這一套慣例包含了一套協議。它必須在通訊的兩頭都被實現。根據不同的實際情況,協議可能是對稱的或是非對稱的。在對稱的協議中,每一方都有可能扮演主從角色;在非對稱協議中,一方被不可改變地認為是主機,而另一方則是從機。一個對稱協議的例子是Internet中用於終端模擬的TELNET。而非對稱協議的例子是Internet中的FTP。無論具體的協議是對稱的或是非對稱的,當服務被提供時必然存在"客戶程式"和"服務程式"。一個服務程式通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務程式一直處於休眠狀態,直到一個客戶對這個服務的地址提出了連線請求。在這個時刻,服務程式被"驚醒"並且為客戶提供服務-對客戶的請求作出適當的反應。這一請求/相應的過程可以簡單的用圖表示。雖然基於連線的服務是設計客戶機/伺服器應用程式時的標準,但有些服務也是可以通過資料包套介面提供的。

資料包套介面可以用來向許多系統支援的網路傳送廣播資料包。要實現這種功能,網路本身必須支援廣播功能,因為系統軟體並不提供對廣播功能的任何模擬。廣播資訊將會給網路造成極重的負擔,因為它們要求網路上的每臺主機都為它們服務,所以傳送廣播資料包的能力被限制於那些用顯式標記了允許廣播的套介面中。廣播通常是為了如下兩個原因而使用的:1. 一個應用程式希望在本地網路中找到一個資源,而應用程式對該資源的地址又沒有任何先驗的知識。2. 一些重要的功能,例如路由要求把它們的資訊傳送給所有可以找到的鄰機。被廣播資訊的目的地址取決於這一資訊將在何種網路上廣播。Internet域中支援一個速記地址用於廣播-INADDR_BROADCAST。由於使用廣播以前必須捆綁一個資料包套介面,所以所有收到的廣播訊息都帶有傳送者的地址和埠。

Intel處理器的位元組順序是和DEC VAX處理器的位元組順序一致的。因此它與68000型處理器以及Internet的順序是不同的,所以使用者在使用時要特別小心以保證正確的順序。任何從Windows Sockets函式對IP地址和埠號的引用和傳送給Windows Sockets函式的IP地址和埠號均是按照網路順序組織的,這也包括了sockaddr_in結構這一資料型別中的IP地址域和埠域(但不包括sin_family域)。考慮到一個應用程式通常用與"時間"服務對應的埠來和伺服器連線,而伺服器提供某種機制來通知使用者使用另一埠。因此getservbyname()函式返回的埠號已經是網路順序了,可以直接用來組成一個地址,而不需要進行轉換。然而如果使用者輸入一個數,而且指定使用這一埠號,應用程式則必須在使用它建立地址以前,把它從主機順序轉換成網路順序(使用htons()函式)。相應地,如果應用程式希望顯示包含於某一地址中的埠號(例如從getpeername()函式中返回的),這一埠號就必須在被顯示前從網路順序轉換到主機順序(使用ntohs()函式)。由於Intel處理器和Internet的位元組順序是不同的,上述的轉換是無法避免的,應用程式的編寫者應該使用作為Windows Sockets API一部分的標準的轉換函式,而不要使用自己的轉換函式程式碼。因為將來的Windows Sockets實現有可能在主機位元組順序與網路位元組順序相同的機器上執行。因此只有使用標準的轉換函式的應用程式是可移植的。

在MFC中MS為套介面提供了相應的類CAsyncSocket和CSocket,CAsyncSocket提供基於非同步通訊的套介面封裝功能,CSocket則是由CAsyncSocket派生,提供更加高層次的功能,例如可以將套介面上傳送和接收的資料和一個檔案物件(CSocketFile)關聯起來,通過讀寫檔案來達到傳送和接收資料的目的,此外CSocket提供的通訊為同步通訊,資料未接收到或是未傳送完之前呼叫不會返回。此外通過MFC類開發者可以不考慮網路位元組順序和忽略掉更多的通訊細節。

在一次網路通訊/連線中有以下幾個引數需要被設定:本地IP地址 - 本地埠號 - 對方埠號 - 對方IP地址。左邊兩部分稱為一個半關聯,當與右邊兩部分建立連線後就稱為一個全關聯。在這個全關聯的套介面上可以雙向的交換資料。如果是使用無連線的通訊則只需要建立一個半關聯,在傳送和接收時指明另一半的引數就可以了,所以可以說無連線的通訊是將資料傳送到另一臺主機的指定埠。此外不論是有連線還是無連線的通訊都不需要雙方的埠號相同。

在建立CAsyncSocket物件時通過呼叫 
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 )通過指明lEvent所包含的標記來確定需要非同步處理的事件,對於指明的相關事件的相關函式呼叫都不需要等待完成後才返回,函式會馬上返回然後在完成任務後傳送事件通知,並利用過載以下成員函式來處理各種網路事件: 標記 事件 需要過載的函式 
FD_READ 有資料到達時發生 void OnReceive( int nErrorCode );  
FD_WRITE 有資料傳送時產生  void OnSend( int nErrorCode );  
FD_OOB 收到外帶資料時發生 void OnOutOfBandData( int nErrorCode );  
FD_ACCEPT 作為服務端等待連線成功時發生 void OnAccept( int nErrorCode );  
FD_CONNECT 作為客戶端連線成功時發生 void OnConnect( int nErrorCode );  
FD_CLOSE 套介面關閉時發生 void OnClose( int nErrorCode );  
我們看到過載的函式中都有一個引數nErrorCode,為零則表示正常完成,非零則表示錯誤。通過int CAsyncSocket::GetLastError()可以得到錯誤值。


下面我們看看套介面類所提供的一些功能,通過這些功能我們可以方便的建立網路連線和傳送資料。

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 );用於建立一個本地套介面,其中nSocketPort為使用的埠號,為零則表示由系統自動選擇,通常在客戶端都使用這個選擇。nSocketType為使用的協議族,SOCK_STREAM表明使用有連線的服務,SOCK_DGRAM表明使用無連線的資料包服務。lpszSocketAddress為本地的IP地址,可以使用點分法表示如10.1.1.3。 
BOOL CAsyncSocket::Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )作為等待連線方時產生一個網路半關聯,或者是使用UDP協議時產生一個網路半關聯。 
BOOL CAsyncSocket::Listen( int nConnectionBacklog = 5 )作為等待連線方時指明同時可以接受的連線數,請注意不是總共可以接受的連線數。 
BOOL CAsyncSocket::Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )作為等待連線方將等待連線建立,當連線建立後一個新的套介面將被建立,該套介面將會被用於通訊。 
BOOL CAsyncSocket::Connect( LPCTSTR lpszHostAddress, UINT nHostPort );作為連線方發起與等待連線方的連線,需要指明對方的IP地址和埠號。 
void CAsyncSocket::Close( );關閉套介面。 
int CAsyncSocket::Send( const void* lpBuf, int nBufLen, int nFlags = 0 )
int CAsyncSocket::Receive( void* lpBuf, int nBufLen, int nFlags = 0 );在建立連線後傳送和接收資料,nFlags為標記位,雙方需要指明相同的標記。 
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 );對於無連線通訊傳送和接收資料,需要指明對方的IP地址和埠號,nFlags為標記位,雙方需要指明相同的標記。 
我們可以看到大多數的函式都返回一個布林值表明是否成功。如果發生錯誤可以通過int CAsyncSocket::GetLastError()得到錯誤值。

由於CSocket由CAsyncSocket派生所以擁有CAsyncSocket的所有功能,此外你可以通過BOOL CSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL )來建立套介面,這樣建立的套介面沒有辦法非同步處理事件,所有的呼叫都必需完成後才會返回。

在上面的介紹中我們看到MFC提供的套介面類遮蔽了大多數的細節,我們只需要做很少的工作就可以開發出利用網路進行通訊的軟體。

6.2 利用WinSock進行無連線的通訊

WinSock提供了對UDP(使用者資料包協議)的支援,通過UDP協議我們可以向指定IP地址的主機傳送資料,同時也可以從指定IP地址的主機接收資料,傳送和接收方處於相同的地位沒有主次之分。利用CSocket操縱無連線的資料傳送很簡單,首先生成一個本地套介面(需要指明SOCK_DGRAM標記),然後利用 
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 )接收資料。函式呼叫順序如圖。

利用UDP協議傳送和接收都可以是雙向的,就是說任何一個主機都可以傳送和接收資料。但是UDP協議是無連線的,所以傳送的資料不一定能被接收,此外接收的順序也有可能與傳送順序不一致。下面是相關程式碼:

/*
傳送方在埠6800上向接收方埠6801傳送資料
*/
//傳送方程式碼:
BOOL CMy62_s1_clientDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 //建立本地套介面
 m_sockSend.Create(6800,SOCK_DGRAM,NULL);
 //繫結本地套介面
 m_sockSend.Bind(6800,"127.0.0.1");
 //建立一個定時器定時傳送
 SetTimer(1,3000,NULL);
...
}
void CMy62_s1_clientDlg::OnTimer(UINT nIDEvent) 
{
 static iIndex=0;
 char szSend[20];
 sprintf(szSend,"%010d",iIndex++);
 //傳送UDP資料
 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();

 //建立本地套介面
 m_sockRecv.Create(6801,SOCK_DGRAM,"127.0.0.1");
 //繫結本地套介面
 m_sockRecv.Bind(6801,"127.0.0.1");
 //建立一個定時器定時讀取
 SetTimer(1,3000,NULL);
...
}
void CMy62_s1_serverDlg::OnTimer(UINT nIDEvent) 
{
 char szRecv[20];
 CString szIP("127.0.0.1");
 UINT uPort=6800;
 //接收UDP資料
 int iRecv =m_sockRecv.ReceiveFrom(szRecv,10,szIP,uPort,0);
 TRACE("received %d byte\n",iRecv);
...
}
/*
接收方採用同步讀取資料的方式,所以沒有讀到資料函式呼叫將不會返回
*/

下載例子程式碼,62_s1_client工程為傳送方,62_s1_server工程為接收方

6.3 利用WinSock進行有連線的通訊
WinSock提供了對TCP(傳輸控制協議)的支援,通過TCP協議我們可以與指定IP地址的主機建立,同時利用建立的連線可以雙向的交換資料。利用CSocket操縱有連線資料交換很簡單,但是在有連線的通訊中必需有一方扮演伺服器的角色等待另一方(客戶方)的連線請求,所以伺服器方需要建立一個監聽套介面,然後在此套介面上等待連線。當連線建立後會產生一個新的套介面用於通訊。而客戶方在建立套介面後只需要簡單的呼叫連線函式就可以建立連線。對於有連線的通訊不論是資料的傳送還是傳送與接收的順序都是有保證的。雙方的函式呼叫順序如圖。
下面的程式碼演示瞭如何建立連線和傳送/接收資料: 
/*
伺服器方在埠6802上等待連線,當連線建立後關閉監聽套介面
客戶方向伺服器埠6802發起連線請求
*/
BOOL CMy63_s1_serverDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 CSocket sockListen;
 //建立本地套介面
 sockListen.Create(6802,SOCK_STREAM,"127.0.0.1");
 //繫結引數
 sockListen.Bind(6802,"127.0.0.1");
 sockListen.Listen(5);
 //等待連線請求,m_sockSend為成員變數,用於通訊
 sockListen.Accept(m_sockSend);
 //關閉監聽套介面
 sockListen.Close();
 //啟動定時器,定時傳送資料
 SetTimer(1,3000,NULL);
...
}
void CMy63_s1_serverDlg::OnTimer(UINT nIDEvent) 
{
 static iIndex=0; 
 char szSend[20]; 
 sprintf(szSend,"%010d",iIndex++); 
 //傳送TCP資料
 int iSend= m_sockSend.Send(szSend,10,0);
...
}
BOOL CMy63_s1_clientDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 //建立本地套介面
 m_sockRecv.Create();
 //發起連線請求
 BOOL fC=m_sockRecv.Connect("127.0.0.1",6802);
 TRACE("connect is %s\n",(fC)?"OK":"Error");
 //啟動定時器,定時接收資料
 SetTimer(1,3000,NULL);
...
}
void CMy63_s1_clientDlg::OnTimer(UINT nIDEvent) 
{
 char szRecv[20]; 
 //接收TCP資料
 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);
 }
...
}

相關文章