程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 接觸VC之三:MFC基於對話框程序

接觸VC之三:MFC基於對話框程序

編輯:vc教程

  最近,本人趕時髦,裝上了一套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_MAPEND_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的技巧,那樣會使你的工作變得事半功倍。

  下一個部分嘛,我想講一講動態鏈接庫的編寫,希望能夠喜歡。

  那麼祝大家愉快吧!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved