最近,本人趕時髦,裝上了一套Visual Studio.net,安裝要2213M呢,硬盤上三個盤符總共剩下不足2G的地方了。不過,界面相當的漂亮,且功能強大,值得心慰。我終於可以在類視圖上,盡情去看類的基類,以及基類的實現代碼了。不僅如此,最好的是那附帶的MSDN上所有的VC基礎文章都是中文,翻譯的比希望出版社的好得沒的說。什麼文檔啊,框加窗口啊,多視圖啊,應有盡有。所以建議大家都來用.net的吧,注意是要那七張盤的,三張的是beta版,VC功能不全的。
這部分該說一說MFC的具體程序了。因為我用的是.net,所以代碼可能會與6.0的略有不同,但也無關緊要,不會妨礙整體結構。我也會小心代碼兼容性的。
好了,拿起手邊的VC吧。跟我一塊來看一個基於對話框程序的所有代碼吧。
如果是6.0的朋友則首先在菜單上選擇新建,在工程(Project)選項卡中選中MFC AppWizard,將工程名(Project name)中起名為Dialog,按確定(OK)。在向導第一步中選擇基於對話框(Dialog based),直接按完成(Finish)就可以了。
如果是.net的朋友則在菜單上選擇新建->項目,在項目類型中選擇Visual C++項目,在模板中選擇MFC應用程序,在名稱中輸入Dialog,按確定。在應用程序類型中選擇基於對話框,後按完成。
於是一個基於對話框程序就做好了。第一次使用MFC的朋友,一定會為之喳舌。自己從零開始編程許久了,也許還不習慣別人為咱們生成代碼吧。“第一映象就是亂”,這就是我的同學給我的回答。沒關系,我們可以一點一點來看和理解VC給我們生成的代碼。畢竟,它為我們節省了很多時間來打WindowSDK框架代碼。
請打開類視圖(ClassView),如果無誤的話,我們可以看到三個類。分別是CAboutDlg, CDialogApp, CDialogDlg這三個類。 其中,CDialogApp是最重要的一個類。雙擊CDialogApp,打開其定義體。我們會看到它是這麼定義的:
class CDialogApp : public CWinApp
我們可以看到這個類是派生於CWinApp的。在MFC編程中,這種情況很多見,繼承類庫類來添加自己需要的功能,然後再去使用。在MFC應用程序中,CWinApp就是這樣使用的。查一查類庫關於CWinApp的描述,是這樣的:
MFC中的主應用程序類封裝用於 Windows 操作系統的應用程序的初始化、運行和終止。基於框架生成的應用程序必須有且僅有一個從 CWinApp 派生的類的對象。在創建窗口之前先構造該對象。
CWinApp 是從 CWinThread 派生的,後者表示可能具有一個或多個線程的應用程序的主執行線程。在最新版本的 MFC 中,InitInstance、Run、ExitInstance 和 OnIdle 成員函數實際位於 CWinThread 類中。此處將這些函數作為 CWinApp 成員來探討,因為探討所關心的是對象作為應用程序對象而不是主線程的角色。
與用於 Windows 操作系統的任何程序一樣,框架應用程序也具有 WinMain 函數。但在框架應用程序中不必編寫 WinMain。它由類庫提供,並在應用程序啟動時調用。WinMain 執行注冊窗口類等標准服務。然後它調用應用程序對象的成員函數來初始化和運行應用程序。(可通過重寫由 WinMain 調用的 CWinApp 成員函數來自定義 WinMain。)
為初始化應用程序,WinMain 調用應用程序對象的 InitApplication 和 InitInstance 成員函數。為運行應用程序的消息循環,WinMain 調用 Run 成員函數。在終止時,WinMain 調用應用程序對象的 ExitInstance 成員函數。
上面這段裡指的框架應用程序,包括了我們這種對話框應用程序。如MSDN所說,MFC類庫已經為我們提供了WinMain函數,而不必我們添加。這就是為什麼在MFC程序看不見主函數的原故。請看這句話“基於框架生成的應用程序必須有且僅有一個從 CWinApp 派生的類的對象。在創建窗口之前先構造該對象。” 打開類視圖的全局(Glotbals),會發現有一個theApp全局變量(或對象,我總覺得變量與對象可以歸為一類,應該有一個統一的名稱來講)。雙擊它,就可以看到CDialogApp theApp這樣的定義。因為全局變量和對象在程序中是最先被創建的,於是保證了在創建窗口之前構造一個CWinApp對象(因為CDialogApp派生於CWinApp,所以theApp也是一個CWinApp對象)。這個全局對象是非常有用,因為CWinApp本身集成了所有的程序資源WinAPI,我們可以使用它來取得程序的資源(如圖標,圖像,預定義字符串等等)。一般要取得此全局對象,不直接使用theApp,而是調用::AfxGetApp()來取得這個全局對象的指針。
MFC默認的主函數,會先調用theApp對象的InitApplication和InitInstance成員函數,來進行程序的初始化,在程序中一般只重寫InitInstance函數。然後,建立一個消息循環,不同的是在循環不停地調用theApp的Run成員函數。當收到WM_QUIT後,退出while循環。最後,執行theApp的ExitInstance成員函數,從而結束整個應用程序。
讓我們在類視圖(Class View)中展開CDialogApp類(點擊那個+符號),我們可以看到CDialogApp重寫了InitInstance()函數。它用於對應用程序主線程進行初始化。雙擊視圖中的InitInstance()來查看此函數的定義。我這裡的函數定義如下:
000:BOOL CDialogApp::InitInstance()
001:{
002: // 如果一個運行在 Windows XP 上的應用程序代碼指定要
003: // 使用 ComCtl32.dll 版本 6 或更高版本來啟用可視化方式,
004: //則需要 InitCommonControls()。否則,將無法創建窗口。
005: InitCommonControls();
006:
007: CWinApp::InitInstance(); //調用父類的InitInstance來進行默認的初始化
008:
009: AfxEnableControlContainer();
010:
011:
012: CDialogDlg dlg; //建立一個對話框對象,CDialogDlg是我們自定義的對話框類
013: m_pMainWnd = &dlg; //將本線程(即程序主線程)的主窗口設置為這個對話框
014: INT_PTR nResponse = dlg.DoModal(); //有模式地顯示這個對話框,直到對話框關閉
015: if (nResponse == IDOK) //如果對話框是用確定來關閉的,則
016: {
017: // TODO:在此放置處理用“確定”來關閉
018: //對話框的代碼
019: }
020: else if (nResponse == IDCANCEL) //如果對話框是用取消來關閉的,則
021: {
022: // TODO:在此放置處理用“取消”來關閉
023: //對話框的代碼
024: }
025:
026: // 由於對話框已關閉,所以將返回 FALSE 以便退出應用程序,
027: // 而不是啟動應用程序的消息泵。
028: return FALSE;
029:}
因為InitInstance()函數的結束返回值是false,應用程序將會立即退出。也就是只顯示對話框,當對話框關閉後,程序就會結束了。這時候的InitInstance函數就有點主函數的味道了。
下面,我們再來看看CDialogDlg類的定義,它是派生於CDialog的。它重寫了以下函數
CDialogDlg(CWnd* pParent = NULL); 自定義的構造函數
virtual BOOL OnInitDialog(); 對話框初始化消息操作函數
afx_msg void OnSysCommand(UINT nID, LPARAM lParam); 系統菜單消息響應函數
afx_msg void OnPaint(); 對話框重繪響應函數
afx_msg HCURSOR OnQueryDragIcon(); 最小化圖標詢問響應函數
另外,要注意的是在CDialogDlg類的定義體中有這麼一個枚舉的定義:
enum { IDD = IDD_DIALOG_DIALOG };
它表明這個CDialogDlg類使用的對話框模板是IDD_DIALOG_DIALOG。
CDialogDlg派生層次如下
CDialogDlg=>CDialog=>CWnd=>CCmdTarget=>CObject
先來看看構造函數:
CDialogDlg::CDialogDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDialogDlg::IDD/*這個IDD就是那個枚舉的值*/, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
在這個函數中首先,調用父類CDialog的構造函數來完成默認構造操作。其次,它使用AfxGetApp函數取得全局CWinApp對象theApp的指針,並使用它的LoadIcon函數來取得程序中IDR_MAINFRAME圖標資源,並賦給成員變量m_hIcon。這個圖標可以在資源視圖的ICON中可以的查到和設定。
在CDialogDlg的實現文件CDialogDlg.cpp中,可以找到如下一段語句
BEGIN_MESSAGE_MAP(CDialogDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
這是一段消息映射宏定義段。表示這個對話框類可以響應WM_SYSCOMMAND ,WM_PAINT,WM_QUERYDRAGICON消息。它們的響應函數,系統默認分別為OnSysCommand,OnPaint,OnQueryDragIcon。這段的意思是說,如果CDialogDlg類的對話框接收到WM_SYSCOMMAND消息,就會調用OnSysCommand。其它消息以此為例。不過,這些響應段一般是用不著我們自己手動添寫的,是由系統來管理的。你如果要分析一個MFC程序代碼,這一塊是一個很好的切入點,可以清楚的看到這個程序到底都可以響應什麼消息,都有些什麼功能。以上這些宏都可以在MSDN中查到。
下面,我們來一個對於對話框非常重要的函數OnInitDialog(),顧名思義這是一個對話框的初始化函數。在對話框創建之後,第一次顯示之前調用。
BOOL CDialogDlg::OnInitDialog()
{
CDialog::OnInitDialog(); //執行父類默認的初始化對話框操作
// IDM_ABOUTBOX 必須在系統命令范圍內。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
// 將\“關於...\”菜單項添加到系統菜單中。
CMenu* pSysMenu = GetSystemMenu(FALSE); //取得此對話框系統菜單的CMenu對象指針,並賦給pSysMenu;
if (pSysMenu != NULL) //如果不為空,則
{
CString strAboutMenu; //聲明一個字符串對象
strAboutMenu.LoadString(IDS_ABOUTBOX); //取得資源IDS_ABOUTBOX預定義字符串,可以
//在資源視圖中的String Table查到和設定這個預定義字符串
if (!strAboutMenu.IsEmpty()) //如果不為空,則
{
pSysMenu->AppendMenu(MF_SEPARATOR); //向菜單添加一個分隔符
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
//向菜單添加這個字符串,並將消息ID設為IDM_ABOUTBOX
}
}
// 設置此對話框的圖標。當應用程序主窗口不是對話框時,框架將自動
// 執行此操作
SetIcon(m_hIcon, TRUE); // 設置大圖標
SetIcon(m_hIcon, FALSE); // 設置小圖標
// TODO:在此添加額外的初始化代碼
return TRUE; // 除非設置了控件的焦點,否則應該返回 TRUE
}
以上,就是這個基於對話框的MFC應用程序的基礎代碼。現在可以直接編譯運行,來查看效果。 下面,我將在這些代碼的基礎上來添加功能,來實現一個復制文件的程序。 首先,我要在資源視圖的Dialog中,修改IDD_DIALOG_DIALOG模板: 我首先將對話框模板上面的所有按鈕和靜態文本全部刪掉,添加兩個文本框和四個按鈕。如果要修改控件的ID值,則要右擊控件,點選屬性,在ID框中輸入任意的ID字符串即可。基本布局如下:
注:粗體字代表該控件的ID值。
如果想向CDialogDlg類添加按鈕事件, 有兩種簡單的方法。第一種在模板設計中,雙擊按鈕,按確定後,即添加該按鈕單擊事件。另一種方法是使用向導(在.NET中是在CDialog類的屬性對話框中的事件欄中添加),首先在視圖(View)菜單中選擇類向導(ClassWizard),彈出類向導對話框,在類名(ClassName)下拉框中選擇我們的要添加事件的類CDialogDlg。對象ID(Object ID)列表框中選擇控件的ID,在消息(Messages)列表框中選擇要添加的事件,按添加函數鈕(Add Function)即可。
將四個按鈕分別添加單擊事件,系統會為我們自動命名成員函數。如果無誤的話,分別是OnBnClickedCancel();OnBnClickedCopy();OnBnClickedSrbrowse();OnBnClickedTrbrowse();因為我用的是.NET, 可能會與6.0生成的函數名略有不同。在添加完事件後,你最好去看看上面所提到過的消息映射宏有什麼變化,是否能夠讀懂它們。
首先在OnBnClickedCancel()函數中添加這麼一行語句:
this->EndDialog(IDCANCEL);
這行語句的作用是關閉當前的對話框,並以IDCANCEL返回,表明用戶是用取消來關閉對話框的。這是CDialog類的一個方法。我們期望如果點擊了取消按鈕,則關閉當前的對話框。
我們再來處理一下浏覽按鈕的功能。我期望可以彈出一個選擇文件的對話框,來選擇源文件和目標文件,並把文件名顯示在文本框裡。這個文件對話框剛好在MFC類庫有所定義,我們可以直接拿來使用。首先,我們必須在CDialogDlg類的實現文件CDialogDlg.cpp的頭幾行添加一個含包頭文件
#include <afxdlgs.h>
然後,在源文件浏覽按鈕(ID_SRBROWSE)的響應函數OnBnClickedSrbrowse裡添加如下語句:
CFileDialog Open(true/*如果為真則對話框為打開對話框,為否則為保存對話框*/,
"" /*默認後綴名*/,
"" /*默認文件名*/,
0 /*對話框風格*/,
"All File|*.*|",
this /*父窗口指針*/);
CString strFilePath;
if (Open.DoModal() == IDOK) //有模式地顯示對話框,如果返回確定則代表有文件選擇,則
{
strFilePath = Open.GetPathName(); //取得文件路徑字符串
SetDlgItemText (IDC_SOURCE, strFilePath); //將ID為IDC_SOURCE的控件的文本設為該字符串
}
要說明的,CString是MFC的字符串類,在形式上可以當成字符數組。而且還可以像VB的字符串一樣使用,直接進行字符串賦值。
還有就是SetDlgItemText,這是CWnd類的一個方法,功能是將改變當前窗口的某控件的文本。這個控件可以是按鈕、文本框、靜態文本、下拉列表框等等。其第一個參數是該控件的ID,第二個參數是以0結尾的字符串。
以這個函數類推,可以將目標浏覽按鈕的功能代碼寫成如下:
CFileDialog Save(false /**/,
"" /*默認後綴名*/,
"" /*默認文件名*/,
0 /*對話框風格*/,
"All File|*.*|",
this /*父窗口指針*/);
CString strFilePath;
if (Save.DoModal() == IDOK)
{
strFilePath = Save.GetPathName();
SetDlgItemText (IDC_TARGET, strFilePath);
}
最後,我們再來完成復制按鈕的功能。在單擊事件響應函數OnBnClickedTrbrowse中添加如下代碼:
CString strSource,strTarget;
GetDlgItemText (IDC_SOURCE, strSource); //取得ID名為IDC_SOURCE控件的文本
GetDlgItemText (IDC_TARGET, strTarget); //取得ID名為IDC_TARGET控件的文本
if (CopyFile (strSource, strTarget, false)) //復制文件,如果返回為真表示成功,則
{
MessageBox ("復制成功!", "報告", MB_OK); //彈出一個確定框
}
這裡要解釋的是GetDlgItemText,它也是CWnd的一個方法,是SetDlgItemText的反過程,用於取得窗口上某個控件的文本。CopyFile是WinAPI,它用於進行文件的復制,第一個參數是表示源文件名的字符串,第二個參數是表示目標文件名的字符串。如果成功的話則返回真。CWnd::MessageBox函數用於顯示一個消息框,第一個參數是消息文本,第二個參數是標題文本,第三個參數是消息框種類,這裡是MB_OK確定框,還可以是MB_YESNO是否框等等,以上這些可以在MSDN中查到。
這樣,一個簡單的基於對話框MFC小程序就做好了。不難吧?也相信諸位看官,已經對MFC的編程方法有一些了解了吧。
如果你想MFC編程變得更得心應手,非常非常建議你經常性的去查閱Visual Studio附帶的MSDN,並且能夠掌握查找MSDN的技巧,那樣會使你的工作變得事半功倍。
下一個部分嘛,我想講一講動態鏈接庫的編寫,希望能夠喜歡。
那麼祝大家愉快吧!
本文配套源碼