前言
如果您已經是MFC高手,那麼這篇文章不適合您;如果您寫過MFC程式,那麼這篇文章可能對您意義不大;如果您是位MFC菜鳥,那麼該文章就比較適合您了。本文章是敘述的是通過執行個體來詳細解釋如何使用MFC開發應用程式的。筆者選用的開發環境是Visual Studio 2008(簡稱VS9)專業版中的Visual C++ 2008(VC9),此文章中的主要內容也同樣適用於使用Visual C++ 6.0的讀者。若問為什麼筆者要選擇Microsoft(MS)的這款產品,因為筆者認為MS的工具最大優點就在細節上設計非常好,使用者用著非常舒服,具體優點會在以後提到。由於筆者此次是第一次寫教程,所以有疏略之處,還請讀者見諒。
筆者的E-Mail是:lightning_0721@163.com。
緒論
MFC對話方塊程式,是一種以MFC為架構的Windows表單程式,開發MFC對話方塊屬於WIN32開發,WIN32開發與16位程式開發不同,除了源檔案外,還需有資源檔,在編譯時間,源檔案和資源檔會分別被編譯為*.obj和*.res檔案,連結時再組合到一起。在資源檔中,描述了應用程式的表徵圖、版本、映像、字串等資訊。在資源檔中一般還會包含resource.h檔案,在這個標頭檔中定義了一些宏,如定義控制項的ID值等。
讓我們先瞭解一下什麼是對話方塊:
http://baike.baidu.com/view/119316.htm
以上文字選取百度百科的“對話方塊”詞條。
一、建立項目
上面說了那麼多,對於剛剛學習Windows程式開發的讀者來說,可能有些摸不著頭腦,不過沒有關係,接下來筆者將會一起通過一個執行個體來學習MFC對話方塊。
使用VS9開發Windows應用程式,第一部是建立一個VC項目(其實VS9的一個開發單元是解決方案,在VC++6.0及以下版本稱為工作區),系統會自動產生一個與建立項目同名的解決方案。
在檔案菜單中選擇“建立”,建立一個項目。在彈出的建立項目對話方塊中選擇MFC應用程式,並在為項目起一個名稱,HelloWorld(如果在同一個目錄中,已經有一個項目與你要建立的項目同名,那麼則會建立失敗),點擊確定按鈕。
然後出現的是MFC應用程式嚮導,VC將會根據程式員在嚮導中的設定自動產生一個應用程式架構。首先出現的是一個概述,這個我們不用管他,下一步。
在這步中,設定我們要建立MFC程式的類型,選擇“基於對話方塊”,在最底處,有一個使用Unicode庫,關於這個Unicode,我們會在今後的論述中提到,此時不要管它,保持預設選中狀態。在右側設定MFC的使用方法,其中“在共用DLL中使用MFC”是讓你編譯的程式通過調用動態連結程式庫的方式來運行MFC程式,如果目標機器沒有安裝VS9或沒有VC9的MFC運行庫檔案的話,那麼產生的程式將無法在這種機器上運行,所以程式員要根據具體情況來選擇。由於我們是學習目的,所以保持此項預設狀態即可,下一步。
在這一步中,嚮導要求我們設定使用者介面(UI)功能。不用管它,保持預設,下一步。
接下來的這一步要設定進階功能,在此只解釋Windows通訊端選項的含義:如果程式員要開發網路應用程式,那麼選擇該選項後,系統會自動添加Socket初始化語句(Socket是用於開發網路應用程式的一個模型,具體資料讀者可以到網上查閱)。繼續下一步。
這是應用程式嚮導的最後一步,在這一步中,嚮導問我們列出了將要產生的類的列表(類名可以修改)。好,點擊完成按鈕,應用程式嚮導設定完成。
嚮導運行結束後,開發介面就出現了,左側的是解決方案的檔案資源樹(包含一個項目,以及屬於該項目的檔案),右側是一個UI設計視窗,在VS9中,這些需要程式員設計的部分預設都是選項卡的形式。
在這個UI設計器上的對話方塊就是我們的程式介面,此時可以按下F5鍵或點擊工具列上的綠色箭頭按鈕來啟動調試這個MFC程式了,因為前面已經說過,MFC程式的架構已經由系統為我們搭建完成,這個架構已經是可以編譯執行的原始碼了,我們只需要在其基礎上完成修改和擴充。程式啟動後,這個程式的介面就出現在我們面前了。點“取消”或“確定”來結束次對話方塊程式,對應也可一按鍵盤上的Esc或Enter鍵來達到同樣效果。如果由於程式員編寫的代碼有問題導致程式無法響應,那麼可以點擊VS9調試工具列上的“停止調試”來終止程式的調試。
如果程式員只想測試程式UI的話,那麼可以點擊“對話方塊編輯器”工具列中的“測試對話方塊按鈕”。
注意在啟動調試這個“綠箭頭”按鈕旁有一個列表框(預設Debug),如果這個程式在開發中,建議保持Debug,如果要發布那麼選擇Release,讀者可以分別用Debug和Release兩種模式編譯器,我們可以發現,在Release下產生的程式要比Debug下產生的程式小很多,這是因為系統在Debug模式下產生的程式要包含調試時需要的種種資訊。
下面我們來設計這個程式的UI,首先在“視圖”菜單中選擇“工具箱”,這個工具箱視窗中,列出了很多常用的Windows控制項(如按鈕、複選框、編輯框、標籤等),點擊一個控制項,此時滑鼠變為十字型,像使用畫圖工具一樣在設計器上繪出該控制項,也可以同過拖拽的方法將控制項直接拖到UI設計器中的對話方塊上,還可以直接雙擊工具列中的控制項。
在UI設計器上的靜態標籤“TODO: 在此放置對話方塊控制項。”可以將其刪掉。按照上述方法在對話方塊上繪製一個按鈕控制項,右擊此控制項,選擇“屬性”會出現屬性視窗,在這個視窗中,左側是控制項的一種屬性,右側是該屬性的當前值,在屬性視窗的低端是屬性的具體含義。對於按鈕控制項,我們現在關注Caption屬性,即按鈕上顯示的文字,我們將其設為“Hello World”,輸入完畢後按斷行符號鍵確定。在UI設計器上的那個按鈕已經變為“Hello World”了。在看一下按鈕的ID屬性,這個ID表示對話方塊每個控制項唯一的標識(注意ID為IDC_STATIC的控制項代表該控制項為靜態,在程式運行時不可以動態改變改控制項的屬性,如標籤控制項。但我們亦可以通過,修改標籤的ID改為非IDC_STATIC值,這樣這個控制項就轉為動態控制項了,此種控制項可以用IDC_STATIC來標識,IDC_STATIC沒有唯一的限制,),我們一般需要將該屬性值改為更有意義的,如IDC_HELLOWORLD。
下面我們為按鈕控制項編寫事件響應代碼,右擊Hello World按鈕,選擇“添加事件處理常式”,依然彈出了一個嚮導對話方塊,在訊息類型中選擇選擇按鈕要響應的訊息,在這個程式中我們是要響應按鈕的單擊事件(其實在一般情況下,按鈕就是用來單擊的),所以保持預設“BN_CLICKED”,在類列表中說明了響應代碼是作為哪個類的成員函數,預設選擇那個以“Dlg”結尾的類,這個類是繼承於CDialog類的一個衍生類別。程式員可以設定自己的“函數處理常式名稱”。完成後點擊“添加編輯”按鈕,進入代碼編輯器編輯代碼(對於按鈕這種簡單控制項,我們要添加其響應代碼,一般在UI設計器中直接雙擊這個控制項即可直接進入到代碼編輯器中編輯響應預設訊息的函數)。
系統為我們自動在CHelloWorldDlg類中添加了OnBnClickedHelloworld()函數,該函數的聲明部分在HelloWorldDlg.h檔案中,在VS9開發環境的右側“方案總管”中雙擊該檔案,或在HelloWorldDlg.cpp檔案中右擊,在快顯功能表中選擇“轉到標頭檔”。讓我們來看一下這個MFC架構是如何構成的。
在這個標頭檔的頭部有一行“#pragma once”,這是條編譯命令,功能是讓次標頭檔(Header)在編譯時間只被編譯一次,因為同一個標頭檔可能被包含(include)過多次。在這個標頭檔中定義了CHelloWorld類,在這個類中聲明了一個HICON類型的m_hIcon成員變數,這是個描述該應用程式圖示的變數。如果你不瞭解HICON這個非標準類型,可以在代碼的HICON處右擊滑鼠,然後選擇“轉到聲明”或“轉到定義”,這是VS9會自動定位游標到HICON的聲明或定義代碼處,筆者認為這是一個非常體貼的功能,為團隊開發提供了很大的便利。
這裡,我們順便來簡單談談MFC採用的“匈牙利標識符命名法”,這是一個約定,可以增加代碼的可讀性。如果你聲明或定義了一個類,那麼這個類可以一“C”(class)為首碼,如CHelloWorldDlg類,如果要定義一個無符號型的局部變數,那麼可以用“u”(unsigned)為首碼,如UINT uPort; ULONG uFlags;如果是int或long類型的變數則以“n”為首碼,DWORD類型的變數首碼為“dw”,字元數組以“sz”作為首碼,CString類的對象以“str”作為首碼,指標以“lp”或“p”(long
pointer或pointer,在WIN32環境下這兩種指標並沒有什麼差別)作為首碼,引用以“r”為首碼,布爾型變數以“b”為首碼,控制代碼型的變數以“h”(handle)作為首碼。如果變數是全域的,那麼以“g_”(global)開頭,如BOOL g_bFlags;如果是類的成員變數則以“m_”(member)開頭,如HICON m_hIcon;。
討論完MFC的命名規則,讓我們繼續看這個標頭檔,在此類中聲明了一個標準建構函式CHelloWorldDlg(CWnd* pParent = NULL);可選形參CWnd* pParent指定此對話方塊的父視窗。
代碼enum { IDD = IDD_HELLOWORLD_DIALOG };的意思是MFC巧妙的將ID為IDD_HELLOWORLD_DIALOG的對話方塊資源與CHelloWorldDlg類綁定在一起。
這些除外的成員函數都是對父類CDialog中成員函數的重載。
成員函數virtual void DoDataExchange(CDataExchange* pDX);是用來支援DDX(對話方塊資料交換,將一個變數和一個控制項進行綁定的時候用DDX)和DDV(對話方塊資料效驗,檢驗該控制項是否為你所需要的時候用DDV)機制的成員函數。
成員函數virtual BOOL OnInitDialog();是在對話被建立(Create)後立即被執行的函數,因此在這裡可以添加對話方塊的初始化所需要的自訂代碼。
成員函數afx_msg void OnSysCommand(UINT nID, LPARAM lParam);是對話方塊的處理WM_SYSCOMMAND訊息的函數。WM_SYSCOMMAND訊息是關於系統控制的訊息,如滑鼠在標題列上的操作等。
成員函數afx_msg void OnPaint();是對話方塊處理WM_PAINT的函數,當對話方塊表單發生重繪時有WM_PAINT訊息到達程式。
成員函數afx_msg HCURSOR OnQueryDragIcon();當使用者拖動已最小化的視窗時系統調用此函數取得游標顯示。
接下來是一個DECLARE_MESSAGE_MAP()宏,這是MFC處理訊息用的,具體資料可以查閱MSDN。
最下一行afx_msg void OnBnClickedHelloworld();就是剛自動產生的處理按鈕單擊訊息的處理函數。
讀者可能注意到了afx_msg,在MFC中,只要是處理訊息的響應函數,在聲明時,都要加上afx_msg。通過尋找其定義,筆者認為這個並沒有什麼功能性意義,只是一個佔位用的或是一個說明標識。
我們分析完HelloWorldDlg.h檔案後,再來看看HelloWorldDlg.cpp檔案。在檔案的include部分,包含了stdafx.h檔案,讀者可以在“”stdafx.h””上右擊滑鼠,開啟該標頭檔,在這個標頭檔中包含了該MFC程式必要的檔案,如果程式員要添加自訂類,如果類中還需要MFC支援,那麼必須包含這個標頭檔。HelloWorld.h檔案中定義了ChelloWorldApp類(繼承於CWinApp),是MFC的應用程式類。一個MFC WIN32應用程式必須是在CWinApp對象之上的,在CHelloWorldApp類中的InitInstance()成員函數的定義中,有相應載入該對話方塊的代碼(CHelloWorldDlg
dlg;之後的代碼)。
然後是一組宏定義#ifdef _DEBUG #define new DEBUG_NEW #endif,_DEBUG宏對應前面提到的Debug模式。
接下來定義了另一個對話方塊類CAboutDlg,這是個“關於”對話方塊的類,用來顯示程式著作權資訊的。
來看看BEGIN_MESSAGE_MAP() …… END_MESSAGE_MAP()這組宏定義,在這中間的代碼就是MFC用來指定哪個訊息用哪個成員函數來響應。對於那些無需指定的,MFC則沒有明確指定,如ON_WM_PAINT()是指定處理WM_PAINT訊息的成員函數是OnPaint(),但,這個使用者一般不會去更改,所以在這裡就沒有去明確指定。但ON_BN_CLICKED()中,卻指定了具體的成員函數:ID值為IDC_HELLOWORLD的控制項的BN_CLICKED訊息由CHelloWorldDlg類的OnBnClickedHelloworld()成員函數來處理。
在看看OnInitDialog()函數的定義部分,首先調用了父類了初始化函數,接下來到SetIcon前,是將“關於”載入到了系統功能表中,這樣使用者在單擊程式表單左上方的表徵圖時,或右擊標題列時,或右擊工作列長條表徵圖時,彈出的菜單就會包含“關於”的功能表項目。
SetIcon()的後面有相應注釋:“設定大表徵圖”和“設定小表徵圖”。
在OnSysCommand()函數中,處理了有關標題列上發生的事件,代碼(nID & 0xFFF0) == IDM_ABOUTBOX表示,如果使用者點擊了標題列系統功能表中的“關於”功能表項目,顯示“關於”對話方塊。
在OnPaint()函數中的代碼,是用來實現重繪操作的。
取/設定編輯框內的文字、設定其可用狀態和設定其可見狀態等方法與操作按鈕的方法類似,在次就不再贅述了。
我們知道我們通過GetDlgItem()函數是擷取的其常值內容,如果想要擷取其數值,則可以用int nValue = _ttoi(strInput);的方法來轉換。這裡介紹一個DDX的方法,將一個成員變數與一個目標控制項進行綁定,這樣變數的值就可以用來反應控制項的值了,同樣,也可以通過設定這個變數的值,來控制控制項的值。下面來看具體步驟:在工具箱中,拖拽一個編輯框到對話方塊上,ID設為IDC_INPUT。右鍵點擊這個控制項,選擇“添加變數”功能表項目,在彈出的“新增成員變數嚮導”對話方塊中設定其訪問屬性(筆者建議用protected);在類別列表框中選擇Value(預設是Control);在變數類型組合框中選擇一種變數類型,這裡選擇int;輸入變數名m_nValue;最小值、最大值、最大字元數和注釋,根據實際情況選填(這裡略過);點擊完成按鈕。完成“添加變數”嚮導後,在HelloWorldDlg.cpp檔案中的CHelloWorldDlg::DoDataExchange()中,系統自動添加了一條語句:DDX_Text(pDX,
IDC_INPUT, m_nValue);這條語句的意思是將ID為IDC_INPUT的控制項與m_nValue成員變數進行綁定。當程式執行UpdateData()的時候,資料便開始進行交換,資料交換方向由UpdateData的參數確定。
在對話方塊上繪製一個按鈕,Caption設為“判斷”,雙擊編寫其訊息響應代碼:
void CHelloWorldDlg::OnBnClickedButton1()
{
// TODO: 在此添加控制項通知處理常式代碼
UpdateData(TRUE);
if (m_nValue >= 0) MessageBox(_T("大於等於零"));
else MessageBox(_T("小於零"));
}
這裡,UpdateData(TRUE);就是讓文字框的值更新到m_nValue裡,如果是UpdateData(FALSE);就是將變數中的資料返回給文字框裡。在對話方塊初始化時,MFC會預設調用一次UpdateData(FALSE);,用來初始化控制項中的顯示內容;在對話方塊結束時,MFC會調用一次UpdateData(TRUE);,用來更新控制項資料到成員變數中去。
還可以為為控制項添加控制項變數,即在“添加變數嚮導”中的變數類別列表中選擇Control,則系統會添加一個MFC的控制項類的對象作為改對話方塊的成員變數,並且將這個控制項對象與響應的控制項進行綁定,這樣操作這個控制項對象就相當於操作相應的控制項。
複選框(Check)和單選框(Radio)控制項。分別以ID為IDC_CHECK1和IDC_RADIO1的控制項為例:
擷取/設定其選中狀態:
成員函數原型:
int GetCheck() const;
void SetCheck(int nCheck);
這兩個函數都是CButton類的成員函數,所以在用GetDlgItem()擷取CWnd指標後,一定要強制轉換為CButton的指標類型。
例子:
BOOL bState;
bState = ((CButton*)GetDlgItem(IDC_CHECK1))->GetCheck(); //擷取複選框狀態
((CButton*)GetDlgItem(IDC_RADIO1))->SetCheck(1); //設定單選框狀態為選中
注意,當複選框的Tri-State屬性為True的時候,那麼複選框可以有三種狀態,在SetCheck()的時候可以通過傳給0、1或2來設定狀態。
操作複選框和單選框的狀態,同樣也可以採用DDX的方法,添加一個int型的成員變數來與控制項進行綁定。
列表框(List)和組合框(Combo),在此只介紹成員函數原型:
列表框常用成員函數:
void ResetContent(); //清空列表
int AddString(LPCTSTR lpszItem); //追加清單項目
int DeleteString(UINT nIndex); //刪除指定項
int SetSel(int nIndex, BOOL bSelect = TRUE); //設定指定項的選擇狀態
int SetCurSel(int nSelect); //設定當前選中項
int GetCurSel() const; //擷取當前選中項
int GetSelItems(int nMaxItems, LPINT rgIndex) const; //擷取所有選中項
int GetSelCount() const; //擷取選中項總數
int GetTextLen(int nIndex) const; //擷取指定項的文本長度
int GetText(int nIndex, LPTSTR lpszBuffer) const; //擷取指定項文本
void GetText(int nIndex, CString &rString) const;
int FindString(int nStartAfter, LPCTSTR lpszItem) const; //尋找指定項
對於組合框來說,可以理解為由編輯框和列表框組合而成的複合控制項,因此其MFC中的成員函數與也可以參照編輯框和列表框控制項類的成員函數。