一、MFC程式設計入門教程之目錄
第1部分:MFC程式設計入門教程之目錄
1.MFC程式設計入門之前言
雞啄米的C++程式設計入門系列給大家講了C++的程式設計入門知識,大家對C++語言在文法和設計思想上應該有了一定的了解了。但是教程中講的例子隻是一個個簡單的例程,并沒有可視化視窗。雞啄米在這套VS2010/MFC程式設計入門教程中将會給大家講解怎樣使用VS2010進行可視化程式設計,也就是基于視窗的程式。
C++程式設計入門系列主要偏重于理論方面的知識,目的是讓大家打好底子,練好内功,在使用VC++程式設計時不至于丈二和尚摸不着頭腦。本套教程也會涉及到VC++的原理性的東西,同樣更重視實用性,讓大家學完本套教程以後,基本的界面程式都能很容易編寫出來。
VC++簡介
VC++全稱是Visual C++,是由微軟提供的C++開發工具,它與C++的根本差別就在于,C++是語言,而VC++是用C++語言編寫程式的工具平台。VC++不僅是一個編譯器更是一個內建開發環境,包括編輯器、調試器和編譯器等,一般它包含在Visual Studio中。Visual Studio包含了VB、VC++、C#等編譯環境。當然我們在使用VC++ 6.0的時候為了輕便,總是隻單獨安裝VC++ 6.0。但自微軟2002年釋出Visual Studio.NET以來,微軟建立了在.NET架構上的代碼托管機制,一個項目可以支援多種語言開發的元件,VC++同樣被擴充為支援代碼托管機制的開發環境,是以.NET Framework是必須的,也就不再有VC++的獨立安裝程式,不過可以在安裝Visual Studio時隻選擇VC++進行安裝。
VC++版本的選擇:VS2010
因為VC++ 6.0以後的版本不再有獨立的安裝程式,是以雞啄米在教程中将不會稱VC++ 6.0以後的版本為VC++ 7.0等等,而是用VC++所屬的Visual Studio的版本名稱代替,比如VS2003。
近些年VC++主要的版本包括:VC++ 6.0、VS2003、VS2005、VS2008和VS2010。
VC++ 6.0占用的系統資源比較少,打開工程、編譯運作都比較快,是以赢得很多軟體開發者的青睐。但因為它先于C++标準推出,是以對C++标準的支援不太好。舉個例子:
for(int i=0; i<5; i++)
{
a[i] = i;
}
for語句中聲明的變量i,對于VC++ 6.0來說,出了for循環仍能使用。但很顯然這與C++标準對于變量生存期的規定不符合。
随着VC++版本的更新,對C++标準的支援越來越好,對各種技術的支援也越來越完善。但同時新版本所需的資源也越來越多,對處理器和記憶體的要求越來越高。到VS2010,光安裝檔案就2G多,安裝後的檔案占3G多空間,其運作也經常受處理器和記憶體等性能的限制。但雞啄米還是推薦大家使用VS2010,畢竟它是最新版本,類庫和開發技術都是最完善的,本教程也将使用VS2010為大家做例程的示範。當然如果系統配置确實比較低,可以選擇VS2005,VS2005和VS2010相比還是要輕量級一些的。VC++ 6.0已經過時,奉勸大家盡量别用了。
VC++與MFC
講VC++免不了要提MFC,MFC全稱Microsoft Foundation Classes,也就是微軟基礎類庫。它是VC++的核心,是C++與Windows API的結合,很徹底的用C++封裝了Windows SDK(Software Development Kit,軟體開發工具包)中的結構和功能,還提供了一個應用程式架構,此應用程式架構為軟體開發者完成了一些例行化的工作,比如各種視窗、工具欄、菜單的生成和管理等,不需要開發者再去解決那些很複雜很乏味的難題,比如每個視窗都要使用Windows API注冊、生成與管理。這樣就大大減少了軟體開發者的工作量,提高了開發效率。
當然VC++不是隻能夠建立MFC應用程式,同樣也能夠進行Windows SDK程式設計,但是那樣的話就舍棄了VC++的核心,放棄了VC++最強大的部分。MFC也不是隻能用于VC++中,它同樣也可以用在Borland C++等編譯器中,當然沒有幾個人這樣做。
本節旨在讓大家對VC++、VS2010和MFC有基本的概念上的認識,後面雞啄米會帶大家進入VS2010/MFC的世界,讓大家輕松的開發各種包含視窗、圖形等的可視化程式。
View Code
2.VS2010與MSDN安裝過程圖解
上一講中雞啄米對VC++和MFC做了一些簡單介紹。在本套教程中雞啄米将使用VS2010為大家講解如何使用VC++和MFC進行程式設計,是以首先要安裝VS2010。
一.下載下傳VS2010
首先我們需要下載下傳VS2010,大家可以在網上下載下傳VS2010破解正式版,建議選擇英文版,養成使用英文工具的習慣。雞啄米使用VS2010旗艦試用版VS2010UltimTrial.iso為例介紹安裝過程,旗艦試用版官方下載下傳位址為:http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=12187。正式版的安裝過程與試用版類似。
二.安裝VS2010
下載下傳後進行安裝。安裝方法與一般的iso檔案一樣,可以使用虛拟光驅軟體Daemon Tools安裝,也可以将其解壓後點選setup.exe進行安裝。
雞啄米為了讓大家更直覺的看到安裝過程,我将在自己機子上再重新安裝一次,并截圖為大家講解。
這裡使用Daemon Tools安裝VS2010。首先打開Daemon Tools,螢幕右下角會出現托盤圖示,在圖示上點右鍵,會彈出菜單,再把滑鼠移到菜單項“虛拟裝置”上,然後再移到子菜單項“裝置 0:[L:] 無媒體”上,最後點選下一級子菜單項“裝載映像”,彈出對話框選擇VS2010UltimTrial.iso檔案。
這樣虛拟光驅就會打開此iso檔案,彈出自動安裝的提示,選擇“運作autorun.exe”就可以了,如果沒有彈出提示就通過資料總管進入虛拟光驅,用setup.exe安裝。接着會彈出下面的對話框:
當然選擇“Install Microsoft Visual Studio 2010”進入下一步,加載安裝元件後如下顯示:
點“Next”後:
選擇“I have read and accept the license terms”後點“Next”彈出對話框:
此處是讓我們選擇要安裝的功能,有兩種:Full(完全)和Custom(自定義)。Full選項表示安裝所有程式設計語言和工具,Custom選擇表示可以自定義要安裝的程式設計語言和工具。右側可以更改安裝路徑,雞啄米建議不要安裝到C槽,因為它占用的空間比較大。雞啄米安裝到了D盤,使用Full完全安裝。如果選擇Custom安裝,點“Next”則出現如下畫面:
大家可以根據自己的需要取消某些語言或工具的安裝,比如不想安裝Visual C#,取消選擇它就可以了。如果覺得以後都有可能會用到,那就像雞啄米一樣選擇完全安裝吧。
Full或Custom方式和安裝路徑設定好後,點“Install”進行安裝:
可能正式版的安裝檔案在安裝過程中會有重新開機過程。雞啄米使用的試用版中間并沒有重新開機。安裝完成:
如果要繼續安裝MSDN,先不要解除安裝虛拟光驅映像。
三.安裝MSDN
我們使用VS2010進行軟體開發同樣離不開幫助文檔,即MSDN。在本地安裝MSDN的方法如下:
在開始菜單的“所有程式”->“Microsoft Visual Studio 2010”->“Visual Studio Tools”下選擇“Manage Help Settings - ENU”:
彈出對話框:
可以将幫助庫存在預設路徑,也可以修改存放路徑。雞啄米使用預設路徑,點“OK”出現:
選擇“Install Content From Disk”後彈出對話框選擇幫助所在檔案,這時需要在加載了VS2010的虛拟光驅中找,選擇圖中所示路徑:
點OK後出現如下對話框,可以點“Add”選擇要添加的幫助庫,雞啄米全部添加了。
點“Update”進行安裝,等待其完成就可以了。
使用MSDN時點選開始菜單的“所有程式”->“Microsoft Visual Studio 2010”->“Microsoft Visual Studio 2010 Documentation”即可。
到此VS2010和MSDN的安裝過程就結束了。以後就可以正式使用VS2010進行軟體開發了。至于VS2010的使用方法在雞啄米的C++程式設計入門系列中已經介紹過,大家可以看看。
View Code
第2部分:MFC應用程式架構
1.利用MFC向導生成單文檔應用程式架構
上一講中講了VS2010和MSDN如何安裝,相信大家都已經安裝好了。這一講給大家一個簡單的例子,示範如何生成單文檔應用程式架構。
解決方案與工程
雞啄米在VS2010的使用介紹中已經講了解決方案與工程的概念,這裡再重提一下。每個應用程式都作為一個工程來處理,它包含了頭檔案、源檔案和資源檔案等,這些檔案通過工程集中管理。在VS2010中,工程都是在解決方案管理之下的。一個解決方案可以管理多個工程,可以把解決方案了解為多個有關系或者沒有關系的工程的集合。VS2010提供了一個Solution Explorer解決方案浏覽器視圖,可以顯示目前解決方案的内容,當建立一個工程時可以選擇建立一個解決方案還是加入目前解決方案。
下圖左側面闆中正在顯示的視圖就是Solution Explorer,視圖中有一個解決方案-HelloWorld,此解決方案下有一個同名的工程-HelloWorld。
在應用程式向導生成應用程式後,VS2010會在使用者設定的路徑下,以解決方案名為名稱建立一個目錄,裡面存放自動生成的檔案。
使用VS2010應用程式向導生成單文檔應用程式架構
雞啄米這裡簡略示範下怎樣生成單文檔應用程式架構,讓大家先有個直覺的了解,有不了解的地方可以留着以後回來再看。下面按照操作步驟一步步講解:
1.點菜單欄File->New->Project,彈出New Project對話框,我們可以選擇工程類型。
如果安裝完VS2010以後第一啟動時已經設定為VC++,則Installed Templates->Visual C++項會預設展開,而如果沒有設定VC++,則可以展開到Installed Templates->Other Languages->Visual C++項。因為我們要生成的是MFC程式,是以在“Visual C++”下選擇“MFC”,對話框中間區域會出現三個選項:MFC ActiveX Control、MFC Application和MFC DLL。MFC ActiveX Control用來生成MFC ActiveX控件程式。MFC Application用來生成MFC應用程式。MFC DLL用來生成MFC動态連結庫程式。當然我們要選擇MFC Application。
在對話框下部有Name、Location和Solution name三個設定項。意義如下:Name--工程名,Location--解決方案路徑,Solution name--解決方案名稱。這裡Name我們設為“HelloWorld”,Location設定為“桌面”的路徑,Solution name預設和Name一樣,當然可以修改為其他名字,這裡我們不作修改,也使用“HelloWorld”。點“OK”按鈕。
2.這時會彈出“MFC Application Wizard”對話框,上部寫有“Welcome to the MFC Application Wizard”,下面顯示了目前工程的預設設定。第一條“Tabbed multiple document interface (MDI)”是說此工程是多文檔應用程式。如果這時直接點下面的“Finish”按鈕,可生成具有上面列出設定的多文檔程式。但我們此例是要建立單文檔應用程式,是以點“Next”按鈕再繼續設定吧。
3.接下來彈出的對話框上部寫有“Application Type”,當然是讓選擇應用程式類型,我們看到有四種類型:Single document(單文檔)、Multiple documents(多文檔)、Dialog based(基于對話框)和Multiple top-level documents。我們選擇Single document類型,以生成一個單文檔應用程式架構。單文檔應用程式運作時是一個單視窗界面。
此對話框的“Resource language”還提供語言的選擇,這裡預設選擇英語。“Project style”可選擇工程風格,我們選擇預設的“Visual Studio”風格。“Use of MFC”有兩個選項:Use MFC in a shared DLL(動态連結庫方式使用MFC)和Use MFC in a static library(靜态庫方式使用MFC)。選擇Use MFC in a shared DLL時MFC的類會以動态連結庫的方式通路,是以我們的應用程式本身就會小些,但是釋出應用程式時必須同時添加必要的動态連結庫,以便在沒有安裝VS2010的機子上能夠正常運作程式。選擇Use MFC in a static library時MFC的類會編譯到可執行檔案中,是以應用程式的可執行檔案要比上種方式大,但可以單獨釋出,不需另加包含MFC類的庫。這裡我們使用預設的Use MFC in a shared DLL。點“Next”按鈕。
4.此時彈出上部寫有“Compound Document Support”的對話框,可以通過它向應用程式加入OLE支援,指定OLE選項的複合文檔類型。本例不需要OLE特性,使用預設值“None”。點“Next”按鈕。
5.彈出的新對話框上部寫有“Document Template Properties”。“File extension”可以設定程式能處理的檔案的擴充名。對話框其他選項還可以更改程式視窗的标題。我們都使用預設設定,點“Next”按鈕。
6.此時彈出的對話框主題是“Database Support”。用于設定資料庫選項。此向導可以生成資料庫應用程式需要的代碼。它有四個選項:
None:忽略所有的資料庫支援;
Header files only:隻包含定義了資料庫類的頭檔案,但不生成對應特定表的資料庫類或視圖類;
Database view without file support:建立對應指定表的一個資料庫類和一個視圖類,不附加标準檔案支援;
Database view with file support:建立對應指定表的一個資料庫類和一個視圖類,并附加标準檔案支援。
本例選擇預設值“None”,不使用資料庫特性。點“Next”按鈕。
7.這時彈出的對話框是關于“User Interface Features”,即使用者界面特性。我們可以設定有無最大化按鈕、最小化按鈕、系統菜單和初始狀态欄等。還可以選擇使用菜單欄和工具欄生成簡單的應用程式還是使用ribbon。這裡我們都選擇預設設定。點“Next”進入下一步。
8.此時彈出“進階特性”對話框。可以設定的進階特性包括有無列印和列印預覽等。在“Number of files on recent file list”項可以設定在程式界面的檔案菜單下面最近打開檔案的個數。我們仍使用預設值。點“Next”按鈕。
9.彈出“生成類”對話框。在對話框上部的“生成類”清單框内,列出了将要生成的4 個類:一個視圖類(CHelloWorldView)、一個應用類(CHelloWorldApp)、一個文檔類(CHelloWorldDoc)和一個主架構視窗類(CMainFrame)。在對話框下面的幾個編輯框中,可以修改預設的類名、類的頭檔案名和源檔案名。對于視圖類,還可以修改其基類名稱,預設的基類是CView,還有其他幾個基類可以選擇。這裡我們還是使用預設設定。點“Finish”按鈕。
應用程式向導最後為我們生成了應用程式架構,并在Solution Explorer中自動打開了解決方案(見上面第一張圖)。
編譯運作生成的程式
點菜單中的Build->Build HelloWorld編譯程式,然後點Debug->Start Without Debugging(快捷鍵Ctrl+F5)運作程式,也可以直接點Debug->Start Without Debugging,這時會彈出對話框提示是否編譯,選擇“Yes”,VS2010将自動編譯連結運作HelloWorld程式。結果頁面如下所示:
終于看見界面了。雞啄米在以後的教程中會繼續講解各種界面和控件的使用方法。歡迎到雞啄米部落格交流,您的關注是我前進的動力。
View Code
2.VS2010應用程式工程中檔案的組成結構
雞啄米在上一講中為大家示範了如何利用應用程式向導建立單文檔應用程式架構。這一節将以上一講中生成應用程式HelloWorld的檔案結構為例,講解VS2010應用程式工程中檔案的組成結構。
用應用程式向導生成架構程式後,我們可以在之前設定的Location下看到以解決方案名命名的檔案夾,此檔案夾中包含了幾個檔案和一個以工程名命名的子檔案夾,這個子檔案夾中又包含了若幹個檔案和一個res檔案夾,建立工程時的選項不同,工程檔案夾下的檔案可能也會有所不同。
如果已經以Debug方式編譯連結過程式,則會在解決方案檔案夾下和工程子檔案夾下各有一個名為“Debug”的檔案夾,而如果是Release方式編譯則會有名為“Release”的檔案夾。這兩種編譯方式将産生兩種不同版本的可執行程式:Debug版本和Release版本。Debug版本的可執行檔案中包含了用于調試的資訊和代碼,而Release版本則沒有調試資訊,不能進行調試,但可執行檔案比較小。
雞啄米将所有檔案分為6個部分:解決方案相關檔案、工程相關檔案、應用程式頭檔案和源檔案、資源檔案、預編譯頭檔案和編譯連結生成檔案。
1.解決方案相關檔案
解決方案相關檔案包括解決方案檔案夾下的.sdf檔案、.sln檔案、.suo檔案和ipch檔案夾。
.sdf檔案和ipch目錄一般占用空間比較大,幾十兆甚至上百兆,與智能提示、錯誤提示、代碼恢複和團隊本地倉庫等相關。如果你覺得不需要則可以設定不生成它們,方法是點選菜單欄Tools->Options,彈出Options對話框,選擇左側面闆中Text Editor->C/C++->Advanced,右側清單中第一項Disable Database由False改為True就可以了,最後關閉VS2010再删除.sdf檔案和ipch目錄以後就不會再産生了。但關閉此選項以後也會有很多不便,例如寫程式時的智能提示沒有了。
.sln檔案和.suo檔案為MFC自動生成的解決方案檔案,它包含目前解決方案中的工程資訊,存儲解決方案的設定。
2.工程相關檔案
工程相關檔案包括工程檔案夾下的.vcxproj檔案和.vcxproj.filters檔案。
.vcxproj檔案是MFC生成的工程檔案,它包含目前工程的設定和工程所包含的檔案等資訊。.vcxproj.filters檔案存放工程的虛拟目錄資訊,也就是在解決方案浏覽器中的目錄結構資訊。
3.應用程式頭檔案和源檔案
應用程式向導會根據應用程式的類型(單文檔、多文檔或基于對話框的程式)自動生成一些頭檔案和源檔案,這些檔案是工程的主體部分,用于實作主架構、文檔、視圖等。雞啄米下面分别簡單介紹下各個檔案:
HelloWorld.h:應用程式的主頭檔案。主要包含由CWinAppEx類派生的CHelloWorldApp類的聲明,以及CHelloWorldApp類的全局對象theApp的聲明。
HelloWorld.cpp:應用程式的主源檔案。主要包含CHelloWorldApp類的實作,CHelloWorldApp類的全局對象theApp的定義等。
MainFrm.h和MainFrm.cpp:通過這兩個檔案從CFrameWndEx類派生出CMainFrame類,用于建立主架構、菜單欄、工具欄和狀态欄等。
HelloWorldDoc.h和HelloWorldDoc.cpp:這兩個檔案從CDocument類派生出文檔類CHelloWorldDoc,包含一些用來初始化文檔、串行化(儲存和裝入)文檔和調試的成員函數。
HelloWorldView.h和HelloWorldView.cpp:它們從CView類派生出名為CHelloWorldView的視圖類,用來顯示和列印文檔資料,包含了一些繪圖和用于調試的成員函數。
ClassView.h和ClassView.cpp:由CDockablePane類派生出CClassView類,用于實作應用程式界面左側面闆上的Class View。
FileView.h和FileView.cpp:由CDockablePane類派生出CFileView類,用于實作應用程式界面左側面闆上的File View。
OutputWnd.h和OutputWnd.cpp:由CDockablePane類派生出COutputWnd類,用于實作應用程式界面下側面闆Output。
PropertiesWnd.h和PropertiesWnd.cpp:由CDockablePane類派生出CPropertiesWnd類,用于實作應用程式界面右側面闆Properties。
ViewTree.h和ViewTree.cpp:由CTreeCtrl類派生出CViewTree類,用于實作出現在ClassView和FileView等中的樹視圖。
4.資源檔案
一般我們使用MFC生成視窗程式都會有對話框、圖示、菜單等資源,應用程式向導會生成資源相關檔案:res目錄、HelloWorld.rc檔案和Resource.h檔案。
res目錄:工程檔案夾下的res目錄中含有應用程式預設圖示、工具欄使用圖示等圖示檔案。
HelloWorld.rc:包含預設菜單定義、字元串表和加速鍵表,指定了預設的About對話框和應用程式預設圖示檔案等。
Resource.h:含有各種資源的ID定義。
5.預編譯頭檔案
幾乎所有的MFC程式的檔案都要包含afxwin.h等檔案,如果每次都編譯一次則會大大減慢編譯速度。是以把常用的MFC頭檔案都放到了stdafx.h檔案中,然後由stdafx.cpp包含stdafx.h檔案,編譯器對stdafx.cpp隻編譯一次,并生成編譯之後的預編譯頭HelloWorld.pch,大大提高了編譯效率。
6.編譯連結生成檔案
如果是Debug方式編譯,則會在解決方案檔案夾和工程檔案夾下都生成Debug子檔案夾,而如果是Release方式編譯則生成Release子檔案夾。
工程檔案夾下的Debug或Release子檔案夾中包含了編譯連結時産生的中間檔案,解決方案檔案夾下的Debug或Release子檔案夾中主要包含有應用程式的可執行檔案。
關于應用程式工程檔案的組成結構雞啄米就先講到這了。其中包含了很多專有名詞,以後大家會慢慢熟悉的。歡迎來雞啄米部落格交流。謝謝。
View Code
3.MFC應用程式架構分析
上一講雞啄米講的是VS2010應用程式工程中檔案的組成結構,可能大家對工程的運作原理還是很模糊,理不出頭緒,畢竟跟C++程式設計入門系列中的例程差别太大。這一節雞啄米就為大家分析下MFC應用程式架構的運作流程。
一.SDK應用程式與MFC應用程式運作過程的對比
程式運作都要有入口函數,在之前的C++教程中都是main函數,而Windows應用程式的入口函數是WinMain函數,MFC程式也是從WinMain函數開始的。下面雞啄米就給出用Windows SDK寫的“HelloWorld”程式,與應用程式架構進行對比,這樣能更好的了解架構是怎樣運作的。Windows SDK開發程式就是不使用MFC類庫,直接用Windows API函數進行軟體開發。雞啄米不是要講解SDK開發,隻是為了對比而簡單介紹,至于SDK開發可以在大家學完MFC以後選擇是否要研究,一般來說有簡單了解就可以了。
SDK應用程式
首先,給出Windows SDK應用程式“HelloWorld”的源碼:
C++代碼
#include <windows.h>
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
const static TCHAR appName[] = TEXT("Hello world");
WNDCLASSEX myWin;
myWin.cbSize = sizeof(myWin);
myWin.style = CS_HREDRAW | CS_VREDRAW;
myWin.lpfnWndProc = myWndProc;
myWin.cbClsExtra = 0;
myWin.cbWndExtra = 0;
myWin.hInstance = hInstance;
myWin.hIcon = 0;
myWin.hIconSm = 0;
myWin.hCursor = 0;
myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
myWin.lpszMenuName = 0;
myWin.lpszClassName = appName;
//Register
if (!RegisterClassEx(&myWin)) return 0;
const HWND hWindow = CreateWindow(
appName,
appName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
hInstance,
0);
ShowWindow(hWindow,iCmdShow);
UpdateWindow(hWindow);
{
MSG msg;
while(GetMessage(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
}
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg==WM_PAINT)
{
PAINTSTRUCT ps;
const HDC hDC = BeginPaint(hWindow,&ps);
RECT rect;
GetClientRect(hWindow,&rect);
DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWindow,&ps);
return 0;
}
else if (msg==WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWindow,msg,wParam,lParam);
}
上面的程式運作的流程是:進入WinMain函數->初始化WNDCLASSEX,調用RegisterClassEx函數注冊視窗類->調用ShowWindow和UpdateWindow函數顯示并更新視窗->進入消息循環。關于消息循環再簡單說下,Windows應用程式是消息驅動的,系統或使用者讓應用程式進行某項操作或完成某個任務時會發送消息,進入程式的消息隊列,然後消息循環會将消息隊列中的消息取出,交予相應的視窗過程處理,此程式的視窗過程函數就是myWndProc函數,視窗過程函數處理完消息就完成了某項操作或任務。本例是要顯示“HELLO WORLD”字元串,UpdateWindow函數會發送WM_PAINT消息,但是此消息不經過消息隊列而是直接送到視窗過程處理,在視窗過程函數中最終繪制了“HELLO WORLD”字元串。
MFC應用程式
下面是MFC應用程式的運作流程,通過MFC庫中代碼進行分析:
首先在HelloWorld.cpp中定義全局對象theApp:CHelloWorldApp theApp;。調用CWinApp和CHelloWorldApp的構造函數後,進入WinMain函數(位于appmodul.cpp中)。
C++代碼
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
在TCHAR.h中,有此定義:#define _tWinMain WinMain,是以這裡的_tWinMain就是WinMain函數。它調用了AfxWinMain函數(位于WinMain.cpp中)。
C++代碼
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)
{
.............略
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
if (!pThread->InitInstance())
{
.........略
}
// Run函數位于THRDCORE.cpp中,由此函數進入消息循環
nReturnCode = pThread->Run();
..............略
return nReturnCode;
}
上面InitInstance函數的代碼如下:
C++代碼
BOOL CTestApp::InitInstance()
{
.............略
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
//ProcessShellCommand位于AppUI2.cpp中,注冊并建立視窗
if (!ProcessShellCommand(cmdInfo))
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
InitInstance中的ProcessShellCommand函數又調用了CMainFrame的LoadFrame函數注冊并建立了視窗,執行完ProcessShellCommand函數以後,調用了m_pMainWnd的ShowWindow和UpdateWindow函數顯示并更新架構視窗。這些是不是與上面的SDK程式十分類似?
接下來該是消息循環了,上面的AfxWinMain函數中調用了pThread的Run函數(位于THRDCORE.cpp中),在Run中包含了消息循環。Run函數的代碼如下:
C++代碼
int CWinThread::Run()
{
.............略
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
..............略
}
BOOL CWinThread::PumpMessage()
{
return AfxInternalPumpMessage();
}
BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
.............略
}
...............略
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
return TRUE;
}
我們看到PumpMessage中通過調用GetMessage、TranslateMessage、DispatchMessage等建立了消息循環并投遞消息。
視窗過程函數AfxWinProc形式如下:
C++代碼
LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)
{
……
CWnd*pWnd=CWnd::FromHandlePermanent(hWnd);
ReturnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);
}
兩者運作過程對比
到此,通過對比可以發現,MFC應用程式的運作流程與SDK程式是類似的,都是先進行一些初始化過程,再注冊并建立視窗,然後顯示、更新視窗,最後進入消息循環,消息都由視窗過程函數處理。現在大家是不是覺得有些頭緒了?在運作流程上有基本的掌握即可。
二.MFC應用程式架構主要類之間的關系
在第二講中,給大家示範了如何利用應用程式向導生成單文檔應用程式架構,可以看到程式的基本架構和必要的代碼都自動生成了,上一講又講解了檔案組成結構,實際上在前面自動生成的架構中比較重要的類包括以下幾個:CHelloWorldApp、CMainFrame、CHelloWorldDoc和CHelloWorldView,至于其他的類比如CClassView、CFileView等都是在架構視窗(CMainFrame)上建立的面闆等,不是必要的。
雞啄米就四個主要類的關系簡單講下,CHelloWorldApp類處理消息,将收到的消息分發給相應的對象。CMainFrame是視圖CHelloWorldView的父視窗,視圖CHelloWorldView就顯示在CMainFrame的客戶區中。視圖類CHelloWorldView用來顯示文檔類CHelloWorldDoc中的資料,并根據對視圖類的操作修改文檔類的資料。一個視圖類隻能跟一個文檔類相聯系,而一個文檔類可以跟多個視圖類相聯系。關于視圖類和文檔類的關系後面會詳細講解。
本節VC++/MFC程式設計入門教程内容比較多,主要是讓大家對MFC應用程式的運作原理有大概的了解。對于以後的MFC開發有很多好處。如果有問題請在雞啄米部落格留言交流。謝謝。
View Code
4.MFC消息映射機制概述
上一講雞啄米為大家簡單分析了MFC應用程式架構,這一講是關于MFC消息映射機制的内容。
前面已經說過,Windows應用程式是消息驅動的。在MFC軟體開發中,界面操作或者線程之間通信都會經常用到消息,通過對消息的處理實作相應的操作。比較典型的過程是,使用者操作視窗,然後有消息産生,送給視窗的消息處理函數處理,對使用者的操作做出響應。
什麼是消息
視窗消息一般由三個部分組成:1.一個無符号整數,是消息值;(2)消息附帶的WPARAM類型的參數;(3)消息附帶的LPARAM類型的參數。其實我們一般所說的消息是狹義上的消息值,也就是一個無符号整數,經常被定義為宏。
什麼是消息映射機制
MFC使用一種消息映射機制來處理消息,在應用程式架構中的表現就是一個消息與消息處理函數一一對應的消息映射表,以及消息處理函數的聲明和實作等代碼。當視窗接收到消息時,會到消息映射表中查找該消息對應的消息處理函數,然後由消息處理函數進行相應的處理。SDK程式設計時需要在視窗過程中一一判斷消息值進行相應的處理,相比之下MFC的消息映射機制要友善好用的多。
Windows消息分類
先講下Windows消息的分類。Windows消息分為系統消息和使用者自定義消息。Windows系統消息有三種:
1.标準Windows消息。除WM_COMMAND外以WM_開頭的消息是标準消息。例如,WM_CREATE、WM_CLOSE。
2.指令消息。消息名為WM_COMMAND,消息中附帶了辨別符ID來區分是來自哪個菜單、工具欄按鈕或加速鍵的消息。
3.通知消息。通知消息一般由清單框等子視窗發送給父視窗,消息名也是WM_COMMAND,其中附帶了控件通知碼來區分控件。
CWnd的派生類都可以接收到标準Windows消息、通知消息和指令消息。指令消息還可以由文檔類等接收。
使用者自定義消息是實際上就是使用者定義一個宏作為消息,此宏的值應該大于等于WM_USER,然後此宏就可以跟系統消息一樣使用,視窗類中可以定義它的處理函數。
消息映射表
除了一些沒有基類的類或CObject的直接派生類外,其他的類都可以自動生成消息映射表。下面的講解都以前面例程HelloWorld的CMainFrame為例。消息映射表如下:
C++代碼
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
ON_WM_CREATE()
ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize)
ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew)
ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)
ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook)
ON_WM_SETTINGCHANGE()
END_MESSAGE_MAP()
在BEGIN_MESSAG_MAP和END_MESSAGE_MAP之間的内容成為消息映射入口項。消息映射除了在CMainFrame的實作檔案中添加消息映射表外,在類的定義檔案MainFrm.h中還會添加一個宏調用:
DECLARE_MESSAGE_MAP()
一般這個宏調用寫在類定義的結尾處。
添加消息處理函數
如何添加消息處理函數呢?不管是自動還是手動添加都有三個步驟:
1.在類定義中加入消息處理函數的函數聲明,注意要以afx_msg打頭。例如MainFrm.h中WM_CREATE的消息處理函數的函數聲明:afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);。
2.在類的消息映射表中添加該消息的消息映射入口項。例如WM_CREATE的消息映射入口項:ON_WM_CREATE()。
3.在類實作中添加消息處理函數的函數實作。例如,MainFrm.cpp中WM_CREATE的消息處理函數的實作:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......
}
通過以上三個步驟以後,WM_CREATE等消息就可以在視窗類中被消息處理函數處理了。
各種Windows消息的消息處理函數
标準Windows消息的消息處理函數都與WM_CREATE消息類似。
指令消息的消息映射入口項形式如:ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize),消息為ID_VIEW_CUSTOMIZE,消息處理函數為OnViewCustomize。
如果想要使用某個處理函數批量處理某些指令消息,則可以像CMainFrame消息映射表中的ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)一樣添加消息映射入口項,這樣值在ID_VIEW_APPLOOK_WIN_2000到ID_VIEW_APPLOOK_WINDOWS_7之間的菜單項等的指令消息都由CMainFrame的OnApplicationLook函數處理。函數原型為afx_msg void OnApplicationLook(UINT id);,參數id為使用者操作的菜單項等的ID。
在操作清單框等控件時往往會給父視窗發送WM_NOTIFY通知消息。WM_NOTIFY消息的wParam參數為發送通知消息的控件的ID,lParam參數指向一個結構體,可能是NMHDR結構體,也可能是第一個元素為NMHDR結構體變量的其他結構體。NMHDR結構體的定義如下(僅作了解):
Typedef sturct tagNMHDR{
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
hwndFrom為發送通知消息控件的句柄,idFrom為控件ID,code為要處理的通知消息的通知碼,例如NM_CLICK。
通知消息的消息映射入口項形式如:
ON_NOTIFY(wNotifyCode,id,memberFxn)
wNotifyCode為要處理的通知消息通知碼,例如:NM_CLICK。id為控件辨別ID。MemberFxn為此消息的處理函數。
通知消息的處理函數的原型為:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
如果需要使用使用者自定義消息,首先要定義消息宏,如:#define WM_UPDATE_WND (WM_USER+1),再到消息映射表中添加消息映射入口項:ON_MESSAGE(WM_UPDATE_WND, &CMainFrame::OnUpdateWnd),然後在MainFrm.h中添加消息處理函數的函數聲明:afx_msg LRESULT OnUpdateWnd(WPARAM wParam, LPARAM lParam);,最後在MainFrm.cpp中實作此函數。
雞啄米本節對MFC消息映射機制隻是做了比較簡單的講解,讓大家對它有一定的認識,程式設計入門者不必強求完全掌握。在以後的教程中會經常涉及到消息的使用,大家會逐漸熟悉MFC的消息映射機制。
View Code
第3部分:對話框
1.對話框:建立對話框模闆和修改對話框屬性
雞啄米在上一講中介紹了MFC的消息映射機制,屬于原理方面的知識。對于VC++程式設計入門學習者來說可能有些抽象,雞啄米會把消息映射的知識滲透到後面的教程中。本節開始為大家講解偏應用的知識-建立對話框。
對話框,大家應該很熟悉了,在我們常用的軟體中大多都有對話框界面,例如,360安全衛士的主界面其實就是個對話框,隻是它做了很多美工方面的工作,将其大大美化了。
建立對話框主要分兩大步,第一,建立對話框資源,主要包括建立新的對話框模闆、設定對話框屬性和為對話框添加各種控件;第二,生成對話框類,主要包括建立對話框類、添加控件變量和控件的消息處理函數等。雞啄米在本節中先講講怎樣建立對話框模闆和設定對話框屬性。
建立基于對話框的應用程式架構
之前雞啄米建立的HelloWorld程式是單文檔應用程式,生成了多種視窗,如果用它來将講建立對話框的話可能有些複雜,對大家單純了解對話框有點影響,是以這裡雞啄米就再建立一個基于對話框的應用程式,用來實作加法運算的功能。建立步驟同單文檔應用程式大同小異,簡單步驟如下:
1.選擇菜單項File->New->Project,彈出“New Project”對話框。
2.左側面闆中Installed Templated的Visual C++下選擇MFC,中間視窗中選擇MFC Application,然後在下面的Name編輯框中鍵入工程名稱,本例取名“Addition”,在Location編輯框中設定工程的儲存路徑。點“OK”。
3.點“Next”到“Application Type”對話框,在Application type下選擇Dialog based,其他使用預設設定,點“Finish”。
我們可以在Solution Explorer視圖中看到,此工程的檔案要比單文檔應用程式少的多,在Class View中主要有三個類:CAboutDlg、CAdditionApp和CAdditionDlg。CAboutDlg是應用程式的“關于”對話框類,CAdditionApp是由CWinApp派生的類,CAdditionDlg是主對話框類,主對話框也就是此應用程式運作後顯示的主要界面。
注:如果在VS2010中找不到Solution Explorer或Class View等視圖,可以在菜單項View下找到對應視圖選項選擇即可。在VS2010的使用介紹中已經有講解。
在Resource View視圖中可以看到工程Addition的資源樹,展開Addition.rc,下面有四個子項:Dialog(對話框)、Icon(圖示)、String Table(字元串表)和Version(版本)。然後展開Dialog項,下面有兩個對話框模闆,其ID分别為:IDD_ABOUTBOX和IDD_ADDITION_DIALOG,前者是“關于”對話框的模闆,後者是主對話框的模闆。ID是資源的唯一辨別,本質上是一個無符号整數,一般ID代表的整數值由系統定義,我們無需幹涉。
對話框模闆
可見對于主對話框來說,建立對話框第一步中的建立新的對話框模闆已經由系統自動完成了。而如果是再添加對話框需要建立新的對話框模闆時,需要在Resource View的“Dialog”節點上點右鍵,在右鍵菜單中選擇“Insert Dialog”,就會生成新的對話框模闆,并且會自動配置設定ID。
在Resource View的資源樹中輕按兩下某個ID,可在中間區域内顯示相應的資源界面。輕按兩下IDD_ADDITION_DIALOG時,中間區域就會顯示Addition對話框模闆。如下圖:
設定對話框屬性
在Addition對話框模闆上點右鍵,然後在右鍵菜單中選擇Properties,則在右側面闆中會顯示對話框的屬性清單。如下圖:
雞啄米在這裡對經常使用的幾個屬性作簡單說明,并對Addition對話框進行屬性設定說明。
1.ID:對話框ID,唯一辨別對話框資源,可以修改。此處為IDD_ADDITION_DIALOG,我們不修改它。
2.Caption:對話框标題。此處預設為Addition,我們将其修改為“加法電腦”。
3.Border:邊框類型。有四種類型:None、Thin、Resizing和Dialog Frame。我們使用預設的Dialog Frame。
4.Maximize:是否使用最大化按鈕。我們使用預設的False。
5.Minimize:是否使用最小化按鈕。同樣我們使用預設的False。
6.Style:對話框類型。有三種類型:Overlapped(重疊視窗)、Popup(彈出式視窗)和Child(子視窗)。彈出式視窗比較常見。我們使用預設的Popup類型。
7.System Menu:是否帶有标題欄左上角的系統菜單,包括移動、關閉等菜單項。我們使用預設的True。
8.Title Bar:是否帶有标題欄。我們使用預設的True。
9.Font(Size):字型類型和字型大小。如果将其修改為非系統字型,則Use System自動改為False。而如果Use System原來為False,将其修改為True,則Font(Size)自動設定為系統字型。這裡我們使用預設的系統字型。
根據以上說明,其實我們隻修改了标題屬性。這時我們運作此程式後的界面如下:
這一講就先講到這裡了,對于建立對話框第一步中的為對話框添加各種控件下一講為大家示範。歡迎來雞啄米部落格交流學習。
View Code
2.對話框:為對話框添加控件
建立對話框資源需要建立對話框模闆、修改對話框屬性、為對話框添加各種控件等步驟,前面一講中雞啄米已經講了建立對話框模闆和修改對話框屬性,本節繼續講如何為對話框添加控件。
上一講中雞啄米建立了一個名為“Addition”的工程,目的是生成一個實作加法運算的應用程式。實作加法計算有幾個必要的因素:被加數、加數、和。被加數和加數需要輸入,和需要輸出顯示。那麼這幾個因素都需要相應的控件來輸入或顯示,下面雞啄米就一步步講解如何添加這些控件。
1.為對話框添加一個靜态文本框(Static Text),用于顯示字元串--“被加數”。
上一講中生成的資源模闆中自動添加了一個标題為“TODO:Place dialog controls here.”的靜态文本框,我們可以修改它的标題繼續使用,也可以删掉它。這裡為了從頭講解靜态文本框的添加過程,将它删掉,繼續添加新的靜态文本框。
删除控件時,可以使用滑鼠左鍵點選選中它,選中後控件的周圍會出現虛線框,然後按Delete鍵就可以将其删除了。在“Addition”工程的Resource View中打開上一講中建立的對話框模闆IDD_ADDITION_DIALOG,自動添加的靜态文本框就可以使用這種方法删除。
在添加新的靜态文本框以前,先看看Toolbox視圖是否顯示了,如果沒有顯示,在菜單欄上點選View->Toolbox即可。Toolbox視圖如下圖:
Toolbox中列出了一些常用控件,其中有一個是Static Text,即是我們要添加的控件。在Toolbox中的Static Text上點下滑鼠左鍵不放開,并拖到IDD_ADDITION_DIALOG對話框模闆上,模闆上會出現一個虛線框,我們找到合适的位置松開滑鼠左鍵放下它。
用滑鼠左鍵選中控件後周圍出現虛線框,然後滑鼠移到虛線框上幾個黑點的位置會變成雙向箭頭的形狀,此時就可以按下滑鼠左鍵并拖動來改變控件大小了。我們可以這樣改變新添加的靜态文本框控件的大小,以更好的顯示标題。當然,整個對話框模闆也可以用這種方法改變大小。
接下來就該修改靜态文本框的文字了。滑鼠右鍵點選靜态文本框,在右鍵菜單中選擇“Properties”,Properties面闆就會顯示出來,在面闆上修改Caption屬性為“被加數”,ID修改為IDC_SUMMAND_STATIC。此時模闆如下圖:
2.為對話框添加一個編輯框(Edit Control),用來輸入被加數。
添加編輯框的過程與靜态文本框類似,在Toolbox中選中Edit Control控件拖到對話框模闆上,并使其與之前的靜态文本框水準對齊(為了美觀),然後調整其大小使之适合被加數的輸入。
在編輯框上點右鍵,仍然在右鍵菜單中選擇“Properties”顯示出屬性(Properties)面闆,修改其ID為IDC_SUMMAND_EDIT。此時模闆如下圖:
3.按照1的方法添加一個标題為“加數”的靜态文本框,用于顯示字元串--“加數”。并将其ID改為IDC_ADDEND_STATIC。
4.按照2的方法添加一個ID為IDC_ADDEND_EDIT的編輯框,用來輸入加數。
5.按照1的方法添加一個标題為“和”的靜态文本框,用于顯示文字--“和”。并修改其ID為IDC_SUM_STATIC。
6.按照2的方法添加一個ID為IDC_SUM_EDIT的編輯框,用來顯示最終的加和。
7.類似的添加按鈕(Button)控件到對話框模闆,用于在被點選後觸發加法計算。修改其标題為“計算”,ID為IDC_ADD_BUTTON。
到此,對話框模闆如圖:
8.删除OK按鈕。打開Cancel按鈕的屬性面闆,将标題改為“退出”,并使其與“計算”按鈕水準對齊。
9.根據控件的布局,适當調整整個對話框模闆的大小,使其相對控件布局來說大小合适,界面美觀。
這樣在對話框模闆中就把我們在本例中需要用到的控件就添加完了。最終效果如下:
至此,我們的對話框資源就基本建立完了。應用程式運作後的界面效果已經很清楚了。後面雞啄米會講如何在對話框類中實作加法計算功能,并能很好的和界面互動。歡迎繼續到雞啄米部落格交流。
View Code
3.對話框:建立對話框類和添加控件變量
前兩講中雞啄米為大家講解了如何建立對話框資源。建立好對話框資源後要做的就是生成對話框類了。雞啄米再聲明下,生成對話框類主要包括建立對話框類、添加控件變量和控件的消息處理函數等。
因為雞啄米給大家的例程Addition是基于對話框的程式,是以程式自動建立了對話框模闆IDD_ADDITION_DIALOG,并自動生成了對話框類CAdditionDlg,它是從CDialogEx類派生的。大家用過VC++ 6.0的可能記得,我們定義的對話框類都是從CDialog類派生的,但在VS2010中,一般對話框類都是繼承自CDialogEx類。
建立對話框類
如果是自己新添加的對話框模闆,怎樣為它建立對話框類呢?
1.首先雞啄米就按第六講:建立對話框模闆和修改對話框屬性中說的那樣,在Resource View的“Dialog”節點上右鍵,然後在右鍵菜單中選擇“Insert Dialog”建立一個新的對話框模闆,ID就使用預設的IDD_DIALOG1。
2.在中間區域會顯示建立的對話框模闆,然後選中此對話框模闆,點右鍵,在右鍵菜單中選擇Add Class。
3.選擇“Add Class”後會彈出一個對話框,在對話框中“Class name”下的編輯框中寫入自定義的類名就可以了,例如CMyDialog。
4.最後點“Finish”完成。
最終你就可以在Class View中看到新生成的對話框類CMyDialog了,并且在Solution Explorer中有相應的MyDialog.h頭檔案和MyDialog.cpp源檔案生成。CMyDialog類同樣派生于CDialogEx類。
注意,一般類名都以C打頭,又比如,CTestDlg。
為對話框中的控件添加變量
在上一講中為對話框添加了幾個控件,包括三個靜态文本框,三個編輯框,一個按鈕控件。程式自動生成的Cancel按鈕保留,作為退出按鈕,而OK按鈕删除掉了。
靜态文本框隻是為了說明後面緊跟的編輯框中資料的意義,是被加數、加數還是和,是以它們是不會變的,我們就不為它們添加變量了。按鈕控件是用來操作的,這裡也不為它們添加變量。編輯框中的資料可能會經常變化,有必要為它們每個控件關聯一個變量。
首先為被加數的編輯框IDC_SUMMAND_EDIT添加變量。
1.在編輯框上點右鍵,在右鍵菜單中選擇“Add Variable”。彈出添加成員變量的向導對話框。
2.我們想為其添加值變量而不是控件變量,是以對話框中“Category”下的組合框中選擇Value。
3.“Variable type”下的組合框此時預設選中的是“CString”,CString是字元串類,顯然不能進行加法運算。我們可以選擇double、float、int等。這裡我們選擇double,即編輯框關聯一個double類型的變量。
4.在“Variable name”中寫入自定義的變量名。雞啄米為其取名m_editSummand。
5.點“Finish”完成。
注意,類的成員變量名一般以m_打頭,以辨別它是一個成員變量。
參照此方法,再分别為加數的編輯框IDD_ADDEND_EDIT添加double型變量m_editAddend、和的編輯框IDD_SUM_EDIT添加double型變量m_editSum。
對話框類的資料交換和檢驗
在程式運作界面中,使用者往往會改變控件的屬性,例如,在編輯框中輸入字元串,或者改變組合框的選中項,又或者改變複選框的選中狀态等。控件的屬性改變後MFC會相應修改控件關聯變量的值。這種同步的改變是通過MFC為對話框類自動生成的成員函數DoDataExchange()來實作的,這也叫做對話框的資料交換和檢驗機制。
我們為三個編輯框添加了變量以後,在AdditionDlg.cpp中CAdditionDlg的DoDataExchange()函數的函數體中多了三條DDX_Text調用語句。下面是函數體代碼和雞啄米添加的注釋。
C++代碼
void CAdditionDlg::DoDataExchange(CDataExchange* pDX)
{
// 處理MFC預設的資料交換
CDialogEx::DoDataExchange(pDX);
// 處理控件IDC_SUMMAND_EDIT和變量m_editSummand之間的資料交換
DDX_Text(pDX, IDC_SUMMAND_EDIT, m_editSummand);
// 處理控件IDC_ADDEND_EDIT和變量m_editAddend之間的資料交換
DDX_Text(pDX, IDC_ADDEND_EDIT, m_editAddend);
// 處理控件IDC_SUM_EDIT和變量m_editSum之間的資料交換
DDX_Text(pDX, IDC_SUM_EDIT, m_editSum);
}
雞啄米再以Addition程式為例簡單說下資料交換機制。如果我們在程式運作界面中輸入被加數,則通過CAddition的DoDataExchange()函數可以将輸入的值儲存到m_editSummand變量中,反之如果程式運作中修改了變量m_editSummand的值,則通過CAddition的DoDataExchange()函數也可以将新的變量值顯示到被加數的編輯框中。
但是這種資料交換機制中,DoDataExchange()并不是被自動調用的,而是需要我們在程式中調用CDialogEx::UpdateData()函數,由UpdateData()函數再去自動調用DoDataExchange()的。
CDialogEx::UpdateData()函數的原型為:
BOOL UpdateData(BOOL bSaveAndValidate = TRUE);
參數:bSaveAndValidate用于訓示資料傳輸的方向,TRUE表示從控件傳給變量,FALSE表示從變量傳給控件。預設值是TRUE,即從控件傳給變量。
傳回值:CDialogEx::UpdateData()函數的傳回值表示操作是否成功,成功則傳回TRUE,否則傳回FALSE。
在下一講中雞啄米将具體示範CDialogEx::UpdateData()函數如何使用。
雞啄米本節主要講的是建立對話框類和添加控件變量,控件的消息處理函數将在下一講詳細介紹。依然歡迎大家常回雞啄米部落格學習和讨論。
View Code
4.對話框:為控件添加消息處理函數
建立對話框類和添加控件變量在上一講中已經講過,這一講的主要内容是如何為控件添加消息處理函數。
MFC為對話框和控件等定義了諸多消息,我們對它們操作時會觸發消息,這些消息最終由消息處理函數處理。比如我們點選按鈕時就會産生BN_CLICKED消息,修改編輯框内容時會産生EN_CHANGE消息等。一般為了讓某種操作達到效果,我們隻需要實作某個消息的消息處理函數。
一.添加消息處理函數
雞啄米仍以前面的加法電腦的程式為例,說明怎樣為“計算”按鈕控件添加消息處理函數。添加方法列出4種:
1.使用Class Wizard添加消息處理函數
用過的VC++ 6.0的朋友應該對Class Wizard很熟悉了,添加類、消息處理函數等經常會用到它,可以說是一個很核心的功能。但從VS2002開始就見不到Class Wizard了,大部分功能都內建到對話框和控件等的屬性中了,使用很友善。到VS2010,久違的Class Wizard又回來了。但雞啄米已經習慣了使用屬性中的功能了,對于從VC++ 6.0直接轉VS2010的朋友可能覺得還是使用Class Wizard比較習慣。
大家應該記得,“計算”按鈕的ID為IDC_ADD_BUTTON,上圖中Commands标簽下,Oject IDs清單中有此ID,因為我們是想實作點選按鈕後的消息處理函數,是以在Messages清單中選擇BN_CLICKED消息,然後點右上方的Add Handler就可以添加BN_CLICKED消息處理函數OnClickedAddButton了。當然你也可以改名,但一般用的預設的就可以。
2.通過“Add Event Handler...”添加消息處理函數
在“計算”按鈕上點右鍵,然後在右鍵菜單中選擇菜單項“Add Event Handler...”,彈出“Event Handler Wizard”對話框,如下圖:
可見“Message type”中預設選中的就是BN_CLICKED消息,函數名和所在類都已經自動給出,直接點“Add and Edit”就可以了。
3.在按鈕的屬性視圖中添加消息處理函數
上面說過,從VS2002開始就主要從屬性視圖添加消息處理函數了。我們在“計算”按鈕上點右鍵,在右鍵菜單中選擇“Properties”,右側面闆中會顯示按鈕的屬性視圖。
我們可以像上圖中那樣,點屬性視圖的“Control Events”按鈕(類似閃電标志),下面列出了“計算”按鈕的所有消息。我們要處理的是BN_CLICKED消息,點其右側空白清單項,會出現一個帶下箭頭的按鈕,再點此按鈕會出現“<Add> OnBnClickedAddButton”選項,最後選中這個選項就會自動添加BN_CLICKED處理函數了。
4.輕按兩下按鈕添加消息處理函數
最直接最簡單的方法就是,輕按兩下“計算”按鈕,MFC會自動為其在CAdditionDlg類中添加BN_CLICKED消息的處理函數OnBnClickedAddButton()。
二.在消息處理函數中添加自定義功能
在我們使用任意一種方法添加了消息處理函數以後,都隻能得到一個空的OnBnClickedAddButton()函數的函數體,要實作我們想要的功能,還需要在函數體中加入自定義功能代碼。
在加法電腦程式中,我們想要“計算”按鈕實作的功能是,擷取被加數和加數的數值,然後計算它們的和并顯示到和的編輯框裡。那麼,OnBnClickedAddButton()的函數體就應修改為:
C++代碼
void CAdditionDlg::OnBnClickedAddButton()
{
// TODO: Add your control notification handler code here
// 将各控件中的資料儲存到相應的變量
UpdateData(TRUE);
// 将被加數和加數的加和指派給m_editSum
m_editSum = m_editSummand + m_editAddend;
// 根據各變量的值更新相應的控件。和的編輯框會顯示m_editSum的值
UpdateData(FALSE);
}
雞啄米在上面的代碼中已經添加注釋,大家應該很容易了解了。對于UpdateData()函數的說明在上一講中已經介紹過,如果忘了可以再回上一講了解了解。
接下來我們運作下此應用程式。在運作結果界面中,輸入被加數5.1,加數2.3,然後點“計算”:
在上圖中可以看到,點“計算”按鈕後,和的編輯框中顯示了正确結果:7.4。
雞啄米簡單分析下運作過程:輸入被加數和加數,點“計算”按鈕後産生點選消息,進而調用OnBnClickedAddButton()函數。進入此函數後,首先由UpdateData(TRUE)函數将被加數的值5.1和加數的值2.3分别儲存到變量m_editSummand和m_editAddend,然後通過語句m_editSum = m_editSummand + m_editAddend;計算出被加數和加數的和為7.4,并把7.4指派給m_editSum。最後調用UpdateData(FALSE)根據被加數、加數、和的值更新三個編輯框的顯示值,就得到了上圖中的結果。
到此,一個具有簡單的加法運算功能的加法電腦應用程式就基本完成了。如果大家想實作其他功能,可以修改控件資源和消息處理函數來練習下。本節就講到這裡了,有問題歡迎到雞啄米部落格或者我們的程式設計入門qq群讨論。
View Code
5.對話框:設定對話框控件的Tab順序
前面幾節雞啄米為大家示範了加法電腦程式完整的編寫過程,本節主要講對話框上控件的Tab順序如何調整。
上一講為“計算”按鈕添加了消息處理函數後,加法電腦已經能夠進行浮點數的加法運算。但是還有個遺留的小問題,就是對話框控件的Tab順序問題。
運作加法電腦程式,顯示對話框後不進行任何操作,直接按回車,可以看到對話框退出了。這是因為“退出”按鈕是Tab順序為1的控件,也就是第一個接受使用者輸入的控件。但是按照我們的輸入習慣,應該是被加數的編輯框首先接受使用者輸入,然後是加數編輯框,再接下來是“計算”按鈕,最後才是“退出”按鈕。
我們先來直覺的看看各個控件的Tab順序吧。打開“Resource View”視圖,然後在資源中找到對話框IDD_ADDITION_DIALOG,輕按兩下ID後中間客戶區域出現其模闆視圖。在主菜單中選擇“Format”->"Tab Order",或者按快捷鍵Ctrl+D,對話框模闆上就會顯示各個控件的Tab順序數字。如下圖:
上圖中每個控件左上角都有一個數字,這就是它的Tab響應順序。對話框剛打開時輸入焦點就在Tab順序為1的“退出”按鈕上,不做任何操作按下Tab鍵,輸入焦點就會轉移到Tab順序為2的“被加數”靜态文本框上,但是因為靜态文本框不接受任何輸入,是以輸入焦點繼續自動轉移到Tab順序為3的被加數編輯框,再按Tab鍵,輸入焦點又會轉移到Tab順序為4的“加數”靜态文本框上,同樣由于它是靜态文本框,輸入焦點不停留繼續轉移到加數編輯框,後面的控件同理。
我們認為這個順序不合理,那怎麼修改呢?很簡單,從自己認為Tab順序應該為1的控件開始依次單擊,随着單擊的完成,各控件的Tab響應順序也按我們的想法設定好了。
例如,此例中我們可以依次單擊被加數編輯框、“被加數”靜态文本框、加數編輯框、“加數”靜态文本框、和編輯框、“和”靜态文本框、“計算”按鈕和“退出”按鈕。設定完後如下圖:
最後按ESC鍵,确認設定并退出對話框模闆的Tab順序設定狀态。
現在我們再運作程式,可以看到對話框打開後最初的輸入焦點在被加數編輯框上,然後我們按Tab鍵,輸入焦點移到加數編輯框上,繼續多次按Tab鍵時,輸入焦點會按“和編輯框--‘計算’按鈕--‘退出’按鈕--被加數編輯框--加數編輯框--和編輯框......”的順序循環轉移。這樣就達到了我們的目的。
本節教程内容比較簡單,相信大家很快就能掌握。依然歡迎大家在雞啄米部落格留言或到我們的程式設計入門群讨論。
View Code
6.對話框:模态對話框及其彈出過程
加法電腦對話框程式大家照着做一遍後,相信對基于對話框的程式有些了解了,有個好的開始對于以後的學習大有裨益。趁熱打鐵,雞啄米這一節講講什麼是模态對話框和非模态對話框,以及模态對話框怎樣彈出。
一.模态對話框和非模态對話框
Windows對話框分為兩類:模态對話框和非模态對話框。
模态對話框是這樣的對話框,當它彈出後,本應用程式其他視窗将不再接受使用者輸入,隻有該對話框響應使用者輸入,在對它進行相應操作退出後,其他視窗才能繼續與使用者互動。
非模态對話框則是,它彈出後,本程式其他視窗仍能響應使用者輸入。非模态對話框一般用來顯示提示資訊等。
大家對Windows系統很了解,相信這兩種對話框應該都遇到過。之前的加法電腦對話框其實就是模态對話框。
二.模态對話框是怎樣彈出的
畢竟加法電腦程式大部分都是MFC自動生成的,對話框怎麼彈出來的大家可能還不是很清楚。雞啄米下面簡單說說它是在哪裡彈出來的,再重建立一個新的對話框并彈出它,這樣大家實踐以後就能更靈活的使用模态對話框了。
大家打開Addition.cpp檔案,可以看到CAdditionApp類有個InitInstance()函數,在MFC應用程式架構分析中提到過此函數,不過那是單文檔應用程式App類中的,函數體不太相同,但都是進行App類執行個體的初始化工作。
InitInstance()函數的後半部分有一段代碼就是定義對話框對象并彈出對話框的,雞啄米下面給出這段代碼并加以注釋:
C++代碼
CAdditionDlg dlg; // 定義對話框類CAdditionDlg的對象dlg
m_pMainWnd = &dlg; // 将dlg設為主視窗
INT_PTR nResponse = dlg.DoModal(); // 彈出對話框dlg,并将DoModal函數的傳回值(退出時點選按鈕的ID)指派給nResponse
if (nResponse == IDOK) // 判斷傳回值是否為OK按鈕(其ID為IDOK,雞啄米已經将它删除)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL) // 判斷傳回值是否為Cancel按鈕(其ID為IDCANCEL,雞啄米将它的Caption改為了“退出”)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
彈出對話框比較關鍵的一個函數,就是對話框類的DoModal()函數。CDialog::DoModal()函數的原型為:
virtual INT_PTR DoModal();
傳回值:整數值,指定了傳遞給CDialog::EndDialog(該函數用于關閉對話框)的nResult參數值。如果函數不能建立對話框,則傳回-1;如果出現其它錯誤,則傳回IDABORT。
調用了它對話框就會彈出,傳回值是退出對話框時所點的按鈕的ID,比如,我們點了“退出”按鈕,那麼DoModal傳回值為IDCANCEL。
三.添加一個新對話框并彈出它
雞啄米再為加法電腦程式添加一個對話框,以在計算之前詢問使用者是否确定要進行計算。大家可以完整的看下對話框的添加和彈出過程。
1.根據“建立對話框模闆和修改對話框屬性”中所講的方法,在Resource View中的“Dialog”上點右鍵選擇“Insert Dialog”,建立一個新的對話框模闆,修改其ID為IDD_TIP_DIALOG,Caption改為“提示”,然後參考“為對話框添加控件”中所講,在對話框模闆上添加一個靜态文本框(static text),Caption改為“您确定要進行加法計算嗎?”,接下來修改OK按鈕的Caption為“确定”,Cancel按鈕的Caption為“取消”,最後調整各個控件的位置和對話框的大小。最終的對話框模闆如下圖:
2.根據“建立對話框類和添加控件變量”中建立對話框類的方法,在對話框模闆上點右鍵選擇“Add Class...”,彈出添加類的對話框,設定“Class name”為CTipDlg,點“OK”。在Solution Explorer中可以看到生成了CTipDlg類的頭檔案TipDlg.h和源檔案TipDlg.cpp。
3.我們要在點“計算”按鈕之後彈出此提示對話框,那麼就要在“計算”按鈕的消息處理函數OnBnClickedAddButton()中通路提示對話框類,是以為了通路CTipDlg類,在AdditionDlg.cpp中包含CTipDlg的頭檔案:#include "TipDlg.h"。
4.修改OnBnClickedAddButton()的函數體,在所有代碼前,構造CTipDlg類的對象tipDlg,并通過語句tipDlg.DoModal();彈出對話框,最後判斷DoModal()函數的傳回值是IDOK還是IDCANCEL來确定是否繼續進行計算。OnBnClickedAddButton()函數修改後如下:
C++代碼
void CAdditionDlg::OnBnClickedAddButton()
{
// TODO: Add your control notification handler code here
INT_PTR nRes; // 用于儲存DoModal函數的傳回值
CTipDlg tipDlg; // 構造對話框類CTipDlg的執行個體
nRes = tipDlg.DoModal(); // 彈出對話框
if (IDCANCEL == nRes) // 判斷對話框退出後傳回值是否為IDCANCEL,如果是則return,否則繼續向下執行
return;
// 将各控件中的資料儲存到相應的變量
UpdateData(TRUE);
// 将被加數和加數的加和指派給m_editSum
m_editSum = m_editSummand + m_editAddend;
// 根據各變量的值更新相應的控件。和的編輯框會顯示m_editSum的值
UpdateData(FALSE);
}
5.測試。編譯運作程式後,在對話框上輸入被加數和加數,點“計算”,彈出提示對話框詢問是否進行計算,如果選擇“确定”,則提示對話框退出,并在主對話框上顯示被加數和加數的和,而如果選擇“取消”,則提示對話框也會退出,但主對話框顯示的和不變,即沒有進行加法計算。
到此,大家對于模态對話框的基本使用方法應該掌握了吧。希望大家繼續關注雞啄米的MFC教程,我們共同進步。
View Code
7.對話框:非模态對話框的建立及顯示
上一節雞啄米講了模态對話框及其彈出過程,本節接着講另一種對話框--非模态對話框的建立及顯示。
雞啄米已經說過,非模态對話框顯示後,程式其他視窗仍能正常運作,可以響應使用者輸入,還可以互相切換。雞啄米會将上一講中建立的Tip模态對話框改為非模态對話框,讓大家看下效果。
非模态對話框的對話框資源和對話框類
實際上,模态對話框和非模态對話框在建立對話框資源和生成對話框類上是沒有差別的,是以上一講中建立的IDD_TIP_DIALOG對話框資源和CTipDlg類都不需要修改。
建立及顯示非模态對話框的步驟
需要修改的是,對話框類執行個體的建立和顯示,也就是之前在CAdditionDlg::OnBnClickedAddButton()函數體中添加的對話框顯示代碼。下面是具體步驟:
1.在AdditionDlg.h中包含CTipDlg頭檔案并定義CTipDlg類型的指針成員變量。詳細操作方法是,在AdditionDlg.cpp中删除之前添加的#include "TipDlg.h",而在AdditionDlg.h中添加#include "TipDlg.h",這是因為我們需要在AdditionDlg.h中定義CTipDlg類型的指針變量,是以要先包含它的頭檔案;然後在AdditionDlg.h中為CAdditionDlg類添加private成員變量CTipDlg *m_pTipDlg;。
2.在CAdditionDlg類的構造函數中初始化成員變量m_pTipDlg。如果cpp檔案中函數太多,我們可以在Class View上半個視圖中找到CAdditionDlg類,再在下半個視圖中找到其構造函數輕按兩下,中間客戶區域即可馬上切到構造函數的實作處。在構造函數體中添加m_pTipDlg = NULL;,這是個好習慣,雞啄米在C++程式設計入門系列的指針的指派和指針運算中說到過,在任何指針變量使用前都初始化,可以避免因誤通路重要記憶體位址而破壞此位址的資料。
3.将上一講中添加的模态對話框顯示代碼注釋或删除掉,添加非模态對話框的建立和顯示代碼。VC++中注釋單行代碼使用“//”,注釋多行代碼可以在需注釋的代碼開始處添加“/*”,結束處添加“*/”。修改後的CAdditionDlg::OnBnClickedAddButton()函數如下:
C++代碼
void CAdditionDlg::OnBnClickedAddButton()
{
// TODO: Add your control notification handler code here
/*INT_PTR nRes; // 用于儲存DoModal函數的傳回值
CTipDlg tipDlg; // 構造對話框類CTipDlg的執行個體
nRes = tipDlg.DoModal(); // 彈出對話框
if (IDCANCEL == nRes) // 判斷對話框退出後傳回值是否為IDCANCEL,如果是則return,否則繼續向下執行
return;*/
// 如果指針變量m_pTipDlg的值為NULL,則對話框還未建立,需要動态建立
if (NULL == m_pTipDlg)
{
// 建立非模态對話框執行個體
m_pTipDlg = new CTipDlg();
m_pTipDlg->Create(IDD_TIP_DIALOG, this);
}
// 顯示非模态對話框
m_pTipDlg->ShowWindow(SW_SHOW);
// 将各控件中的資料儲存到相應的變量
UpdateData(TRUE);
// 将被加數和加數的加和指派給m_editSum
m_editSum = m_editSummand + m_editAddend;
// 根據各變量的值更新相應的控件。和的編輯框會顯示m_editSum的值
UpdateData(FALSE);
}
4.因為此非模态對話框執行個體是動态建立的,是以需要手動删除此動态對象來銷毀對話框。我們在CAdditionDlg類的析構函數中添加删除代碼,但是MFC并沒有自動給出析構函數,這時需要我們手動添加,在對話框對象析構時就會調用我們自定義的析構函數了。在AdditionDlg.h檔案中為CAdditionDlg添加析構函數聲明:~CAdditionDlg();,然後在AdditionDlg.cpp檔案中添加析構函數的實作,函數體如下:
C++代碼
CAdditionDlg::~CAdditionDlg()
{
// 如果非模态對話框已經建立則删除它
if (NULL != m_pTipDlg)
{
// 删除非模态對話框對象
delete m_pTipDlg;
}
}
這樣,非模态對話框建立和顯示的代碼就添加修改完了。讓我們運作下看看效果吧。
在加法電腦對話框上輸入被加數和加數,然後點“計算”按鈕,依然像上節一樣彈出了提示對話框,但是先不要關閉它,你可以拖動它後面的加法電腦對話框試試,我們發現加法電腦對話框竟然可以拖動了,而且“和”編輯框裡已經顯示了運算結果,這表明提示對話框顯示以後還沒有關閉,OnBnClickedAddButton() 就繼續向下執行了,不僅如此,加法電腦的每個編輯框還都可以響應輸入。
這隻是一個簡單的例子,非模态對話框的用處有很多,以後大家在軟體開發中會用到。
本節教程就到這裡了,相信大家對對話框的使用更上了一個台階了,在不同的情況下可以選擇使用模态對話框和非模态對話框了。雞啄米歡迎大家留言讨論。
View Code
8.對話框:屬性頁對話框及相關類的介紹
前面講了模态對話框和非模态對話框,本節開始雞啄米講一種特殊的對話框--屬性頁對話框。另外,本套教程所講大部分對VC++各個版本均可适用或者稍作修改即可,但考慮到終究還是基于VS2010版本的,是以将《VC++/MFC程式設計入門》改為《VS2010/MFC程式設計入門》。
屬性頁對話框的分類
屬性頁對話框想必大家并不陌生,XP系統中桌面右鍵點屬性,彈出的就是屬性頁對話框,它通過标簽切換各個頁面。另外,我們在建立MFC工程時使用的向導對話框也屬于屬性頁對話框,它通過點選“Next”等按鈕來切換頁面。
屬性頁對話框就是包含一般屬性頁對話框和向導對話框兩類。它将多個對話框內建于一身,通過标簽或按鈕來切換頁面。
屬性頁對話框相關類
我們使用屬性頁對話框時,用到的類主要有兩個:CPropertyPage類和CPropertySheet類。
1.CPropertyPage類
CPropertyPage類繼承自CDialog類,它被用于處理某單個的屬性頁,是以要為每個屬性頁都建立一個繼承自CPropertyPage的子類。大家可以在VS2010的MSDN中查找CPropertyPage類以及它的成員的詳細說明。下面雞啄米就為大家講解MSDN中列出的CPropertyPage類的部分主要成員函數。
(1)構造函數
這裡講三個CProperty類的構造函數,函數原型為:
CPropertyPage( );
explicit CPropertyPage(
UINT nIDTemplate,
UINT nIDCaption = 0,
DWORD dwSize = sizeof(PROPSHEETPAGE)
);
explicit CPropertyPage(
LPCTSTR lpszTemplateName,
UINT nIDCaption = 0,
DWORD dwSize = sizeof(PROPSHEETPAGE)
);
第一個是沒有任何參數的構造函數。
第二個構造函數中,參數nIDTemplate是屬性頁的對話框資源ID,參數nIDCaption是屬性頁對話框頁籤的标題所用字元串資源的ID,若設為0,則頁籤标題就使用該屬性頁的對話框資源的标題。
第三個構造函數中,參數lpszTemplateName為屬性頁的對話框資源的名稱字元串,不能為NULL。參數nIDCaption同上。
(2)CancelToClose()函數
在模态屬性頁對話框的屬性頁進行了某不可恢複的操作後,使用CancelToClose()函數将“OK”按鈕改為“Close”按鈕,并禁用“Cancel”按鈕。函數原型為:
void CancelToClose( );
(3)SetModified()函數
調用此函數可激活或禁用“Apply”按鈕,函數原型為:
void SetModified(BOOL bChanged = TRUE);
(4)可重載函數
CPropertyPage類提供了一些消息處理函數,來響應屬性頁對話框的各種消息。我們重載這些消息處理函數,就可以自定義對屬性頁對話框操作的處理。可重載的消息處理函數包括:
OnApply:處理屬性頁的“Apply”按鈕被單擊的消息
OnCancel:處理屬性頁的“Cancel”按鈕被單擊的消息
OnKillActive:處理屬性頁目前活動狀态被切換的消息,常用于資料驗證
OnOK:處理屬性頁的“OK”按鈕、“Apply”按鈕或者“Close”按鈕被單擊的消息
OnQueryCancel:處理屬性頁的“Cancel”按鈕被單擊前發出的消息
OnReset:處理屬性頁的“Reset”按鈕被單擊的消息
OnSetActive:處理屬性頁被切換為目前活動頁的消息
OnWizardBack:處理屬性頁的“Back”按鈕被單擊的消息,僅在向導對話框中有效
OnWizardFinish:處理屬性頁的“Finish”按鈕被單擊的消息,僅在向導對話框中有效
OnWizardNext:處理屬性頁的“Next”按鈕被單擊的消息,僅在向導對話框中有效
2.CPropertySheet類
CPropertySheet類繼承自CWnd類,它是屬性表類,負責加載、打開或删除屬性頁,并可以在屬性頁對話框中切換屬性頁。它跟對話框類似,也有模态和非模态兩種。下面雞啄米就講解CPropertySheet類的部分成員函數。
(1)構造函數
這裡依然列出CPropertySheet類的三個構造函數:
CPropertySheet( );
explicit CPropertySheet(
UINT nIDCaption,
CWnd* pParentWnd = NULL,
UINT iSelectPage = 0
);
explicit CPropertySheet(
LPCTSTR pszCaption,
CWnd* pParentWnd = NULL,
UINT iSelectPage = 0
);
參數nIDCaption:标題的字元串資源的ID。
參數pParentWnd:屬性頁對話框的父視窗,若設為NULL,則父視窗為應用程式的主視窗。
參數iSelectPage:初始狀态時,活動屬性頁的索引,預設為第一個添加到屬性表的屬性頁。
參數pszCaption:标題字元串。
(2)GetActiveIndex()函數
擷取目前活動屬性頁的索引。函數原型為:
int GetActiveIndex( ) const;
傳回值:目前活動屬性頁的索引。
(3)GetActivePage()函數
擷取目前活動屬性頁對象。函數原型為:
CPropertyPage* GetActivePage( ) const;
傳回值:目前活動屬性頁對象的指針。
(4)GetPage()函數
擷取某個屬性頁對象。函數原型為:
CPropertyPage* GetPage(int nPage) const;
參數nPage:目标屬性頁的索引。
傳回值:目标屬性頁對象的指針。
(5)GetPageCount()函數
擷取屬性頁的數量。函數原型為:
int GetPageCount( ) const;
傳回值:屬性頁的數量。
(6)GetPageIndex()函數
擷取某屬性頁在屬性頁對話框中的索引。函數原型為:
int GetPageIndex(CPropertyPage* pPage);
參數pPage:要擷取索引的屬性頁對象的指針。
傳回值:屬性頁對象在屬性頁對話框中的索引。
(7)SetActivePage()函數
設定某個屬性頁為活動屬性頁。函數原型為:
BOOL SetActivePage(
int nPage
);
BOOL SetActivePage(
CPropertyPage* pPage
);
參數nPage:要設定為活動屬性頁的索引。
參數pPage:要設定為活動屬性頁的對象指針。
(8)SetWizardButtons()函數
在向導對話框上啟用或禁用Back、Next或Finish按鈕,應在調用DoModal之前調用此函數。函數原型為:
void SetWizardButtons(
DWORD dwFlags
);
參數dwFlags:設定向導按鈕的外觀和功能屬性。可以是以下值的組合:
PSWIZB_BACK 啟用“Back”按鈕,如果不包含此值則禁用“Back”按鈕。
PSWIZB_NEXT 啟用“Next”按鈕,如果不包含此值則禁用“Next”按鈕。
PSWIZB_FINISH 啟用“Finish”按鈕。
PSWIZB_DISABLEDFINISH 顯示禁用的“Finish”按鈕。
(9)SetWizardMode()函數
設定屬性頁對話框為向導對話框模式,應在調用DoModal之前調用此函數。函數原型為:
void SetWizardMode( );
(10)SetTitle()函數
設定屬性對話框的标題。函數原型為:
void SetTitle(
LPCTSTR lpszText,
UINT nStyle = 0
);
參數lpszText:标題字元串。
參數nStyle:指定屬性表标題的風格。應當為0或PSH_PROPTITLE。如果設為PSH_PROPTITLE,則單詞“Properties”會出現在指定标題之後。例如,SetTitle("Simple",PSH_PROPTITLE)這種調用會使得屬性表标題為“Simple Properties”。
(11)AddPage()函數
為屬性對話框添加新的屬性頁。函數原型為:
void AddPage(
CPropertyPage *pPage
);
參數pPage:要添加的新的屬性頁的對象指針。
(12)PressButton()函數
模拟按下某指定的按鈕。函數原型為:
void PressButton(
int nButton
);
參數nButton:要模拟按下的按鈕,它可以是下列值之一:
PSBTN_BACK 選擇“Back”按鈕。
PSBTN_NEXT 選擇“Next”按鈕。
PSBTN_FINISH 選擇“Finish”按鈕。
PSBTN_OK 選擇“OK”按鈕。
PSBTN_APPLYNOW 選擇“Apply”按鈕。
PSBTN_CANCEL 選擇“Cancel”按鈕。
PSBTN_HELP 選擇“幫助”按鈕。
(13)RemovePage()函數
删除某屬性頁。函數原型為:
void RemovePage(
CPropertyPage *pPage
);
void RemovePage(
int nPage
);
參數pPage:要删除的屬性頁的對象指針。
參數nPage:要删除的屬性頁的索引。
屬性對話框和相關的兩個類雞啄米就先介紹到這,主要是為後面使用屬性頁對話框做準備。有問題可以到雞啄米部落格交流。謝謝。
View Code
9.對話框:向導對話框的建立及顯示
上一講雞啄米講了屬性頁對話框和相關的兩個類CPropertyPage類和CPropertySheet類,對使用屬性頁對話框做準備。本節将為大家示範如何建立向導對話框。
仍然以前面的“加法電腦”的例子為基礎,在其中加入向導對話框,我們可以用它來說明加法電腦的使用方法,一步一步引導使用者操作,這也是比較常見的用法。
加法電腦使用時大概可以分為三步:輸入被加數、輸入加數、點“計算”按鈕。
雞啄米就詳細說明向導對話框的建立步驟:
1.建立屬性頁對話框資源
根據建立對話框模闆和修改對話框屬性中所講方法,在“Resource View”的Dialog”節點上點右鍵,然後在右鍵菜單中選擇“Insert Dialog”建立第一個對話框模闆,對話框的ID屬性設定為IDD_SUMMAND_PAGE,Caption屬性改為“被加數頁”,Style屬性在下拉清單中選擇“Child”,Border屬性在下拉清單中選擇“Thin”。
删除“OK”和“Cancel”按鈕,再按照為對話框添加控件中所講方法,添加一個靜态文本框,并修改靜态文本框的Caption屬性為“請先輸入double型被加數”。
按照上述步驟,繼續添加第二個和第三個對話框資源。第二個對話框模闆的ID設為IDD_ADDEND_PAGE,Caption屬性改為“加數頁”,也添加一個靜态文本框,Caption設為“請繼續輸入double型加數”,其他屬性同第一個對話框。第三個對話框模闆的ID設為IDD_ADD_PAGE,Caption屬性改為“計算頁”,添加靜态文本框的Caption屬性改為“最後請按下“計算”按鈕”,其他屬性也第一個對話框一樣。
2.建立屬性頁類
按照建立對話框類和添加控件變量中的方法,在第一個對話框模闆上點右鍵,在右鍵菜單中選擇“Add Class”,彈出類向導對話框,在“Class name”編輯框中輸入類名“CSummandPage”,與之前不同的是,因為屬性頁類都應繼承于CPropertyPage類,是以要修改下面“Base class”的選項,在下拉清單中選擇“CPropertyPage”。
因為是第一個屬性頁,是以它應該有一個“下一步”按鈕,在哪裡添加呢?上一講CPropertyPage類的可重載函數中提到,OnSetActive函數用于處理屬性頁被切換為目前活動頁的消息,是以我們可以在OnSetActive函數中進行相關設定。
那怎樣重載OnSetActive函數呢?我們可以在“Class View”中找到“CSummandPage”節點,點右鍵彈出右鍵菜單,選擇“Properties”,然後VS2010右側面闆上會顯示對話框的屬性清單,屬性清單的工具欄上有個tip資訊為“Overrides”的按鈕,按下它,下方清單中就列出了重載函數,找到“OnSetActive”,點其右側空白清單項出現向下箭頭,再點箭頭就在下面出現了“<Add>OnSetActive”的選項,選擇它就會自動在CSummandPage類中添加函數OnSetActive。
我們隻需在OnSetActive函數體中添加相關代碼就可以實作添加“下一步”按鈕的效果了。新的函數體如下:
C++代碼
BOOL CSummandPage::OnSetActive()
{
// TODO: Add your specialized code here and/or call the base class
// 獲得父視窗,即屬性表CPropertySheet類
CPropertySheet* psheet = (CPropertySheet*) GetParent();
// 設定屬性表隻有“下一步”按鈕
psheet->SetWizardButtons(PSWIZB_NEXT);
return CPropertyPage::OnSetActive();
}
為第二個和第三個對話框也分别添加屬性頁類CAddendPage和CAddPage。但第二個對話框的屬性頁不需要重載OnSetActive函數。第三個對話框是最後一個對話框,是以不需要“下一步”按鈕,而應該換成“完成”按鈕,是以也需要重載OnSetActive函數設定“完成”按鈕。重載後的OnSetActive如下:
C++代碼
BOOL CAddPage::OnSetActive()
{
// TODO: Add your specialized code here and/or call the base class
// 獲得父視窗,即屬性表CPropertySheet類
CPropertySheet* psheet = (CPropertySheet*) GetParent();
//設定屬性表隻有“完成”按鈕
psheet->SetFinishText(_T("完成"));
return CPropertyPage::OnSetActive();
}
上面的代碼段中,字元串“完成”前加了個_T,這是因為本工程建立的時候用的預設的Unicode字元集,而如果“完成”前不加_T就是ASCII字元串。_T實際上是一個宏,工程的字元集選擇為Unicode時字元串就轉為Unicode字元串,選擇為Muli-Byte時就轉為ASCII字元串。我們可以在Solution Explorer的Addition根節點上點右鍵,在右鍵菜單上選擇“Properties”,彈出工程的屬性對話框,Configuration Properties->General右側清單中的Character Set就顯示選擇的字元集。
那點了第三個屬性頁上的“完成”按鈕我們想進行某些處理的話,就重載OnWizardFinish函數,方法同OnSetActive函數。重載後的OnWizardFinish函數如下:
C++代碼
BOOL CAddPage::OnWizardFinish()
{
// TODO: Add your specialized code here and/or call the base class
// 提示向導完成
MessageBox(_T("使用說明向導已閱讀完!"));
return CPropertyPage::OnWizardFinish();
}
3.建立屬性表類
屬性頁資源和屬性頁類建立完以後,還不能生成向導對話框,我們還需要一個屬性表類,來容納這些屬性頁。
在Solution Explorer視圖中的根節點“Addition”上點右鍵,在右鍵菜單中選擇Add->Class,彈出“Add Class”對話框,然後在中間區域中選擇“MFC Class”,點“Add”按鈕,彈出另一個類向導對話框,設定Class name為CAddSheet,Base class選擇“CPropertySheet”,點“Finish”按鈕,這樣就屬性表類就建好了。
接下來,在新生成的AddSheet.h中包含三個屬性頁類的頭檔案:
#include "SummandPage.h"
#include "AddendPage.h"
#include "AddPage.h"
之後在AddSheet.h中添加private變量:
CSummandPage m_summandPage;
CAddendPage m_addendPage;
CAddPage m_addPage;
然後在AddSheet.cpp檔案中修改CAddSheet的兩個構造函數為:
C++代碼
CAddSheet::CAddSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
// 添加三個屬性頁到屬性表
AddPage(&m_summandPage);
AddPage(&m_addendPage);
AddPage(&m_addPage);
}
CAddSheet::CAddSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
// 添加三個屬性頁到屬性表
AddPage(&m_summandPage);
AddPage(&m_addendPage);
AddPage(&m_addPage);
}
4.顯示向導對話框
我們在加法電腦對話框上添加一個按鈕,點選它就打開向導對話框。此按鈕的ID設為IDC_INSTRUCT_BUTTON,Caption屬性設為“使用說明”。
按照為控件添加消息處理函數中所講方法,為IDC_INSTRUCT_BUTTON按鈕在CAdditionDlg類中添加點選消息的處理函數OnBnClickedInstructButton。然後在AdditionDlg.cpp檔案中包含CAddSheet的頭檔案:#include "AddSheet.h"。最後修改OnBnClickedInstructButton函數如下:
C++代碼
void CAdditionDlg::OnBnClickedInstructButton()
{
// TODO: Add your control notification handler code here
// 建立屬性表對象
CAddSheet sheet(_T(""));
// 設定屬性對話框為向導對話框
sheet.SetWizardMode();
// 打開模态向導對話框
sheet.DoModal();
}
到此,向導對話框就完整的建立完成了,并可以在加法電腦對話框上點“使用說明”按鈕顯示出來。我們來看看效果吧:
上圖隻是被加數頁的效果,點其上“下一步”按鈕就可以繼續顯示後面的兩個頁面。
是不是向導對話框沒有以前想象的那般複雜了?大家可以發揮想象,進行更複雜的修改,實作更完善的功能。依然歡迎朋友們到雞啄米部落格來交流學習。
View Code
10.對話框:一般屬性頁對話框的建立及顯示
屬性頁對話框包括向導對話框和一般屬性頁對話框兩類,上一節雞啄米講了如何建立并顯示向導對話框,本節将繼續介紹一般屬性頁對話框的建立和顯示。
實際上,一般屬性頁對話框的建立和顯示過程和向導對話框是很類似的。雞啄米将上一節中的向導對話框進行少量修改,使其成為一般屬性頁對話框。
一般屬性頁對話框的建立步驟:
1.建立屬性頁對話框資源
屬性頁對話框資源的建立方法同向導對話框是一樣的,上一講中的對話框資源不需進行任何修改。
2.建立屬性頁類
屬性頁類的建立和向導對話框的屬性頁類也基本一樣,隻是一般屬性頁對話框中不需要“下一步”和“完成”等按鈕,是以上一講中屬性頁類的OnSetActive和OnWizardFinish等重載函數可以去掉。即CSummandPage類中的OnSetActive函數、CAddPage類中的OnSetActive函數和OnWizardFinish函數可以删除或注釋掉。其他部分不需作任何修改。
3.建立屬性表類
建立屬性表類的過程同向導對話框屬性表類也是一樣的,是以上一講中的CAddSheet類不需修改。
4.顯示一般屬性頁對話框
上一講向導對話框的顯示是在OnBnClickedInstructButton函數中實作的,其中語句sheet.SetWizardMode();旨在設定屬性表為向導對話框模式,是以顯示一般屬性頁對話框時不需調用SetWizardMode成員函數。另外,我們可以将屬性頁對話框的标題設為“使用說明”,在構造屬性表對象時将此字元串作為構造函數的參數傳入。OnBnClickedInstructButton函數修改如下:
C++代碼
void CAdditionDlg::OnBnClickedInstructButton()
{
// TODO: Add your control notification handler code here
// 建立屬性表對象
CAddSheet sheet(_T("使用說明"));
// 打開模态一般屬性頁對話框
sheet.DoModal();
}
這樣一般屬性頁對話框的建立和顯示就講完了,我們運作下程式,在結果對話框上點“使用說明”按鈕看看效果吧:
再總結下,一般屬性頁對話框和向導對話框的建立和顯示的不同包括,是否需要OnSetActive和OnWizardFinish等重載函數,是否需要調用屬性表類的SetWizardMode函數設定為向導對話框模式。
是不是一般屬性頁對話框的建立和顯示也很簡單?到此,屬性頁對話框就講完了。雞啄米歡迎大家繼續關注後面的内容。
View Code
11.對話框:一般屬性頁對話框的建立及顯示
屬性頁對話框包括向導對話框和一般屬性頁對話框兩類,上一節雞啄米講了如何建立并顯示向導對話框,本節将繼續介紹一般屬性頁對話框的建立和顯示。
實際上,一般屬性頁對話框的建立和顯示過程和向導對話框是很類似的。雞啄米将上一節中的向導對話框進行少量修改,使其成為一般屬性頁對話框。
一般屬性頁對話框的建立步驟:
1.建立屬性頁對話框資源
屬性頁對話框資源的建立方法同向導對話框是一樣的,上一講中的對話框資源不需進行任何修改。
2.建立屬性頁類
屬性頁類的建立和向導對話框的屬性頁類也基本一樣,隻是一般屬性頁對話框中不需要“下一步”和“完成”等按鈕,是以上一講中屬性頁類的OnSetActive和OnWizardFinish等重載函數可以去掉。即CSummandPage類中的OnSetActive函數、CAddPage類中的OnSetActive函數和OnWizardFinish函數可以删除或注釋掉。其他部分不需作任何修改。
3.建立屬性表類
建立屬性表類的過程同向導對話框屬性表類也是一樣的,是以上一講中的CAddSheet類不需修改。
4.顯示一般屬性頁對話框
上一講向導對話框的顯示是在OnBnClickedInstructButton函數中實作的,其中語句sheet.SetWizardMode();旨在設定屬性表為向導對話框模式,是以顯示一般屬性頁對話框時不需調用SetWizardMode成員函數。另外,我們可以将屬性頁對話框的标題設為“使用說明”,在構造屬性表對象時将此字元串作為構造函數的參數傳入。OnBnClickedInstructButton函數修改如下:
C++代碼
void CAdditionDlg::OnBnClickedInstructButton()
{
// TODO: Add your control notification handler code here
// 建立屬性表對象
CAddSheet sheet(_T("使用說明"));
// 打開模态一般屬性頁對話框
sheet.DoModal();
}
這樣一般屬性頁對話框的建立和顯示就講完了,我們運作下程式,在結果對話框上點“使用說明”按鈕看看效果吧:
再總結下,一般屬性頁對話框和向導對話框的建立和顯示的不同包括,是否需要OnSetActive和OnWizardFinish等重載函數,是否需要調用屬性表類的SetWizardMode函數設定為向導對話框模式。
是不是一般屬性頁對話框的建立和顯示也很簡單?到此,屬性頁對話框就講完了。雞啄米歡迎大家繼續關注後面的内容。
View Code
12.對話框:消息對話框
前面幾節雞啄米講了屬性頁對話框,我們可以根據所講内容友善的建立自己的屬性頁對話框。本節講解Windows系統中最常用最簡單的一類對話框--消息對話框。
我們在使用Windows系統的過程中經常會見到消息對話框,提示我們有異常發生或提出詢問等。因為在軟體開發中經常用到消息對話框,是以MFC提供了兩個函數可以直接生成指定風格的消息對話框,而不需要我們在每次使用的時候都要去建立對話框資源和生成對話框類等。這兩個函數就是CWnd類的成員函數MessageBox()和全局函數AfxMessageBox()。
一.CWnd::MessageBox()函數和AfxMessageBox()函數的用法
下面雞啄米就分别講解兩個函數的用法。
1.CWnd::MessageBox()函數
CWnd::MessageBox()的函數原型如下:
int MessageBox(
LPCTSTR lpszText,
LPCTSTR lpszCaption = NULL,
UINT nType = MB_OK
);
參數說明:
lpszText:需要顯示的消息字元串。
lpszCaption:消息對話框的标題字元串。預設值為NULL。取值為NULL時使用預設标題。
nType:消息對話框的風格和屬性。預設為MB_OK風格,即隻有“确定”按鈕。
nType的取值可以是下面兩個表中任取一個值,也可以是各取一個值的任意組合。即可以指定一個對話框類型,也可以指定一個對話框圖示,還可以兩者都設定。
nType 取值 參數說明
MB_ABORTRETRY 有“終止”、“重試”和“忽略”按鈕
MB_OK 有“确定”按鈕
MB_OKCANCEL 有“确定”和“取消”按鈕
MB_RETRYCANCEL 有“重試”和“取消”按鈕
MB_YESNO 有“是”和“否”按鈕
MB_YESNOCANCEL 有“是”、“否”和“取消”按鈕
對話框類型表
nType 取值 顯示圖示
MB_ICONEXCLAMTIONMB_ICONWARNING
MB_ICONASTERISKMB_ICONINFORMATION
MB_ICONQUESTION
MB_ICONHANDMB_ICONSTOPMB_ICONERROR
對話框圖示表
如果想要設定nType的值為類型和圖示的組合,可以像這樣取值:MB_OKCANCEL | MB_ICONQUESTION。按位取或就可以了。
2.AfxMessageBox()函數
AfxMessageBox()的函數原型為:
int AfxMessageBox(
LPCTSTR lpszText,
UINT nType = MB_OK,
UINT nIDHelp = 0
);
參數說明:
lpszText:同CWnd::MessageBox()函數
nType:CWnd::MessageBox()函數
nIDHelp:此消息的幫助的上下文ID。預設值為0,取0時表示要使用應用程式的預設幫助上下文。
二.CWnd::MessageBox()和AfxMessageBox()的傳回值
我們在調用了上面兩個函數後,都可以彈出模态消息對話框。消息對話框關閉後,我們也都可以得到它們的傳回值。兩者的傳回值就是使用者在消息對話框上單擊的按鈕的ID,可以是以下值:
IDABORT:單擊“終止”按鈕。
IDCANCEL:單擊“取消”按鈕。
IDIGNORE:單擊“忽略”按鈕。
IDNO:單擊“否”按鈕。
IDOK:單擊“确定”按鈕。
IDRETRY:單擊“重試”按鈕。
IDYES:單擊“是”按鈕。
三.應用舉例
我們還是拿前面加法電腦的程式做例子。
大家是否記得,在模态對話框及其彈出過程中我們修改了CAdditionDlg::OnBnClickedAddButton()函數,在點了“計算”按鈕以後先彈出了一個模态對話框,詢問使用者是否确定要進行加法計算,并通過模态對話框DoModal函數的傳回值判斷使用者選擇了“确定”還是“取消”。這些功能很明顯消息對話框完全能夠實作,雞啄米就使用消息對話框來替代原來的模态對話框。
在非模态對話框的建立及顯示中,雞啄米注釋了模态對話框的相關代碼,加入了非模态對話框的建立和顯示代碼,我們在加入消息對話框之前将非模态對話框的代碼也注釋或删除掉,確定此函數中不再生成原來的模态對話框或非模态對話框。
修改後的CAdditionDlg::OnBnClickedAddButton()函數如下:
C++代碼
void CAdditionDlg::OnBnClickedAddButton()
{
// TODO: Add your control notification handler code here
INT_PTR nRes;
// 顯示消息對話框
nRes = MessageBox(_T("您确定要進行加法計算嗎?"), _T("加法電腦"), MB_OKCANCEL | MB_ICONQUESTION);
// 判斷消息對話框傳回值。如果為IDCANCEL就return,否則繼續向下執行
if (IDCANCEL == nRes)
return;
// 将各控件中的資料儲存到相應的變量
UpdateData(TRUE);
// 将被加數和加數的加和指派給m_editSum
m_editSum = m_editSummand + m_editAddend;
// 根據各變量的值更新相應的控件。和的編輯框會顯示m_editSum的值
UpdateData(FALSE);
// 設定屬性對話框為向導對話框
//sheet.SetWizardMode();
}
編譯運作,在運作結果對話框上點“計算”按鈕彈出以下消息對話框:
大家也可以将MessageBox函數換為AfxMessageBox()函數,同時參數進行相應修改,運作下看看效果。
消息對話框就講到這裡了。在以後的軟體開發中用到它的頻率很高,希望大家慢慢熟悉并掌握它。有問題歡迎回雞啄米部落格交流或加入我們的程式設計入門群。
View Code
13.對話框:檔案對話框
上一講雞啄米介紹的是消息對話框,本節講解檔案對話框。檔案對話框也是很常用的一類對話框。
檔案對話框的分類
檔案對話框分為打開檔案對話框和儲存檔案對話框,相信大家在Windows系統中經常見到這兩種檔案對話框。例如,很多編輯軟體像記事本等都有“打開”選項,選擇“打開”後會彈出一個對話框,讓我們選擇要打開檔案的路徑,這個對話框就是打開檔案對話框;除了“打開”選項一般還會有“另存為”選項,選擇“另存為”後往往也會有一個對話框彈出,讓我們選擇儲存路徑,這就是儲存檔案對話框。
正如上面舉例說明的,打開檔案對話框用于選擇要打開的檔案的路徑,儲存檔案對話框用來選擇要儲存的檔案的路徑。
檔案對話框類CFileDialog
MFC使用檔案對話框類CFileDialog封裝了對檔案對話框的操作。CFileDialog類的構造函數原型如下:
explicit CFileDialog(
BOOL bOpenFileDialog,
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL,
DWORD dwSize = 0,
BOOL bVistaStyle = TRUE
);
參數說明:
bOpenFileDialog:指定要建立的檔案對話框的類型。設為TRUE将建立打開檔案對話框,否則将建立儲存檔案對話框。
lpszDefExt:預設的檔案擴充名。如果使用者在檔案名編輯框中沒有輸入擴充名,則由lpszDefExt指定的擴充名将被自動添加到檔案名後。預設為NULL。
lpszFileName:檔案名編輯框中顯示的初始檔案名。如果為NULL,則不顯示初始檔案名。
dwFlags:檔案對話框的屬性,可以是一個值也可以是多個值的組合。關于屬性值的定義,可以在MSDN中查找結構體OPENFILENAME,元素Flags的說明中包含了所有屬性值。預設為OFN_HIDEREADONLY和OFN_OVERWRITEPROMPT的組合,OFN_HIDEREADONLY表示隐藏檔案對話框上的“Read Only”複選框,OFN_OVERWRITEPROMPT表示在儲存檔案對話框中如果你選擇的檔案存在了,就彈出一個消息對話框,要求确定是否要覆寫此檔案。
lpszFilter:檔案過濾器,它是由若幹字元串對組成的一個字元串序列。如果指定了檔案過濾器,則檔案對話框中隻有符合過濾條件的檔案顯示在檔案清單中待選擇。給大家看看VS2010 MSDN中給出的一個例子:
static TCHAR BASED_CODE szFilter[] = _T("Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||");
這樣設定過濾器以後,檔案對話框的擴充名組合框中将有四個選項:Chart Files (*.xlc)、Worksheet Files (*.xls)、Data Files(*.xlc;*.xls)和All Files (*.*),大家可以看到每種檔案的擴充名規定都是一個字元串對,例如Chart Files的過濾字元串是Chart Files(*.xlc)和*.xlc成對出現的。
pParentWnd:檔案對話框的父視窗的指針。
dwSize:OPENFILENAME結構體的大小。不同的作業系統對應不同的dwSize值。MFC通過此參數決定檔案對話框的适當類型(例如,建立Windows 2000檔案對話框還是XP檔案對話框)。預設為0,表示MFC将根據程式運作的作業系統版本來決定使用哪種檔案對話框。
bVistaStyle:指定檔案對話框的風格,設為TRUE則使用Vista風格的檔案對話框,否則使用舊版本的檔案對話框。此參數僅在Windows Vista中編譯時适用。
檔案對話框也是模态對話框,是以在打開時也需要調用CFileDialog類的DoModal()成員函數。在打開檔案對話框中點了“打開”或者在儲存檔案對話框中點了“儲存”以後,我們可以使用CFileDialog類的成員函數GetPathName()擷取選擇的檔案路徑。
下面列出幾個CFileDialog類的成員函數,我們可以使用它們獲得檔案對話框中的各種選擇。
GetFileExt():獲得標明檔案的字尾名。
GetFileName():獲得標明檔案的名稱,包括字尾名。
GetFileTitle():獲得標明檔案的标題,即不包括字尾名。
GetFolderPath():獲得標明檔案的目錄。
GetNextPathName():獲得下一個標明的檔案的路徑全名。
GetPathName():獲得標明檔案的路徑全名。
GetReadOnlyPref():獲得是否“以隻讀方式打開”。
GetStartPosition():獲得檔案名清單中的第一個元素的位置。
檔案對話框執行個體
根據前面所講内容,雞啄米給大家做個檔案對話框執行個體。
1.建立一個基于對話框的MFC應用程式工程,名稱設為“Example17”。
2.修改主對話框IDD_EXAMPLE17_DIALOG的模闆,删除自動生成的“TODO: Place dialog controls here.”靜态文本框,添加兩個編輯框,ID分别為IDC_OPEN_EDIT和IDC_SAVE_EDIT,再添加兩個按鈕,ID分别設為IDC_OPEN_BUTTON和IDC_SAVE_BUTTON,Caption分别設為“打開”和“儲存”。按鈕IDC_OPEN_BUTTON用于顯示打開檔案對話框,編輯框IDC_OPEN_EDIT顯示在打開檔案對話框中選擇的檔案路徑。按鈕IDC_SAVE_BUTTON用于顯示儲存檔案對話框,編輯框IDC_SAVE_BUTTON顯示在儲存檔案對話框中選擇的檔案路徑。
3.分别為按鈕IDC_OPEN_BUTTON和IDC_SAVE_BUTTON添加點選消息的消息處理函數CExample17Dlg::OnBnClickedOpenButton()和CExample17Dlg::OnBnClickedSaveButton()。
4.修改兩個消息處理函數如下:
C++代碼
void CExample17Dlg::OnBnClickedOpenButton()
{
// TODO: Add your control notification handler code here
// 設定過濾器
TCHAR szFilter[] = _T("文本檔案(*.txt)|*.txt|所有檔案(*.*)|*.*||");
// 構造打開檔案對話框
CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, this);
CString strFilePath;
// 顯示打開檔案對話框
if (IDOK == fileDlg.DoModal())
{
// 如果點選了檔案對話框上的“打開”按鈕,則将選擇的檔案路徑顯示到編輯框裡
strFilePath = fileDlg.GetPathName();
SetDlgItemText(IDC_OPEN_EDIT, strFilePath);
}
}
void CExample17Dlg::OnBnClickedSaveButton()
{
// TODO: Add your control notification handler code here
// 設定過濾器
TCHAR szFilter[] = _T("文本檔案(*.txt)|*.txt|Word檔案(*.doc)|*.doc|所有檔案(*.*)|*.*||");
// 構造儲存檔案對話框
CFileDialog fileDlg(FALSE, _T("doc"), _T("my"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter, this);
CString strFilePath;
// 顯示儲存檔案對話框
if (IDOK == fileDlg.DoModal())
{
// 如果點選了檔案對話框上的“儲存”按鈕,則将選擇的檔案路徑顯示到編輯框裡
strFilePath = fileDlg.GetPathName();
SetDlgItemText(IDC_SAVE_EDIT, strFilePath);
}
}
上面顯示編輯框内容時,雞啄米使用了Windows API函數SetDlgItemText,當然也可以先給編輯框關聯變量,然後再使用雞啄米在建立對話框類和添加控件變量中介紹的
CDialogEx::UpdateData()函數,但是雞啄米比較習慣使用SetDlgItemText函數,感覺比較靈活。
5.運作此程式,在結果對話框上點“打開”按鈕,顯示打開檔案對話框如下:
點“儲存”按鈕後,顯示儲存檔案對話框:
在打開檔案對話框和儲存檔案對話框都選擇了檔案路徑後,主對話框如下:
到此,檔案對話框就講完了,是不是依然很簡單?如果忘記了檔案對話框類構造函數的參數意義,可以回到雞啄米來看看或者在MSDN上查閱。
View Code
14.對話框:字型對話框
雞啄米在上一節為大家講解了檔案對話框的使用,本節則主要介紹字型對話框如何應用。
字型對話框的作用是用來選擇字型。我們也經常能夠見到。MFC使用CFontDialog類封裝了字型對話框的所有操作。字型對話框也是一種模态對話框。
CFontDialog類的構造函數
我們先來了解CFontDialog類。它的常用構造函數原型如下:
CFontDialog(
LPLOGFONT lplfInitial = NULL,
DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
CDC* pdcPrinter = NULL,
CWnd* pParentWnd = NULL
);
參數說明:
lplfInitial:指向LOGFONT結構體資料的指針,可以通過它設定字型的一些特征。
dwFlags:指定選擇字型的一個或多個屬性,詳情可在MSDN中查閱。
pdcPrinter:指向一個列印裝置上下文的指針。
pParentWnd:指向字型對話框父視窗的指針。
上面的構造函數中第一個參數為LOGFONT指針,LOGFONT結構體中包含了字型的大部分特征,包括字型高度、寬度、方向、名稱等等。下面是此結構體的定義:
typedef struct tagLOGFONT {
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
擷取字型對話框中所選字型
我們在字型對話框中選擇了字型後,如何擷取標明的字型呢?我們可以通過CFontDialog類的成員變量m_cf間接獲得標明字型的CFont對象。m_cf是CHOOSEFONT類型的變量,CHOOSEFONT結構體定義如下:
typedef struct {
DWORD lStructSize;
HWND hwndOwner;
HDC hDC;
LPLOGFONT lpLogFont;
INT iPointSize;
DWORD Flags;
COLORREF rgbColors;
LPARAM lCustData;
LPCFHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
HINSTANCE hInstance;
LPTSTR lpszStyle;
WORD nFontType;
INT nSizeMin;
INT nSizeMax;
} CHOOSEFONT, *LPCHOOSEFONT;
CHOOSEFON結構體中有個成員lpLogFont,它是指向LOGFONT結構體變量的指針,就像上面所說,LOGFONT中包含了字型特征,例如,我們可以通過LOGFONT的lfFaceName得知字型名。
我們最終要獲得的是所選擇字型的CFont對象,有了字型的LOGFONT怎樣獲得對應的CFont對象呢?使用CFont類的成員函數CreateFontIndirect可以達到此目的。函數原型如下:
BOOL CreateFontIndirect(const LOGFONT* lpLogFont );
參數是LOGFONT指針類型,我們可以傳入CFontDialog類成員變量m_cf的lpLogFont成員,就可以得到所選字型的CFont對象了。
字型對話框應用執行個體
雞啄米給大家做一個字型對話框的執行個體。先介紹此執行個體要實作的功能,生成一個對話框,對話框中放置一個“字型選擇”按鈕和一個編輯框。點選“字型選擇”按鈕将彈出字型對話框。編輯框用于顯示所選字型名,并以標明的字型來顯示字型名字元串,例如,如果選擇了宋體,則在編輯框中以宋體顯示字元串“宋體”。
以下是建立此執行個體的步驟:
1.建立一個基于對話框的MFC工程,名字為“Example18”。
2.在自動生成的主對話框IDD_EXAMPLE18_DIALOG的模闆中,删除“TODO: Place dialog controls here.”靜态文本框,添加一個按鈕,ID設為IDC_FONT_BUTTON,Caption設為“字型選擇”,用于顯示字型對話框來選擇字型,再添加一個編輯框,ID設為IDC_FONT_EDIT,用來以所選字型顯示字型名字元串。
3.在Example18Dlg.h中為CExample18Dlg類添加private成員變量:CFont m_font;,用來儲存編輯框中選擇的字型。
4.為按鈕IDC_FONT_BUTTON添加點選消息的消息處理函數CExample18Dlg::OnBnClickedFontButton()。
5.修改消息處理函數CExample18Dlg::OnBnClickedFontButton()如下:
C++代碼
void CExample18Dlg::OnBnClickedFontButton()
{
// TODO: Add your control notification handler code here
CString strFontName; // 字型名稱
LOGFONT lf; // LOGFONT變量
// 将lf所有位元組清零
memset(&lf, 0, sizeof(LOGFONT));
// 将lf中的元素字型名設為“宋體”
_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("宋體"));
// 構造字型對話框,初始選擇字型名為“宋體”
CFontDialog fontDlg(&lf);
if (IDOK == fontDlg.DoModal()) // 顯示字型對話框
{
// 如果m_font已經關聯了一個字型資源對象,則釋放它
if (m_font.m_hObject)
{
m_font.DeleteObject();
}
// 使用標明字型的LOGFONT建立新的字型
m_font.CreateFontIndirect(fontDlg.m_cf.lpLogFont);
// 擷取編輯框IDC_FONT_EDIT的CWnd指針,并設定其字型
GetDlgItem(IDC_FONT_EDIT)->SetFont(&m_font);
// 如果使用者選擇了字型對話框的OK按鈕,則擷取被選擇字型的名稱并顯示到編輯框裡
strFontName = fontDlg.m_cf.lpLogFont->lfFaceName;
SetDlgItemText(IDC_FONT_EDIT, strFontName);
}
}
6.最後,編譯運作程式。顯示結果對話框,點選“字型選擇”按鈕,将彈出字型對話框,預設選擇為“宋體”,我們改而選擇“華文彩雲”字型點“确定”,編輯框中會像如下顯示:
到此,我們又學會了字型對話框的使用,對于以後在界面開發中控制顯示的字型很有幫助。有問題歡迎在雞啄米留言。
View Code
15.對話框:顔色對話框
雞啄米在上一節中為大家講解了字型對話框的使用方法,熟悉了字型對話框,本節繼續講另一種通用對話框--顔色對話框。
顔色對話框大家肯定也不陌生,我們可以打開它選擇需要的顔色,簡單說,它的作用就是用來選擇顔色。MFC中提供了CColorDialog類封裝了顔色對話框的所有操作,我們可以通過它顯示顔色對話框,并擷取顔色對話框中選擇的顔色。顔色對話框跟字型對話框一樣,也是一種模态對話框。
CColorDialog類的構造函數
CColorDialog(
COLORREF clrInit = 0,
DWORD dwFlags = 0,
CWnd* pParentWnd = NULL
);
參數說明:
clrInit:預設選擇顔色的顔色值,類型為COLORREF,實際上就是unsigned long類型。如果沒有設定它的值,則預設為RGB(0,0,0),即黑色。
注:RGB(r,g,b)是宏,可以計算顔色值。括号中的三個值分别為紅、綠、藍分量的值。
dwFlags:自定義顔色對話框功能和外觀的屬性值。詳情可在MSDN中查閱。
pParentWnd:顔色對話框的父視窗的指針。
擷取顔色對話框中所選顔色值
我們使用顔色對話框的最終目的還是要獲得在顔色對話框中選擇的顔色值。為此CColorDialog類的成員函數GetColor()能夠很好的實作我們的要求。GetColor()函數的原型為:
COLORREF GetColor( ) const;
它傳回所選顔色的COLORREF值。
如果我們想獲得R、G、B各分量的值呢?可以根據GetColor得到的COLORREF顔色值,通過使用GetRValue、GetGValue和GetBValue三個宏獲得。GetRValue的文法形式為:
BYTE GetRValue(DWORD rgb);
參數rgb就是COLORREF顔色值,傳回值即是R分量值。其他兩個宏的形式與之類似。例如,GetColor()函數傳回的COLORREF為10000,則R分量值就是GetRValue(10000)。
顔色對話框應用執行個體
雞啄米下面給大家做一個顔色對話框的小例子。此例要實作的功能簡單介紹下:生成一個對話框,對話框中放置一個“顔色選擇”按鈕,四個靜态文本框和四個編輯框。四個靜态文本框分别顯示Color:、R:、G:、B:,每個靜态文本框後面跟一個編輯框,分别用來顯示顔色對話框中選擇的顔色值和所選顔色值的紅色分量、綠色分量、藍色分量。
以下是執行個體建立的步驟:
1.建立一個基于對話框的MFC工程,名字為“Example19”。
2.在自動生成的主對話框IDD_EXAMPLE19_DIALOG的模闆中,删除“TODO: Place dialog controls here.”靜态文本框,添加一個按鈕,ID設為IDC_COLOR_BUTTON,Caption設為“顔色選擇”,用于顯示顔色對話框來選擇顔色。再添加四個靜态文本框,ID分别為IDC_COLOR_STATIC、IDC_R_STATIC、IDC_G_STATIC、IDC_B_STATIC,Caption分别設為“Color:”、“R:”、“G:”、“B:”,然後每個靜态文本框後添加一個編輯框,四個編輯框的ID分别為IDC_COLOR_EDIT、IDC_R_EDIT、IDC_G_EDIT、IDC_B_EDIT,分别用來顯示顔色對話框中選擇的顔色值和所選顔色值的紅色分量、綠色分量、藍色分量。
3.為按鈕IDC_COLOR_BUTTON添加點選消息的消息處理函數CExample19Dlg::OnBnClickedColorButton()。
4.修改消息處理函數CExample19Dlg::OnBnClickedColorButton()如下:
C++代碼
void CExample19Dlg::OnBnClickedColorButton()
{
// TODO: Add your control notification handler code here
COLORREF color = RGB(255, 0, 0); // 顔色對話框的初始顔色為紅色
CColorDialog colorDlg(color); // 構造顔色對話框,傳入初始顔色值
if (IDOK == colorDlg.DoModal()) // 顯示顔色對話框,并判斷是否點選了“确定”
{
color = colorDlg.GetColor(); // 擷取顔色對話框中選擇的顔色值
SetDlgItemInt(IDC_COLOR_EDIT, color); // 在Color編輯框中顯示所選顔色值
SetDlgItemInt(IDC_R_EDIT, GetRValue(color)); // 在R編輯框中顯示所選顔色的R分量值
SetDlgItemInt(IDC_G_EDIT, GetGValue(color)); // 在G編輯框中顯示所選顔色的G分量值
SetDlgItemInt(IDC_B_EDIT, GetBValue(color)); // 在B編輯框中顯示所選顔色的B分量值
}
}
5.最後編譯運作程式,在結果對話框中點選“顔色選擇”按鈕,彈出顔色對話框。初始狀态下,選擇框在紅色上,我們選另一種顔色,此時的顔色對話框如下:
點“确定”,主對話框上的四個編輯框中分别顯示了選擇的顔色值、R分量、G分量和B分量:
我們在實際開發中,可以用擷取到的顔色值來設定其他對象的顔色,使用還是很友善的。
關于顔色對話框就講到這裡了。其實各種對話框的使用都有很多相似之處,相信大家越來越熟悉了。最後還是歡迎大家繼續關注雞啄米的VS2010/MFC入門教程。
View Code
更多 http://www.jizhuomi.com/software/162.html
轉載于:https://www.cnblogs.com/blogpro/p/11456980.html