第五課 對話框
對話框是一種用戶界面,它的主要功能是輸出信息和接收用戶的輸入。對話框與控件是密不可分的,在每個對話框內一般都有一些控件,對話框依靠這些控件與用戶進行交互.一個典型的對話框例子是選擇了File-Open命令後彈出的文件對話框.對話框是一種複雜的用戶界面,本章的討論將圍繞對話框和基本控件進行,主要包括以下幾點:
5.1對話框和控件的基本概念
5.1.1對話框的基本概念 對話框(Dialog)實際上是一個窗口.在MFC中,對話框的功能被封裝在了CDialog類中,CDialog類是CWnd類的派生類.對話框分爲模態對話框和非模態對話框兩種.大部分讀者都會有這樣的經歷,當你通過File-Open命令打開一個文件對話框後,再用鼠標去選擇菜單將只會發出嘟嘟聲,這是因爲文件對話框是一個模態對話框.模態對話框壟斷了用戶的輸入,當一個模態對話框打開時,用戶只能與該對話框進行交互,而其它用戶界面對象收不到輸入信息.我們平時所遇到的大部分對話框都是模態對話框。非模態對話框的典型例子是Windows95提供的寫字板程序中的搜索對話框,搜索對話框不壟斷用戶的輸入,打開搜索對話框後,仍可與其它用戶界面對象進行交互,用戶可以一邊搜索,一邊修改文章,這樣就大大方便了使用.
本節主要介紹模態對話框,在第四節將介紹非模態對話框.
從MFC編程的角度來看,一個對話框由兩部分組成:
對話框模板資源.對話框模板用於指定對話框的控件及其分佈,Windows根據對話框模板來創建並顯示對話框.
對話框類.對話框類用來實現對話框的功能,由於對話框行使的功能各不相同,因此一般需要從CDialog類派生一個新類,以完成特定的功能.
相應地,對話框的設計包括對話框模板的設計和對話框類的設計兩個主要方面.
與對話框有關的消息主要包括WM_INITDIALOG消息和控件通知消息。在對話框創建時,會收到WM_INITDIALOG消息,對話框對該消息的處理函數是OnInitDialog 。
OnInitDialog的主要用處是初始化對話框。對話框的控件會向對話框發送控件通知消息,以表明控件的狀態發生了變化。
5.1.2控件的基本概念
圖5.1對話框中的控件
控件(Control)是獨立的小部件,在對話框與用戶的交互過程中,控件擔任着主要角色.控件的種類較多,圖5.1顯示了對話框中的一些基本的控件.MFC的控件類封裝了控件的功能,表5.1介紹了一些常用的控件及其對應的控件類.
表5.1
控件 | 功能 | 對應控件類 |
靜態正文(Static Text) | 顯示正文,一般不能接受輸入信息。 | CStatic |
圖片(Picture) | 顯式位圖、圖標、方框和圖元文件,一般不能接受輸入信息. | CStatic |
編輯框(Edit Box) | 輸入並編輯正文,支持單行和多行編輯. | CEdit |
命令按鈕(Pushbutton) | 響應用戶的輸入,觸發相應的事件. | CButton |
檢查框(Check Box) | 用作選擇標記,可以有選中、不選中和不確定三種狀態。 | CButton |
單選按鈕(Radio Button) | 用來從兩個或多個選項中選中一項. | CButton |
組框(Group Box) | 顯示正文和方框,主要用來將相關的一些控件聚成一組. | CButton |
列表框(List Box) | 顯示一個列表,用戶可以從該列表中選擇一項或多項. | CListBox |
組合框(Combo Box) | 是一個編輯框和一個列表框的組合.分爲簡易式、下拉式和下拉列表式. | CComboBox |
滾動條(Scroll Bar) | 主要用來從一個預定義範圍值中迅速而有效地選取一個整數值. | CScrollBar |
控件實際上都是窗口,所有的控件類都是CWnd類的派生類.控件通常是作爲對話框的子窗口而創建的,控件也可以出現在視窗口,工具條和狀態條中.
5.2對話框模板的設計
利用Developer Studio提供的可視化設計工具,用戶可以方便地設計對話框模板.請讀者按前面章節介紹的方法利用AppWizard建立一個名爲Register的MFC應用程序,並在進入MFC AppWizard對話框後按下面幾步操作:
- 在第1步中選中Single document以建立一個單文檔應用程序.
- 在第4步中使Docking toolbar項不選中,這樣AppWizard就不會創建工具條.
-
在第6步中先選擇CRegisterView,然後在Base class欄中選擇CEditView,這樣CRegisterView將是CEditView的繼承類,從而使視圖具有了編輯功能.
編譯並運行Register,讀者會發現Register居然是個編輯器,它可以打開、編輯和保存文本文件. 當然,Register的目的不僅僅是個編輯器。假設要對某一地區的就業情況進行調查,我們希望Register程序能夠登錄就業情況數據並將數據存儲起來.
要登錄數據,用對話框是再合適不過了。一個典型的就業情況登錄對話框如圖5.1所示,本節的任務就是設計如圖5.1的中文對話框模板.
切換至資源視圖,選擇Insert-Resource命令,並在Insert Resource對話框中雙擊Dialog項。完成後在資源視圖中會出現一個名爲IDD_DIALOG1的新的對話框模板資源。雙擊IDD_DIALOG1,則會打開該對話框模板的編輯窗口,如圖5.2所示。缺省的對話框模板有OK和Cancel兩個按鈕,在窗口的旁邊有一個控件面板,在控件面板上用鼠標選擇一個控件,然後在對話框中點擊,則相應的控件就被放置到了對話框模板中。圖5.3顯示了控件面板上的按鈕所代表的控件。讀者不用記憶圖5.3的內容,如果不能確定控件的類型,可將鼠標在某個控件按鈕上停留片刻,則會顯示一個工具提示,指出該按鈕所代表控件的名稱。
圖5.2 缺省的對話框模板
圖5.3 控件面板
提示:若讀者看不到控件面板,請在Developer Studio的工具條的空白處單擊鼠標右鍵,並在隨之彈出的菜單中選中Controls。 |
讀者可以在對話框模板中隨意加幾個控件試試看。當用鼠標選擇對話框或控件時,會出現一個圍繞它的虛框,拖動虛框的邊界可以改變對話框或控件的大小,在Developer Studio的狀態條中會顯示出所選對象的座標和尺寸。控件可以被拖動,也可以按箭頭鍵來移動選中的控件。在拖動控件時若按住Ctrl鍵,則控件會被複制。
用戶可以一次選擇多個控件,選擇的方法有兩個:1。 在對話框的空白處拖動鼠標,則拖動出來的虛線框內的控件將被選中。2。在選擇控件時按住Ctrl鍵,則可以多重選擇。
選中控件或對話框後按回車鍵,則會彈出一個屬性對話框,屬性對話框用來設置控件或對話框的各種屬性。屬性對話框是標籤式對話框,第一頁是常規屬性(General)。一個典型的控件屬性對話框如圖5.4所示.如果對屬性對話框中的選項的意思不明白,可以按F1鍵獲得幫助.
圖5.4 控件屬性對話框
在控件屬性對話框的常規屬性中,有一些控件共同的屬性:
ID屬性。用於指定控件的標識符,Windows依靠ID來區分不同的控件。
Caption(標題)屬性。靜態正文、組框、按鈕、檢查框、單選按鈕等控件可以顯示標題,用來對控件進行文字說明。控件標題中的字符&使緊跟其後的字符有下劃線,按Alt+下劃線將啓動該控件。若控件是一個單選按鈕,則Alt+下劃線字符將選擇該按鈕;若是檢查框,則相當於對該檢查框按空格鍵;若是按鈕,則將激活按鈕命令;若控件是一個靜態正文,則將激活按tab順序緊隨其後的下一個控件。
Visible屬性。用來指定控件是否是可見的。
- Disable屬性。使控件允許或禁止,一個禁止的控件呈灰色顯示,不能接收任何輸入。
- Tabstop屬性。用戶可以按Tab鍵移動到具有Tabstop屬性的控件上。Tab移動的順序可以由用戶指定。按Ctrl+D則Tab順序會顯示出來,如圖5.5,用戶可以用鼠標來重新指定Tab順序。缺省的Tab順序是控件的創建次序。
- Group屬性。用來指定一組控件,用戶可以用箭頭鍵在該組控件內移動。在同一組內的單選按鈕具有互斥的特性,即在這些單選按鈕中只能有一個是選中的。如果一個控件具有Group屬性,則這個控件以及按Tab順序緊隨其後的所有控件都屬於一組的,直到遇到另一個有Group屬性的控件爲止。
現在就開始進行對話框模板的設計。首先,用鼠標選中對話框,按回車鍵,在彈出的屬性對話框中將ID改爲IDD_REGISTER並指定對話框的標題爲“登錄數據”。需要注意的是,由於要在對話框中顯示漢字,因此必須設定正確的語種和字體。請讀者在工作區資源視圖的Dialog類型中單擊鼠標選中IDD_REGISTER項,然後按Alt+Enter鍵,並在彈出的屬性對話框中的Language欄中選擇Chinese(P.R.C.)。接着,打開模板的屬性對話框,單擊Font...按鈕,並選擇“宋體”。
接着,請將對話框模板上的所有控件刪除,刪除的辦法是選擇控件後按Del鍵。爲了容納所有需要的控件,需將對話框的尺寸擴大到280×180。然後,請讀者按圖5.1和表5.2來設計對話框模板。
提示:對話框的尺寸單位不是象素,而是與字體的大小有關。X方向上一個單位等於字符平均寬度的1/4,Y方向上一個單位等於字符平均高度的1/8。這樣,隨着字體的改變,對話框單位也會改變,對話框本身的總體比例保持不變。 |
表5.2
控件類型 | ID | 標題(Caption) | 其它屬性 |
組框(個人情況) | 缺省 | 個人情況 | 缺省 |
組框(單位情況) | 缺省 | 單位情況 | 缺省 |
靜態正文(姓名) | 缺省 | 姓名 | 缺省 |
編輯框(姓名) | IDC_NAME | 缺省 | |
檢查框(婚否) | IDC_MARRIED | 婚否 | 缺省 |
靜態正文(年齡) | 缺省 | 年齡 | 缺省 |
編輯框(年齡) | IDC_AGE | 缺省 | |
組框(性別) | 缺省 | 性別 | 缺省 |
單選按鈕(男) | IDC_SEX | 男 | Group、Tabstop |
單選按鈕(女) | 缺省 | 女 | 缺省 |
組框(就業狀況) | 缺省 | 就業狀況 | 缺省 |
單選按鈕(在職) | IDC_WORK | 在職 | Group、Tabstop |
單選按鈕(下崗) | IDC_WORK1 | 下崗 | 缺省 |
靜態正文(工作單位) | 缺省 | 工作單位 | 缺省 |
編輯框(工作單位) | IDC_UNIT | 缺省 | |
靜態正文(單位性質) | 缺省 | 單位性質 | 缺省 |
組合框(單位性質) | IDC_KIND | Drop List、不排序(不選中Sort風格)、初始化列表項(見下文說明) | |
靜態正文(工資收入) | 缺省 | 工資收入 | 缺省 |
列表框(工資收入) | IDC_INCOME | 不排序(不選中Sort) | |
按鈕(確定) | IDOK | 確定(&Y) | 缺省 |
按鈕(取消) | IDCANCEL | 取消(&C) | 缺省 |
請注意組合框IDC_KIND的Drop List屬性,Drop List屬性是在屬性對話框的Styles(風格)頁的Type欄中選擇的,這使得IDC_KIND成爲一個下拉列表式組合框。組合框有簡易式(Simple)、下拉式(Dropdown)和下拉列表式(Drop List)三種。簡易式組合框包含一個編輯框和一個總是顯示的列表框。下拉式組合框同簡易式組合框的區別在於僅當單擊下滾箭頭時纔出現列表框。下拉列表式組合框也有一個下拉的列表框,但它的編輯框是隻讀的,不能輸入字符。組合框IDC_KIND不要自動排序,因此需在Styles頁中使Sort項不被選中。
組合框的列表項可以在設計模板時初始化,而列表框的初始化只能在程序中進行。請讀者在組合框IDC_KIND的屬性對話框的General頁中輸入以下幾個列表項,以作爲單位性質的選項。輸入時要注意,換行時不要按回車鍵,而應按Ctrl+回車鍵。
國有企事業
集體企業
私有企業
中外合資
外商獨資
組合框控件的一個與衆不同之處是它有兩個尺寸,一個是下拉前的尺寸,一個是下拉後的尺寸。當用鼠標點擊組合框上的箭頭後,可設定下拉後的尺寸。
控件最好都放在對話框模板的藍色虛框內,控件之間的距離不要太近,否則有可能造成不正確的顯示。
安置好控件之後,下一步的任務是指定Tab順序。按Ctrl+D鍵後,會顯示當前的Tab順序,通過用鼠標點擊控件可以設定新的Tab順序,如果想放棄本次修改,在對話框的空白處點擊一下即可。請讀者按圖5.5安排Tab順序。
圖5.5 對話框的Tab順序
最後,需要測試一下對話框。按Ctrl+T,則會彈出一個當前模板的測試對話框,這個對話框的外觀和基本行爲與程序中將要彈出的對話框一樣。這樣,讀者不用編譯運行程序,通過測試對話框就可以評估對話框是否合乎要求。如果發現了錯誤或不滿意的地方,可按ESC鍵退出測試對話框並重新修改對話框模板。
至此,對話框模板的設計就完成了。
5.3 對話框類的設計
完成對話框模板的設計後,就需要設計一個對話框類以實現對話框的功能。設計對話框類主要包括下面幾步:創建對話框類。該類應從CDialog類派生。
爲對話框類加入與控件相對應的成員變量。
爲對話框進行初始化工作。
增加對控件通知消息的處理
5.3.1對話框類的創建
利用ClassWizard,程序員可以十分方便的創建MFC窗口類的派生類,對話框類也不例外。請讀者按以下幾步操作:
打開IDD_REGISTER對話框模板,然後按Ctrl+W進入ClassWizard。
進入ClassWizard後,ClassWizard發現IDD_REGISTER是一個新的對話框模板,於是它會詢問是否要爲IDD_REGISTER創建一個對話框類。按OK鍵確認。
如圖5.6在Create New Class對話框中,在Name欄中輸入CRegisterDialog,在Base class欄中選擇CDialog,在Dialog ID欄中選擇IDD_REGISTER。按Create按鈕後,對話框類CRegisterDialog即被創建。
圖5.6 Create New Class對話框
ClassWizard自動使類CRegesterDialog與IDD_REGISTER模板聯繫起來。
提示:只要想創建的類是某一MFC窗口類的派生類,一般都可以利用ClassWizard來自動完成創建。創建的一般方法是:打開ClassWizard,選擇Add Class->New,然後在Create New Class對話框中輸入新類的類名,選擇其MFC基類,如果是對話框類,則還要選擇對話框的ID。 |
5.3.2爲對話框類加入成員變量
對話框的主要功能是輸出和輸入數據,例子中的登錄數據對話框的任務就是輸入數據。對話框需要有一組成員變量來存儲數據。在對話框中,控件用來表示或輸入數據,因此,存儲數據的成員變量應該與控件相對應。
與控件對應的成員變量即可以是一個數據,也可以是一個控件對象,這將由具體需要來確定。例如,可以爲一個編輯框控件指定一個數據變量,這樣就可以很方便地取得或設置編輯框控件所代表的數據,如果想對編輯框控件進行控制,則應該爲編輯框指定一個CEdit對象,通過CEdit對象,程序員可以控制控件的行爲。需要指出的是,不同類的控件對應的數據變量的類型往往是不一樣的,而且一個控件對應的數據變量的類型也可能有多種。表5.3說明了控件的數據變量的類型。
表5.3
控件 | 數據變量的類型 |
編輯框 | CString, int, UINT, long, DWORD, float, double, short, BOOL, COleDateTime, COleCurrency |
普通檢查框 | BOOL(真表示被選中,假表示未選中) |
三態檢查框 | int(0表示未選中,1表示選中,2表示不確定狀態) |
單選按鈕(組中的第一個按鈕) | int(0表示選擇了組中第一個單選按鈕,1表示選擇了第二個...,-1表示沒有一個被選中) |
不排序的列表框 | CString(爲空則表示沒有一個列表項被選中),
int(0表示選擇了第一項,1表示選了第二項,-1表示沒有一項被選中) |
下拉式組合框 | CString, int(含義同上) |
其它列表框和組合框 | CString(含義同上) |
利用ClassWizard可以很方便地爲對話框類CRegisterDialog加入成員變量。請讀者按下列步驟操作。
按Ctrl+W進入ClassWizard。
選擇ClassWizard上部的Member Variables標籤,然後在Class name欄中選擇CRegisterDialog。這時,在下面的變量列表中會出現對話框控件的ID,如圖5.7所示。
圖5.7 ClassWizard對話框
雙擊列表中的ID_AGE會彈出Add Member Variable對話框,如圖5.8所示。在Member variable name欄中輸入m_nAge,在Category欄中選擇Value,在Variable type欄中選擇UINT。按OK按鈕後,數據變量m_nAge就會被加入到變量列表中。
圖5.8 Add Member Variable對話框
仿照第3步和表5.4,爲各個控件加入相應的成員變量。
將m_nAge的值限制在16到65之間。方法是先選擇m_nAge,然後在ClassWizard對話框的左下角輸入最大和最小值。m_nAge代表年齡,這裏規定被調查的人的年齡應在16歲以上,64歲以下。有了這個限制後,對話框會對輸入的年齡值進行有效性檢查,若輸入的值不在限制範圍內,則對話框會提示用戶輸入有效的值。
表5.4
控件ID |
變量類型 |
變量名 |
IDC_AGE |
UINT |
m_nAge |
IDC_INCOME |
CString |
m_strIncome |
IDC_INCOME |
CListBox |
m_ctrlIncome |
IDC_KIND |
CString |
m_strKind |
IDC_MARRIED |
BOOL |
m_bMarried |
IDC_NAME |
CString |
m_strName |
IDC_SEX |
int |
m_nSex |
IDC_UNIT |
CString |
m_strUnit |
IDC_WORK |
int |
m_nWork |
讀者會注意到控件IDC_INCOME居然有兩個變量,一個是CString型的,一個是CListBox型的,這是完全合法的,不會引起任何衝突。之所以要加入CListBox型的變量,是因爲列表框的初始化要通過CListBox對象進行。
提示:在ClassWizard中可分別爲一個控件指定一個數據變量和一個控件對象,這樣做的好處是即能方便地獲得數據,又能方便地控制控件。 |
5.3.3對話框的初始化
對話框的初始化工作一般在構造函數和OnInitDialog函數中完成。在構造函數中的初始化主要是針對對話框的數據成員。讀者可以找到CRegisterDialog的構造函數,如清單5.1所示。
清單5.1 CRegisterDialog的構造函數
CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/)
: CDialog(CRegisterDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CRegisterDialog)
m_nAge = 0;
m_strIncome = _T("");
m_strKind = _T("");
m_bMarried = FALSE;
m_strName = _T("");
m_nSex = -1;
m_strUnit = _T("");
m_nWork = -1;
//}}AFX_DATA_INIT
}
可以看出,對數據成員的初始化是由ClassWizard自動完成的。若讀者對初值的含義還不太清楚,請參看表5.3。
在對話框創建時,會收到WM_INITDIALOG消息,對話框對該消息的處理函數是OnInitDialog。調用OnInitDialog時,對話框已初步創建,對話框的窗口句柄也已有效,但對話框還未被顯示出來。因此,可以在OnInitDialog中做一些影響對話框外觀的初始化工作。OnInitDialog對對話框的作用與OnCreate對CMainFrame的作用類似。
提示:MFC窗口的初始化工作一般在OnCreate成員函數中進行,但對話框的初始化工作最好在OnInitDialog中進行。 |
OnInitDialog是WM_INITDIALOG消息的處理函數,所以要用ClassWizard爲RegisteritDialog類增加一個WM_INITDIALOG消息的處理函數,增加的方法是進入ClassWizard後,先選中MessageMaps標籤,然後在Class name中選擇CRegisterDialog,在Object IDs欄中選擇CRegisterDialog,在Messages欄中找到WM_INITDIALOG並雙擊之,最後按OK按鈕退出ClassWizard。
請讀者按清單5.2修改OnInitDialog函數。
清單5.2 OnInitDialog函數
BOOL CRegisterDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
m_ctrlIncome.AddString("500元以下");
m_ctrlIncome.AddString("500-1000元");
m_ctrlIncome.AddString("1000-2000元");
m_ctrlIncome.AddString("2000元以上");
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
CRegisterDialog::OnInitDialog()的主要任務是對工資收入列表框的列表項進行初始化。調用CListBox::AddString可將指定的字符串加入到列表框中。由於該列表是不自動排序的,因此AddString將表項加在列表框的末尾。
5.3.4對話框的數據交換機制
對話框的數據成員變量存儲了與控件相對應的數據。數據變量需要和控件交換數據,以完成輸入或輸出功能。例如,一個編輯框即可以用來輸入,也可以用來輸出:用作輸入時,用戶在其中輸入了字符後,對應的數據成員應該更新;用作輸出時,應及時刷新編輯框的內容以反映相應數據成員的變化。對話框需要一種機制來實現這種數據交換功能,這對對話框來說是至關重要的。
MFC提供了類CDataExchange來實現對話框類與控件之間的數據交換(DDX),該類還提供了數據有效機制(DDV)。數據交換和數據有效機制適用於編輯框、檢查框、單選按鈕、列表框和組合框。
數據交換的工作由CDialog::DoDataExchange來完成。讀者可以找到CRegisterDialog::DoDataExchange函數,如清單5.3所示。
清單5.3 DoDataExchange函數
void CRegisterDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CRegisterDialog)
DDX_Control(pDX, IDC_INCOME, m_ctrlIncome);
DDX_LBString(pDX, IDC_INCOME, m_strIncome);
DDX_CBString(pDX, IDC_KIND, m_strKind);
DDX_Check(pDX, IDC_MARRIED, m_bMarried);
DDX_Text(pDX, IDC_NAME, m_strName);
DDX_Radio(pDX, IDC_SEX, m_nSex);
DDX_Text(pDX, IDC_UNIT, m_strUnit);
DDX_Radio(pDX, IDC_WORK, m_nWork);
DDX_Text(pDX, IDC_AGE, m_nAge);
DDV_MinMaxUInt(pDX, m_nAge, 16, 65);
//}}AFX_DATA_MAP
}
讀者可以看出,該函數中的代碼是由ClassWizard自動加入的。DoDataExchange只有一個參數,即一個CDataExchange對象的指針pDX。在該函數中調用了DDX函數來完成數據交換,調用DDV函數來進行數據有效檢查。
當程序需要交換數據時,不要直接調用DoDataExchange函數,而應該調用CWnd::UpdateData。UpdataData函數內部調用了DoDataExchange。該函數只有一個布爾型參數,它決定了數據傳送的方向。調用UpdateData(TRUE)將數據從對話框的控件中傳送到對應的數據成員中,調用UpdateData(FALSE)則將數據從數據成員中傳送給對應的控件。
在缺省的CDialog::OnInitDialog中調用了UpdateData(FALSE),這樣,在對話框創建時,數據成員的初值就會反映到相應的控件上。若用戶是按了OK(確定)按鈕退出對話框,則對話框認爲輸入有效,就會調用UpdataData(TRUE)將控件中的數據傳給數據成員。圖5.9描繪了對話框的這種數據交換機制。
圖5.9 對話框的數據交換
5.3.5對話框的運行機制
在程序中運行模態對話框有兩個步驟:
在堆棧上以變量的形式構建一個對話框對象。
調用CDialog::DoModal ( )。
DoModal負責對模態話框的創建和撤銷。在創建對話框時,DoModal的任務包括載入對話框模板資源、調用OnInitDialog初始化對話框和將對話框顯示在屏幕上。完成對話框的創建後,DoModal啓動一個消息循環,以響應用戶的輸入。由於該消息循環截獲了幾乎所有的輸入消息,使主消息循環收不到對對話框的輸入,致使用戶只能與模態對話框進行交互,而其它用戶界面對象收不到輸入信息。
若用戶在對話框內點擊了ID爲IDOK的按鈕(通常該按鈕的標題是“確定”或“OK”),或按了回車鍵,則CDialog::OnOK將被調用。OnOK首先調用UpdateData(TRUE)將數據從控件傳給對話框成員變量,然後調用CDialog::EndDialog關閉對話框。關閉對話框後,DoModal會返回值IDOK。
若用戶點擊了ID爲IDCANCEL的按鈕(通常其標題爲“取消”或“Cancel”),或按了ESC鍵,則會導致CDialog::OnCancel的調用。該函數只調用CDialog::EndDialog關閉對話框。關閉對話框後,DoModal會返回值IDCANCEL。
程序根據DoModal的返回值是IDOK還是IDCANCEL就可以判斷出用戶是確定還是取消了對對話框的操作。
在弄清了對話框的運行機制後,下面讓我們來就可以實現Register程序登錄數據的功能。
首先,將Register工程的工作區切換至資源視圖。打開IDR_MAINFRAME菜單資源,在Edit菜單的底端加入一個名爲“登錄數據”的新菜單項,並令其ID爲ID_EDIT_REGISTER(最好在該項之前加一條分隔線,以便和前面的菜單項分開)。注意不要忘了把菜單資源的語種設置成中文,否則菜單中將顯示不出中文來。設置的方法是先在工作區資源視圖中選擇IDR_MAINFRAME菜單資源,然後按Alt+Enter鍵,並在彈出的屬性對話框中的Language欄中選擇Chinese(P.R.C.)。
接着,用ClassWizard爲該菜單命令創建命令處理函數CRegisterView::OnEditRegister。注意,OnEditRegister是類CRegisterView的成員函數,這是因爲CRegisterView要負責打開和關閉登錄數據對話框,並將從對話框中輸入的數據在視圖中輸出。
然後,請讀者在RegisterView.cpp文件的開頭加入下面一行
#include "RegisterDialog.h"
最後,按清單5.4修改程序。
清單5.4 OnEditRegister函數
void CRegisterView::OnEditRegister()
{
// TODO: Add your command handler code here
CRegisterDialog dlg;
if(dlg.DoModal()==IDOK)
{
CString str;
//獲取編輯正文
GetWindowText(str);
//換行
str+="/r/n";
str+="姓名:";
str+=dlg.m_strName;
str+="/r/n";
str+="性別:";
str+=dlg.m_nSex?"女":"男";
str+="/r/n";
str+="年齡:";
CString str1;
//將數據格式輸出到字符串對象中
str1.Format("%d",dlg.m_nAge);
str+=str1;
str+="/r/n";
str+="婚否:";
str+=dlg.m_bMarried?"已婚":"未婚";
str+="/r/n";
str+="就業狀況:";
str+=dlg.m_nWork?"下崗":"在職";
str+="/r/n";
str+="工作單位:";
str+=dlg.m_strUnit;
str+="/r/n";
str+="單位性質:";
str+=dlg.m_strKind;
str+="/r/n";
str+="工資收入:";
str+=dlg.m_strIncome;
str+="/r/n";
//更新編輯視圖中的正文
SetWindowText(str);
}
}
在OnEditRegister函數中,首先構建了一個CRegisterDialog對象,然後調用CDialog::DoModal來實現模態對話框。如果DoModal返回IDOK,則說明用戶確認了登錄數據的操作,程序需要將錄入的數據在編輯視圖中輸出。程序用一個CString對象來作爲編輯正文的緩衝區,CString是一個功能強大的字符串類,它的最大特點在於可以存儲動態改變大小的字符串,這樣,用戶不必擔心字符串的長度超過緩衝區的大小, 使用十分方便。
在輸出數據時,程序首先調用CWnd::GetWindowText獲得編輯正文,這是一個多行的編輯正文。CWnd::GetWindowText用來獲取窗口的標題,若該窗口是一個控件,則獲取的是控件內的正文。CRegisterView是CEditView的繼承類,而CEditView實際上包含了一個編輯控件,因此在CRegisterView中調用GetWindowText獲得的是編輯正文。
然後,程序在該編輯正文的末尾加入新的數據。在程序中大量使用了CString類的重載操作符“+=”,該操作符的功能是將操作符右側字符串添加到操作符左側的字符串的末尾。注意在多行編輯控件中每行末尾都有一對回車和換行符。在程序中還調用了CString::Format來將數據格式化輸出到字符串中,Format的功能與sprintf類似。最後,調用CWnd::SetWindowText來更新編輯視圖中的正文。
編譯並運行Register,打開登錄數據對話框,輸入一些數據試試。現在,Register已經是一個簡易的數據庫應用程序了,它可以將與就業情況有關的數據輸出到一個編輯視圖中。用戶可以編輯視圖中的正文,並將結果保存在文本文件中。
5.3.6處理控件通知消息
雖然Register已經可以登錄數據了,但讀者會很快會發現該程序還有一些不完善的地方:
登錄完一個人的數據後,對話框就關閉了,若用戶有很多人的數據要輸入,則必須頻繁地打開對話框,很不方便。在登錄數據時,應該使對話框一直處於打開狀態。
登錄數據對話框分個人情況和單位情況兩組,若被調查人是下崗職工,則不必輸入單位情況。程序應該能夠對用戶的輸入及時地作出反應,即當用戶選擇了“下崗”單選按鈕時,應使單位情況組中的控件禁止。一個禁止的控件呈灰色示,並且不能接收用戶的輸入。
要解決上述問題,就必須對控件通知消息進行處理。當控件的狀態因爲輸入等原因而發生變化時,控件會向其父窗口發出控件通知消息。例如,如果用戶在登錄數據對話框中的某一按鈕(包括普通按鈕、檢查框和單選按鈕)上單擊鼠標,則該按鈕會向對話框發送BN_CLICKED消息。對話框根據按鈕的ID激活相應的BN_CLICKED消息處理函數,以對單擊按鈕這一事件作出反應。通過對按鈕的BN_CLICKED消息的處理,我們可以使登錄數據對話框的功能達到上述要求。
首先,讓我們來解決第一個問題。我們的設想是修改原來的“確定(Y)”按鈕,使得當用戶點擊該按鈕後,將數據輸出到視圖中,並且對話框不關閉,以便用戶輸入下一個數據。請讀者按下面幾步進行修改。
修改登錄數據對話框的“確定(Y)”按鈕,使該按鈕的標題變爲“添加(&A)”,ID變爲IDC_ADD。這樣,當用戶點擊該按鈕後,對話框會收到BN_CLICKED消息。由於這個BN_CLICKED消息對應的按鈕ID不是IDOK,不會觸發OnOK消息處理函數,因此不會關閉對話框。
爲按鈕IDC_ADD的BN_CLICKED消息創建消息處理函數。創建的方法是進入ClassWizard後,選Message Maps頁並在Class name欄中選擇CRegisterDialog,然後在Object IDs欄中選擇IDC_ADD,在Messages欄中雙擊BN_CLICKED。在確認使用缺省的消息處理函數名OnAdd後,按回車鍵退出ClassWizard。
OnAdd要向編輯視圖輸出正文,就必須獲得一個指向CRegisterView對象的指針以訪問該對象。爲此,請在CRegisterDialog類的說明中加入下面一行
Cwnd* m_pParent;
注意不要加在AFX註釋對中。爲實現IDC_ADD按鈕的功能,請按清單5.5和清單5.6修改程序。主要的改動是把原來由CRegiserView::OnEditRegister完成的在視圖中輸出數據的任務交給CRegisterDialog::OnAdd來完成。
清單5.5 CRegisterView::OnEditRegister函數
void CRegisterView::OnEditRegister()
{
// TODO: Add your command handler code here
CRegisterDialog dlg(this);
dlg.DoModal();
}
清單5.6 CRegisterDialog類的部分源代碼
CRegisterDialog::CRegisterDialog(CWnd* pParent /*=NULL*/)
: CDialog(CRegisterDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CRegisterDialog)
. . . . . .
//}}AFX_DATA_INIT
m_pParent=pParent;
}
void CRegisterDialog::OnAdd()
{
// TODO: Add your control notification handler code here
//更新數據
UpdateData(TRUE);
//檢查數據是否有效
if(m_strName=="" || m_nSex<0 || m_nWork<0 || m_strUnit==""
|| m_strKind=="" || m_strIncome=="")
{
AfxMessageBox("請輸入有效數據");
return;
}
CString str;
//獲取編輯正文
m_pParent->GetWindowText(str);
//換行
str+="/r/n";
str+="姓名:";
str+=m_strName;
str+="/r/n";
str+="性別:";
str+=m_nSex?"女":"男";
str+="/r/n";
str+="年齡:";
CString str1;
//將數據格式輸出到字符串對象中
str1.Format("%d",m_nAge);
str+=str1;
str+="/r/n";
str+="婚否:";
str+=m_bMarried?"已婚":"未婚";
str+="/r/n";
str+="就業狀況:";
str+=m_nWork?"下崗":"在職";
str+="/r/n";
str+="工作單位:";
str+=m_strUnit;
str+="/r/n";
str+="單位性質:";
str+=m_strKind;
str+="/r/n";
str+="工資收入:";
str+=m_strIncome;
str+="/r/n";
//更新編輯視圖中的正文
m_pParent->SetWindowText(str);
}
CRegisterDialog的構造函數有一個參數pParent,該參數是一個指向CWnd對象的指針,用於指定對話框的父窗口或擁有者窗口。在CRegisterView:: OnEditRegister函數中,在構建CRegisterDialog對象時指定了this參數,this指針指向CRegisterView對象本身。這樣在調用CRegisterDialog的構造函數時,this指針值被賦給了CRegisterDialog的成員m_pParent。OnAdd函數可利用m_pParent來訪問對話框的擁有者即CRegisterView對象。
提示:術語父窗口(Parent)是相對於子窗口而言。若某一個窗口擁有一個子窗口(Child),則該窗口就被稱爲子窗口的父窗口。子窗口就是具有WS_CHILD風格的窗口,子窗口依賴於父窗口且完全被限制在父窗口內部。擁有者窗口(owner)相對於被擁有者窗口而言。若某一個窗口擁有一個非子窗口,則該窗口被稱爲擁有者窗口。被擁有窗口(owned)不具有WS_CHILD風格,可在屏幕上任意移動。 |
當用戶用鼠標點擊IDC_ADD按鈕時,該按鈕的BN_CLICKED消息處理函數CRegisterDialog::OnAdd將被調用。在OnAdd中,首先調用了UpdateData(TRUE)以把數據從控件傳給對話框的數據成員變量。然後,程序要對數據的有效性進行檢查,如果輸入的數據不完全有效,則會顯示一個消息對話框,提示用戶輸入有效的數據。接下來進行的工作是在視圖中輸出數據,這部分代碼與清單5.4類似,讀者應該比較熟悉了。
完成上述工作後,登錄數據對話框就變得較爲實用了。打開對話框後,用戶可以方便地輸入多人的數據,只有按了取消按鈕後,對話框纔會關閉。
接下來讓我們來解決第二個問題。解決該問題的關鍵在於當用戶點擊“在職”或“下崗”單選按鈕時,程序要對收到的BN_CLICKED消息作出響應。有些讀者可能會想到爲兩個單選按鈕分別創建BN_CLICKED消息處理函數,這在只有兩個單選按鈕的情況下是可以的,但如果一組內有多個單選按鈕,則分別創建消息處理函數就比較麻煩了。利用MFC提供的消息映射宏ON_CONTROL_RANGE可以避免這種麻煩,該映射宏把多個ID連續的控件發出的消息映射到同一個處理函數上。這樣,我們只要編寫一個消息處理函數,就可以對“在職”和“下崗”兩個單選按鈕的BN_CLICKED消息作出響應。ClassWizard不支持ON_CONTROL_RANGE宏,所以我們必須手工創建單選按鈕的消息映射和消息處理函數。
首先,在CRegisterDialog類的頭文件中加入消息處理函數的聲明,該函數名爲OnWorkClicked,如清單5.7所示。
清單5.7 BN_CLICKED消息處理函數OnWorkClicked的聲明
. . . . . .
protected:
void OnWorkClicked(UINT nCmdID);
// Generated message map functions
//{{AFX_MSG(CRegisterDialog)
virtual BOOL OnInitDialog();
afx_msg void OnAdd();
//}}AFX_MSG
. . . . . .
然後,在CRegisterDialog類的消息映射中加入ON_CONTROL_RANGE映射,如清單5.8所示。ON_CONTROL_RANGE映射的形式是ON_CONTROL_RANGE
清單5.8 在CRegisterDialog類的消息映射中加入ON_CONTROL_RANGE映射
BEGIN_MESSAGE_MAP(CRegisterDialog, CDialog)
//{{AFX_MSG_MAP(CRegisterDialog)
ON_BN_CLICKED(IDC_ADD, OnAdd)
//}}AFX_MSG_MAP
ON_CONTROL_RANGE(BN_CLICKED, IDC_WORK, IDC_WORK1, OnWorkClicked)
END_MESSAGE_MAP()
ON_CONTROL_RANGE消息映射宏的第一個參數是控件消息碼,第二和第三個參數分別指明瞭一組連續的控件ID中的頭一個和最後一個ID,最後一個參數是消息處理函數名。如果讀者是按表5.2的順序放置控件的則IDC_WORK和IDC_WORK1應該是連續的。這樣,無論用戶是在IDC_WORK還是在IDC_WORK1單選按鈕上單擊,都會調用OnWorkClicked消息處理函數。
提示:如果不能確定兩個ID是否是連續的,請用File->Open命令打開resource.h文件,在該文件中有對控件ID值的定義。如果發現兩個ID是不連續的,讀者可以改變對ID的定義值使之連續,但要注意改動後的值不要與別的ID值發生衝突。
最後,在CRegisterDialog類所在CPP文件的最後插入消息處理函數CRegisterDialog::OnWorkClicked,如清單5.9所示。
清單5.9 CRegisterDialog::OnWorkClicked消息處理函數
void CRegisterDialog::OnWorkClicked(UINT nCmdID)
{
//判斷“在職”單選按鈕是否被選中
if(IsDlgButtonChecked(IDC_WORK))
{
//使控件允許
GetDlgItem(IDC_UNIT)->EnableWindow(TRUE);
GetDlgItem(IDC_KIND)->EnableWindow(TRUE);
GetDlgItem(IDC_INCOME)->EnableWindow(TRUE);
}
else
{
//清除編輯框的內容並使之禁止
GetDlgItem(IDC_UNIT)->SetWindowText("");
GetDlgItem(IDC_UNIT)->EnableWindow(FALSE);
//使組合框處於未選擇狀態並使之禁止
CComboBox *pComboBox=(CComboBox *)GetDlgItem(IDC_KIND);
pComboBox->SetCurSel(-1);
pComboBox->EnableWindow(FALSE);
//使列表框處於未選擇狀態並使之禁止
m_ctrlIncome.SetCurSel(-1);
m_ctrlIncome.EnableWindow(FALSE);
}
}
OnWorkClicked函數判斷“在職”單選按鈕是否被選中。若該按鈕被選中,則使單位情況組中的控件允許,若該按鈕未被選中,則說明“下崗”按鈕被選中,這時應使控件禁止,清除編輯框中的正文, 並且使組合框和列表框處於未選中狀態。
在OnWorkClicked函數中主要調用了下列函數:
CWnd::IsDlgButtonChecked函數,用來判斷單選按鈕或檢查框是否被選擇,該函數的聲明爲
UINT IsDlgButtonChecked(int nIDButton) const;
參數nIDButton爲按鈕的ID。若按鈕被選擇,則函數返回1,否則返回0,若按鈕處於不確定狀態,則返回值爲2。CWnd::GetDlgItem函數,用來獲得指向某一控件的指針,該函數的聲明爲
CWnd* GetDlgItem(int nID) const;
參數nID爲控件的ID。該函數返回一個指定控件的CWnd對象指針,通過該指針,程序可以對控件進行控制。CWnd::EnableWindow函數,該函數使窗口允許或禁止,禁止的窗口呈灰色顯示,不能接收鍵盤和鼠標的輸入。該函數的聲明是
BOOL EnableWindow( BOOL bEnable = TRUE );
若參數bEnable的值爲TRUE,則窗口被允許,若bEnable的值爲FALSE,則窗口被禁止。CListBox::SetCurSel和CComboBox::SetCurSel函數功能類似,用來使列表中的某一項被選中,選中的項呈高亮度顯示。函數的聲明是
int SetCurSel(int nSelect);
參數nSelect指定了新選項的索引,第一項的索引值爲0,若nSelect的值爲-1,那麼函數將清除以前的選擇,使列表處於未選擇狀態。
有時,需要將GetDlgItem返回的CWnd指針強制轉換成控件對象的指針,以便調用控件對象專有的成員函數對控件進行控制。例如,在程序中GetDlgItem(IDC_KIND)返回的指針被強制轉換成CComboBox類型,只有這樣,才能調用CComboBox::SetCurSel成員函數。
爲了對控件進行查詢和控制,在程序中採用了兩種訪問控件的方法。一種方法是直接利用ClassWizard提供的控件對象,例如m_ctrlIncome列表框對象。另一種方法是利用CWnd類提供的一組管理對話框控件的成員函數,例如程序中用到的GetDlgItem和IsDlgButtonChecked。這兩種方法是在對話框內訪問控件的常用方法,讀者都應該掌握。表5.5列出了管理對話框控件的Cwnd成員函數。
表5.5 用來管理對話框控件的CWnd成員函數
函數名 |
功能 |
CheckDlgButton |
選中或不選中按鈕控件。 |
CheckRadioButton |
選擇一個指定的單選按鈕並使同組內的其它單選按鈕不被選擇。 |
DlgDirList |
往一個列表框中添加文件、目錄或驅動器的列表。 |
DlgDirListComboBox |
往一個組合框中的列表框內添加文件、目錄或驅動器的列表。 |
DlgDirSelect |
從一個列表框中獲得當前選擇的文件、目錄或驅動器。 |
DlgDirSelectBomboBox |
從一個組合框中獲得當前選擇的文件、目錄或驅動器。 |
GetCheckedRadioButton |
返回指定的單選按鈕組中被選擇的單選按鈕的ID。 |
GetDlgItem |
返回一個指向一給定的控件的臨時對象的指針。 |
GetDlgItemInt |
返回在一個指定的控件中由正文表示的數字值。 |
GetDlgItemText |
獲得在一個控件內顯示的正文。 |
GetNextDlgGroupItem |
返回一個指向一組控件內的下一個或上一個控件的臨時對象的指針。 |
GetNextDlgTabItem |
返回下一個tab順序的控件的臨時對象的指針。 |
IsDlgButtonChecked |
返回一個按鈕控件的狀態。 |
SendDlgItemMessage |
把一個消息傳送給一個控件。 |
SetDlgItemInt |
將一個整數轉換爲正文,並將此正文賦給控件。 |
SetDlgItemText |
設置一個控件顯示的正文。 |
編譯並運行Register看看,現在的登錄數據對話框已經比較令人滿意了.
5.4 非模態對話框
5.4.1 非模態對話框的特點
與模態對話框不同,非模態對話框不壟斷用戶的輸入,用戶打開非模態對話框後,仍然可以與其它界面進行交互。
非模態對話框的設計與模態對話框基本類似,也包括設計對話框模板和設計CDialog類的派生類兩部分。但是,在對話框的創建和刪除過程中,非模態對話框與模態對話框相比有下列不同之處:
-
非模態對話框的模板必須具有Visible風格,否則對話框將不可見,而模態對話框則無需設置該項風格。更保險的辦法是調用CWnd::ShowWindow(SW_SHOW)來顯示對話框,而不管對話框是否具有Visible風格。
-
非模態對話框對象是用new操作符在堆中動態創建的,而不是以成員變量的形式嵌入到別的對象中或以局部變量的形式構建在堆棧上。通常應在對話框的擁有者窗口類內聲明一個指向對話框類的指針成員變量,通過該指針可訪問對話框對象。
-
通過調用CDialog::Create函數來啓動對話框,而不是CDialog::DoModal,這是模態對話框的關鍵所在。由於Create函數不會啓動新的消息循環,對話框與應用程序共用同一個消息循環,這樣對話框就不會壟斷用戶的輸入。Create在顯示了對話框後就立即返回,而DoModal是在對話框被關閉後才返回的。衆所周知,在MFC程序中,窗口對象的生存期應長於對應的窗口,也就是說,不能在未關閉屏幕上窗口的情況下先把對應的窗口對象刪除掉。由於在Create返回後,不能確定對話框是否已關閉,這樣也就無法確定對話框對象的生存期,因此只好在堆中構建對話框對象,而不能以局部變量的形式來構建之。
-
必須調用CWnd::DestroyWindow而不是CDialog::EndDialog來關閉非模態對話框。調用CWnd::DestroyWindow是直接刪除窗口的一般方法。由於缺省的CDialog::OnOK和CDialog::OnCancel函數均調用EndDialog,故程序員必須編寫自己的OnOK和OnCancel函數並且在函數中調用DestroyWindow來關閉對話框。
-
因爲是用new操作符構建非模態對話框對象,因此必須在對話框關閉後,用delete操作符刪除對話框對象。在屏幕上一個窗口被刪除後,框架會調用CWnd::PostNcDestroy,這是一個虛擬函數,程序可以在該函數中完成刪除窗口對象的工作,具體代碼如下
void CModelessDialog::PostNcDestroy
{
delete this; //刪除對象本身
}
這樣,在刪除屏幕上的對話框後,對話框對象將被自動刪除。擁有者對象就不必顯式的調用delete來刪除對話框對象了。 -
必須有一個標誌表明非模態對話框是否是打開的。這樣做的原因是用戶有可能在打開一個模態對話框的情況下,又一次選擇打開命令。程序根據標誌來決定是打開一個新的對話框,還是僅僅把原來打開的對話框激活。通常可以用擁有者窗口中的指向對話框對象的指針作爲這種標誌,當對話框關閉時,給該指針賦NULL值,以表明對話框對象已不存在了。
提示:在C++編程中,判斷一個位於堆中的對象是否存在的常用方法是判斷指向該對象的指針是否爲空。這種機制要求程序員將指向該對象的指針初始化爲NULL值,在創建對象時將返回的地址賦給該指針,而在刪除對象時將該指針置成NULL值。 |
根據上面的分析,我們很容易把Register程序中的登錄數據對話框改成非模態對話框。這樣做的好處在於如果用戶在輸入數據時發現編輯視圖中有錯誤的數據,那麼不必關閉對話框,就可以在編輯視圖中進行修改。
請讀者按下面幾步操作:
在登錄數據對話框模板的屬性對話框的More Styles頁中選擇Visible項。
在RegisterView.h頭文件的CRegisterView類的定義中加入
public:
CRegisterDialog* m_pRegisterDlg;在RegisterView.h頭文件的頭部加入對CRegisterDialog類的聲明
class CRegisterDialog;
加入該行的原因是在CRegisterView類中有一個CRegisterDialog類型的指針,因此必須保證CRegisterDialog類的聲明出現在CRegisterView之前,否則編譯時將會出錯。解決這個問題有兩種辦法,一種辦法是保證在#include “RegisterView.h”語句之前有#include “RegisterDialog.h”語句,這種辦法造成了一種依賴關係,增加了編譯負擔,不是很好;另一種辦法是在CRegisterView類的聲明之前加上一個對CRegisterDialog的聲明來暫時“矇蔽”編譯器,這樣在有#include “RegisterView.h”語句的模塊中,除非要用到CRegisterDialog類,否則不用加入#include “RegisterDialog.h”語句。在RegisterDialog.cpp文件的頭部的#include語句區的末尾添加下面兩行
#include "RegisterDoc.h"
#include "RegisterView.h"利用ClassWizard爲CRegisterDialog類加入OnCancel和PostNcDestroy成員函數。加入的方法是進入ClassWizard後選擇Message Maps頁,並在Class name欄中選擇CRegisterDialog。然後,在Object IDs欄中選擇IDCANCEL後,在Messages欄中雙擊BN_CLICKED,這就創建了OnCancel。要創建PostNcDestroy,先在Object IDs欄中選擇CRegisterDialog,再在Messages欄中雙擊PostNcDestroy即可。分別按清單5.10和5.11,對CRegisterView類和CRegisterDialog類進行修改。
清單5.10 CRegisterView類的部分代碼
CRegisterView::CRegisterView()
{
// TODO: add construction code here
m_pRegisterDlg=NULL; //指針初始化爲NULL
}
void CRegisterView::OnEditRegister()
{
// TODO: Add your command handler code here
if(m_pRegisterDlg)
m_pRegisterDlg->SetActiveWindow(); //激活對話框
else
{
//創建非模態對話框
m_pRegisterDlg=new CRegisterDialog(this);
m_pRegisterDlg->Create(IDD_REGISTER,this);
}
}
清單5.11 CRegisterDialog的部分代碼
void CRegisterDialog::PostNcDestroy()
{
// TODO: Add your specialized code here and/or call the base class
delete this; //刪除對話框對象
}
void CRegisterDialog::OnCancel()
{
// TODO: Add extra cleanup here
((CRegisterView*)m_pParent)->m_pRegisterDlg=NULL;
DestroyWindow(); //刪除對話框
}
CRegisterView::OnEditRegister函數判斷登錄數據對話框是否已打開,若是,就激活對話框,否則,就創建該對話框。該函數中主要調用了下列函數:
調用CWnd::SetActiveWindow激活對話框,該函數的聲明爲
CWnd* SetActiveWindow( );
該函數使本窗口成爲活動窗口,並返回原來活動的窗口。調用CDialog::Create來顯示對話框,該函數的聲明爲
BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );
參數nIDTemplate是對話框模板的ID。pParentWnd指定了對話框的父窗口或擁有者。
當用戶在登錄數據對話框中點擊“取消”按鈕後,CRegisterDialog::OnCancel將被調用,在該函數中調用CWnd::DestroyWindow來關閉對話框,並且將CRegisterView的成員m_pRegisterDlg置爲NULL以表明對話框被關閉了。調用DestroyWindow導致了對CRegisterDialog::PostNcDestroy的調用,在該函數中用delete操作符刪除了CRegisterDialog對象本身。
編譯並運行Register,現在登錄數據對話框已經變成一個非模態對話框了。5.4.2 窗口對象的自動清除
一個MFC窗口對象包括兩方面的內容:一是窗口對象封裝的窗口,即存放在m_hWnd成員中的HWND(窗口句柄),二是窗口對象本身是一個C++對象。要刪除一個MFC窗口對象,應該先刪除窗口對象封裝的窗口,然後刪除窗口對象本身。
刪除窗口最直接方法是調用CWnd::DestroyWindow或::DestroyWindow,前者封裝了後者的功能。前者不僅會調用後者,而且會使成員m_hWnd保存的HWND無效(NULL)。如果DestroyWindow刪除的是一個父窗口或擁有者窗口,則該函數會先自動刪除所有的子窗口或被擁有者,然後再刪除父窗口或擁有者。在一般情況下,在程序中不必直接調用DestroyWindow來刪除窗口,因爲MFC會自動調用DestroyWindow來刪除窗口。例如,當用戶退出應用程序時,會產生WM_CLOSE消息,該消息會導致MFC自動調用CWnd::DestroyWindow來刪除主框架窗口,當用戶在對話框內按了OK或Cancel按鈕時,MFC會自動調用CWnd::DestroyWindow來刪除對話框及其控件。
窗口對象本身的刪除則根據對象創建方式的不同,分爲兩種情況。在MFC編程中,會使用大量的窗口對象,有些窗口對象以變量的形式嵌入在別的對象內或以局部變量的形式創建在堆棧上,有些則用new操作符創建在堆中。對於一個以變量形式創建的窗口對象,程序員不必關心它的刪除問題,因爲該對象的生命期總是有限的,若該對象是某個對象的成員變量,它會隨着父對象的消失而消失,若該對象是一個局部變量,那麼它會在函數返回時被清除。
對於一個在堆中動態創建的窗口對象,其生命期卻是任意長的。初學者在學習C++編程時,對new操作符的使用往往不太踏實,因爲用new在堆中創建對象,就不能忘記用delete刪除對象。讀者在學習MFC的例程時,可能會產生這樣的疑問,爲什麼有些程序用new創建了一個窗口對象,卻未顯式的用delete來刪除它呢?問題的答案就是有些MFC窗口對象具有自動清除的功能。
如前面講述非模態對話框時所提到的,當調用CWnd::DestroyWindow或::DestroyWindow刪除一個窗口時,被刪除窗口的PostNcDestroy成員函數會被調用。缺省的PostNcDestroy什麼也不幹,但有些MFC窗口類會覆蓋該函數並在新版本的PostNcDestroy中調用delete this來刪除對象,從而具有了自動清除的功能。此類窗口對象通常是用new操作符創建在堆中的,但程序員不必操心用delete操作符去刪除它們,因爲一旦調用DestroyWindow刪除窗口,對應的窗口對象也會緊接着被刪除。
不具有自動清除功能的窗口類如下所示。這些窗口對象通常是以變量的形式創建的,無需自動清除功能。
所有標準的Windows控件類。
從CWnd類直接派生出來的子窗口對象(如用戶定製的控件)。
切分窗口類CSplitterWnd。
缺省的控制條類(包括工具條、狀態條和對話條)。
模態對話框類。
具有自動清除功能的窗口類如下所示,這些窗口對象通常是在堆中創建的。
主框架窗口類(直接或間接從CFrameWnd類派生)。
視圖類(直接或間接從CView類派生)。
讀者在設計自己的派生窗口類時,可根據窗口對象的創建方法來決定是否將窗口類設計成可以自動清除的。例如,對於一個非模態對話框來說,其對象是創建在堆中的,因此應該具有自動清除功能。
綜上所述,對於MFC窗口類及其派生類來說,在程序中一般不必顯式刪除窗口對象。也就是說,既不必調用DestroyWindow來刪除窗口對象封裝的窗口,也不必顯式地用delete操作符來刪除窗口對象本身。只要保證非自動清除的窗口對象是以變量的形式創建的,自動清除的窗口對象是在堆中創建的,MFC的運行機制就可以保證窗口對象的徹底刪除。
如果需要手工刪除窗口對象,則應該先調用相應的函數(如CWnd::DestroyWindow)刪除窗口,然後再刪除窗口對象.對於以變量形式創建的窗口對象,窗口對象的刪除是框架自動完成的.對於在堆中動態創建了的非自動清除的窗口對象,必須在窗口被刪除後,顯式地調用delete來刪除對象(一般在擁有者或父窗口的析構函數中進行).對於具有自動清除功能的窗口對象,只需調用CWnd::DestroyWindow即可刪除窗口和窗口對象。注意,對於在堆中創建的窗口對象,不要在窗口還未關閉的情況下就用delete操作符來刪除窗口對象.
提示:在非模態對話框的OnCancel函數中可以不調用CWnd::DestroyWindow,取而代之的是調用CWnd::ShowWindow(SW_HIDE)來隱藏對話框.在下次打開對話框時就不必調用Create了,只需調用CWnd::ShowWindow(SW_SHOW)來顯示對話框.這樣做的好處在於對話框中的數據可以保存下來,供以後使用.由於擁有者窗口在被關閉時會調用DestroyWindow刪除每一個所屬窗口,故只要非模態對話框是自動清除的,程序員就不必擔心對話框對象的刪除問題. |
5.5 標籤式對話框
在設計較爲複雜的對話框時,常常會遇到這種情況:對某一事物的設置或選項需要用到大量的控件,以至於一個對話框放不下,而這些控件描述的是類似的屬性,不能分開。用普通的對話框技術,這一問題很難解決。
MFC提供了對標籤式對話框的支持,可以很好的解決上述問題。標籤式對話框實際上是一個包含了多個子對話框的對話框,這些子對話框通常被稱爲頁(Page)。每次只有一個頁是可見的,在對話框的頂端有一行標籤,用戶通過單擊這些標籤可切換到不同的頁。顯然,標籤式對話框可以容納大量的控件。在象Word和Developer Studio這樣複雜的軟件中,用戶會接觸到較多的標籤式對話框,一個典型的標籤式對話框如圖5.10所示。
圖5.10 典型的標籤式對話框
5.5.1 標籤式對話框的創建
爲了支持標籤式對話框,MFC提供了CPropertySheet類和CPropertyPage類。前者代表對話框的框架,後者代表對話框中的某一頁。CPropertyPage是CDialog類的派生類,而CPropertySheet是CWnd類的派生類。雖然CPropertySheet不是CDialog類的派生類,但使用CPropertySheet對象的方法與使用CDialog對象是類似的。標籤式對話框是一種特殊的對話框,因此,和普通對話框相比,它的設計與實現既有許多相似之處,又有一些不同的特點。
創建一個標籤式對話框一般包括以下幾個步驟:
分別爲各個頁創建對話框模板,去掉缺省的OK和Cancel按鈕。每頁的模板最好具有相同的尺寸,如果尺寸不統一,則框架將根據最大的頁來確定標籤對話框的大小。在創建模板時,需要在模板屬性對話框中指定下列屬性:
指定標題(Caption)的內容。標題的內容將顯示在該頁對應的標籤中。
選擇TitleBar、Child、ThinBorder和Disable屬性。
根據各個頁的模板,用ClassWizard分別爲每個頁創建CPropertyPage類的派生類。這一過程與創建普通對話框類的過程類似,不同的是在創建新類對話框中應在Base class一欄中選擇CPropertyPage而不是CDialog。
用ClassWizard爲每頁加入與控件對應的成員變量,這個過程與爲普通對話框類加入成員變量類似。
程序員可直接使用CPropertySheet類,也可以從該類派生一個新類。除非要創建一個非模態對話框,或要在框架對話框中加入控件,否則沒有必要派生一個新類。如果直接使用CPropertySheet類,則一個典型的標籤式對話框的創建代碼如清單5.12所示,該段代碼也演示了標籤式對話框與外界的數據交換。這些代碼通常是放在顯示對話框的命令處理函數中。可以看出,對話框框架的創建過程及對話框與外界的數據交換機制與普通對話框是一樣的,不同之處是還需將頁對象加入到CPropertySheet對象中。如果要創建的是模態對話框,應調用CPropertySheet::DoModal,如果想創建非模態對話框,則應該調用CPropertySheet::Create。
若從CPropertySheet類派生了一個新類,則應該將所有的頁對象以成員變量的形式嵌入到派生類中,並在派生類的構造函數中調用CPropertySheet::AddPage函數來把各個頁添加到對話框中。這樣,在創建標籤式對話框時就不用做添加頁的工作了。
清單5.12 典型的標籤式對話框創建代碼
void CMyView::DoModalPropertySheet()
{
CPropertySheet propsheet;
CMyFirstPage pageFirst; // derived from CPropertyPage
CMySecondPage pageSecond; // derived from CPropertyPage
// Move member data from the view (or from the currently
// selected object in the view, for example).
pageFirst.m_nMember1 = m_nMember1;
pageFirst.m_nMember2 = m_nMember2;
pageSecond.m_strMember3 = m_strMember3;
pageSecond.m_strMember4 = m_strMember4;
propsheet.AddPage(&pageFirst);
propsheet.AddPage(&pageSecond);
if (propsheet.DoModal() == IDOK)
{
m_nMember1 = pageFirst.m_nMember1;
m_nMember2 = pageFirst.m_nMember2;
m_strMember3 = pageSecond.m_strMember3;
m_strMember4 = pageSecond.m_strMember4;
. . .
}
}
.5.2 標籤式對話框的運行機制
標籤式對話框的初始化包括框架對話框的初始化和頁的初始化。頁的初始化工作可在OnInitDialog函數中進行,而框架對話框的初始化應該在OnCreate函數中完成。
根據CPropertySheet::DoModal返回的是IDOK還是IDCANCEL,程序可判斷出關閉對話框時按的是OK還是Cancel按鈕,這與普通對話框是一樣的。
如果標籤式對話框是模態對話框,在其底部會有三個按鈕,依次爲OK、Cancel和Apply(應用)按鈕,如果對話框是非模態的,則沒有這些按鈕。OK和Cancel按鈕的意義與普通對話框沒什麼兩樣,Apply按鈕則是標籤對話框所特有的。普通的模態對話框只有在用戶按下了OK按鈕返回後,對話框的設置才能生效,而設計Apply按鈕的意圖是讓用戶能在不關閉對話框的情況下使對話框中的設置生效。由此可見,Apply的作用與前面例子中登錄數據的“添加”按鈕類似,用戶不必退出對話框,就可以反覆進行設置,這在某些應用場合下是很有用的。
爲了對上述三個按鈕作出響應,CPropertyPage類提供了OnOK、OnCancel和OnApply函數,用戶可覆蓋這三個函數以完成所需的工作。需要指出的是這三個函數並不是直接響應按鈕的BN_CLICKED消息的,但在按鈕按下後它們會被間接調用。這些函數的說明如下:
virtual void OnOK( );
在按下OK或Apply按鈕後,該函數將被調用。缺省的OnOK函數幾乎什麼也不幹,象數據交換和關閉對話框這樣的工作是在別的地方完成的,這與普通對話框的OnOK函數是不同的。virtual void OnCancel( );
在按下Cancel按鈕後,該函數將被調用。缺省的OnCancel函數也是幾乎什麼都不幹。virtual BOOL OnApply( );
在按下OK或Apply按鈕後,該函數將被調用。缺省的OnApply會調用OnOK函數。函數的返回值如果是TRUE,則對話框中的設置將生效,否則無效。
按理說,CPropertySheet類也應該提供上述函數,特別是OnApply。但奇怪的是,MFC並未考慮CPropertySheet類的按鈕響應問題。讀者不要指望能通過ClassWizard來自動創建按鈕的BN_CLICKED消息處理函數,如果需要用到這類函數,那麼只好手工創建了。
下列幾個CPropertyPage類的成員函數也與標籤對話框的運行機制相關。
void SetModified( BOOL bChanged = TRUE );
該函數用來設置修改標誌。若參數bChanged爲TRUE,則表明對話框中的設置已改動,否則說明設置未改動。該函數的一個主要用途是允許或禁止Apply按鈕。在缺省情況下,Apply按鈕是禁止的。只要一調用SetModified(TRUE),Apply按鈕就被允許,而調用SetModified(FALSE)並不一定能使Apply按鈕禁止,只有在所有被標爲改動過的頁都調用了SetModified(FALSE)後,Apply按鈕纔會被禁止。另外,該函數對OnApply的調用也有影響,當Apply按鈕被按下後,只有那些被標爲改動過的頁的OnApply函數纔會被調用。在調用該函數之前,程序需要判斷頁中的內容是否已被修改,可以通過處理諸如BN_CLICKED、EN_CHANG這樣的控件通知消息來感知頁的內容的改變。virtual BOOL OnSetActive( );
當頁被激活或被創建時,都會調用該函數。該函數的缺省行爲是若頁還未創建,就創建之,若頁已經創建,則將其激活,並調用UpdateData(FALSE)更新控件。用戶可覆蓋該函數完成一些刷新方面的工作。virtual BOOL OnKillActive( );
當原來可見的頁被覆蓋或被刪除時,都會調用該函數。該函數的缺省行爲是調用UpdateData(TRUE)更新數據。用戶可覆蓋該函數完成一些特殊數據的有效性檢查工作。
需要說明的是,標籤對話框中的所有頁不一定都會被創建。實際上,那些從未打開過的頁及其控件是不會被創建的。因此,在CPropertyPage類的派生類中,只有在確定了頁已存在後,才能調用與對話框及控件相關的函數(如UpdateData)。如果收到控件通知消息,或OnSetActive函數被調用,則說明頁已經存在。正是由於上述原因,使得標籤式對話框的內部數據交換只能在OnSetActive和OnKillActive函數中進行。
5.5.3 標籤式對話框的具體實例
通過上面的分析,讀者對標籤式對話框已經比較瞭解了。現在,讓我們在前面做過的Register程序中加入一個標籤式對話框來試驗一下其功能。
在Register程序的登錄數據對話框中有“個人情況”和“單位情況”兩組控件,顯然,我們可以創建一個標籤式對話框並把兩組控件分別放到兩個頁中。爲了簡單起見,我們僅要求輸入姓名和單位名,簡化後的標籤式對話框如圖5.11所示。
圖5.11 簡化後的標籤式對話框
通過對標籤式對話框的分析,讀者已經知道CPropertySheet類未對Apply按鈕的控件通知消息進行處理,這是一個不足之處。Register的新版本將向讀者演示如何在CPropertySheet類的派生類中手工加入Apply按鈕的BN_CLICKED消息處理函數。另外,新版本還演示了對話框與外部對象交流的一種較好辦法,即通過發送用戶定義消息來向外部對象傳遞信息。在登錄數據對話框中,與外界交流的方法是在對話框內部直接訪問派生的視圖對象,這樣做的優點是方便快捷,缺點則是對外界依賴較大,不利於移植。而用發送用戶定義消息的方法則可以避免這個缺點。
具體工作請按下面幾步進行:
在菜單資源中的Edit菜單的“登錄數據...”項的後面插入一個名爲“標籤式對話框...”的菜單項,並指定其ID爲ID_EDIT_PROPDLG。然後用ClassWizard,在CRegisterView類內爲該菜單命令創建命令處理函數OnEditPropdlg,該函數將用來顯示標籤式對話框。
爲標籤式對話框的第一頁創建對話框模板。去掉缺省的OK和Cancel按鈕。注意應選擇中文語種和宋體字體。在屬性對話框中,指定對話框的ID爲IDD_PERSONAL,標題爲“個人情況”,在Styles頁中,選中TitleBar項,並在Style欄中選擇Child,在Border欄中選擇ThinBorder。在More Styles頁中,選中Disable。然後,在模板中加入控件,如圖5.11和表5.6所示。
表5.6
控件類型 |
控件ID |
控件標題 |
靜態正文 |
缺省 |
姓名: |
編輯框 |
IDC_NAME |
用ClassWizard爲模板IDD_PERSONAL創建CPropertyPage類的派生類,類名爲CPersonalPage。在該類中爲控件IDC_NAME加入對應的成員變量,變量名爲m_strName,類型爲CString。爲控件IDC_NAME加入EN_CHANGE消息處理函數OnChangeName,當編輯框的內容被改變時,控件會向對話框發出EN_CHANGE消息。在OnChangeName中,應該使Apply按鈕允許。
仿照步2,爲標籤式對話框的第二頁創建對話框模板。指定其ID爲IDD_UNIT,標題爲“單位情況”。在模板中加入的控件如圖5.11和表5.7所示。
表5.7
控件類型 |
控件ID |
控件標題 |
靜態正文 |
缺省 |
工作單位: |
編輯框 |
IDC_UNIT |
用ClassWizard爲模板IDD_UNIT創建CPropertyPage類的派生類,類名爲CUnitPage。在該類中爲控件IDC_UNIT加入對應的成員變量,變量名爲m_strUnit,類型爲CString。爲控件IDC_UNIT加入EN_CHANGE消息處理函數OnChangeUnit。
用ClassWizard創建一個CPropertySheet的派生類,類名爲CRegisterSheet。
在CRegisterApp類的頭文件的開頭加入下面一行
#define WM_USER_OUTPUT (WM_USER+200)
WM_USER_OUTPUT不是標準的Windows消息,而是一個用戶定義消息。在本例中,當標籤式對話框的Apply按鈕被按下後,程序會向編輯視圖發送該消息,編輯視圖對應的消息處理函數應該輸出對話框的數據。用戶定義消息的編碼範圍是WM_USER—0x7FFF。請讀者按清單5.13、5.14、5.15修改程序,限於篇幅,這裏僅列出了需要修改的部分源代碼。
清單5.13 CPersonalPage類和CUnitPage類的部分代碼
void CPersonalPage::OnChangeName()
{
// TODO: Add your control notification handler code here
SetModified(TRUE); //使Apply按鈕允許
UpdateData(TRUE);
}
void CUnitPage::OnChangeUnit()
{
// TODO: Add your control notification handler code here
SetModified(TRUE); //使Apply按鈕允許
UpdateData(TRUE);
}
當頁中的編輯框的內容被改變時,頁會收到EN_CHANGE消息,這將導致OnChangeName或OnChangeUnit被調用。對該消息的處理是使Apply按鈕允許並調用UpdateData(TRUE)更新數據。
清單5.14 CRegisterSheet類的部分代碼
//文件RegisterSheet.h
class CRegisterSheet : public CPropertySheet
{
. . . . . .
// Construction
public:
CRegisterSheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
public:
CPersonalPage m_PersonalPage;
CUnitPage m_UnitPage;
. . . . . .
protected:
//{{AFX_MSG(CRegisterSheet)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
afx_msg void OnApplyNow();
DECLARE_MESSAGE_MAP()
};
//文件RegisterSheet.cpp
#include "stdafx.h"
#include "Register.h"
#include "PersonalPage.h"
#include "UnitPage.h"
#include "RegisterSheet.h"
CRegisterSheet::CRegisterSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_PersonalPage); //向標籤對話框中添加頁
AddPage(&m_UnitPage);
}
BEGIN_MESSAGE_MAP(CRegisterSheet, CPropertySheet)
//{{AFX_MSG_MAP(CRegisterSheet)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
ON_BN_CLICKED(ID_APPLY_NOW, OnApplyNow)
END_MESSAGE_MAP()
void CRegisterSheet::OnApplyNow()
{
CFrameWnd* pFrameWnd = (CFrameWnd*) AfxGetMainWnd();
//獲取指向視圖的指針
CView* pView = pFrameWnd->GetActiveFrame()->GetActiveView();
//發送用戶定義消息,在視圖中輸出信息
pView->SendMessage(WM_USER_OUTPUT, (WPARAM)this);
m_PersonalPage.SetModified(FALSE);
m_UnitPage.SetModified(FALSE); //使Apply按鈕禁止
}
在CRegisterSheet類內嵌入了CPersonalPage和CUnitPage對象,在該類的構造函數中調用CPropertySheet::AddPage將兩個頁添加到對話框中。
標籤式對話框的OK、Cancel和Apply按鈕的ID分別是IDOK、IDCANCEL和ID_APPLY_NOW。在按下Apply按鈕後,CRegisterSheet對象應該作出響應,由於ClassWizard不能爲CRegisterSheet類提供Apply按鈕的BN_CLICKED消息處理函數,故必須手工聲明和定義消息處理函數OnApplyNow,並在消息映射表中手工加入ID_APPLY_NOW的BN_CLICKED消息映射,該映射是通過ON_BN_CLICKED宏實現的。
函數OnApplyNow用CWnd::SendMessage向視圖發送用戶定義消息WM_USER_OUTPUT,並調用CPropertyPage::SetModified(FALSE)來禁止Apply按鈕。在發送消息時,將this指針作爲wParam參數一併發送,這是因爲視圖對象需要指向CRegisterSheet對象的指針來訪問該對象。該函數演示瞭如何在程序的任意地方獲得當前活動視圖的方法:首先,調用AfxGetMainWnd()返回程序主窗口的CWnd類指針,然後將該指針強制轉換成CFrameWnd類型,接着調用CFrameWnd::GetActiveFrame返回當前活動的框架窗口的一個CFrameWnd型指針,最後調用CFrameWnd::GetActiveView返回當前活動視圖的一個Cview型指針。
在函數OnApplyNow中主要調用了下列函數:
CWnd* AfxGetMainWnd( );
該函數返回一個指向程序的主窗口CWnd指針。程序的主窗口可以是一個框架窗口,也可以是一個對話框。virtual CFrameWnd* GetActiveFrame( );
函數返回一個CFrameWnd型的指針。如果是MDI(多文檔界面)程序,則該函數將返回當前活動的子框架窗口,如果是SDI(單文檔界面)程序,該函數將返回主框架窗口本身。CView* GetActiveView( ) const;
返回一個指向當前活動視圖的Cview型指針。LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
用於向本窗口發送消息。SendMessage會直接調用發送消息的處理函數,直到發送消息被處理完後該函數才返回。參數message說明了要發送的消息,wParam和lParam則提供了消息的附加信息。
清單5.15 CRegisterView類的部分代碼
//文件RegisterView.h
class CRegisterView : public CEditView
{
. . . . . .
// Generated message map functions
protected:
//{{AFX_MSG(CRegisterView)
afx_msg void OnEditRegister();
afx_msg void OnEditPropdlg();
//}}AFX_MSG
afx_msg LRESULT OnOutput(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
//文件RegisterView.cpp
#include "stdafx.h"
#include "Register.h"
#include "RegisterDoc.h"
#include "RegisterView.h"
#include "RegisterDialog.h"
#include "PersonalPage.h"
#include "UnitPage.h"
#include "RegisterSheet.h"
BEGIN_MESSAGE_MAP(CRegisterView, CEditView)
. . . . . .
ON_MESSAGE(WM_USER_OUTPUT, OnOutput)
END_MESSAGE_MAP()
void CRegisterView::OnEditPropdlg()
{
// TODO: Add your command handler code here
CRegisterSheet RegisterSheet("登錄");
RegisterSheet.m_PersonalPage.m_strName="張穎峯";
RegisterSheet.m_UnitPage.m_strUnit="南京郵電學院";
if(RegisterSheet.DoModal()==IDOK)
OnOutput((WPARAM)&RegisterSheet,0);
}
//用戶定義消息WM_USER_OUTPUT的處理函數
LRESULT CRegisterView::OnOutput(WPARAM wParam, LPARAM lParam)
{
CRegisterSheet *pSheet=(CRegisterSheet*)wParam;
CString str;
GetWindowText(str);
str+="/r/n";
str+="姓名:";
str+=pSheet->m_PersonalPage.m_strName;
str+="/r/n";
str+="工作單位:";
str+=pSheet->m_UnitPage.m_strUnit;
str+="/r/n";
SetWindowText(str);
return 0;
}
OnEditPropdlg函數負責初始化和創建標籤式對話框,這一過程與創建普通對話框差不多。如果用戶是按OK按鈕返回的,則調用OnOutput函數輸出數據。
CRegisterView類的OnOutput函數負責處理標籤對話框發來的用戶定義消息WM_USER_OUTPUT。用戶定義消息的處理函數只能用手工的方法加入。用戶定義消息的消息映射是用ON_MESSAGE宏來完成的。
函數OnOutput的兩個參數wParam和lParam分別對應消息的wParam和lParam值。該函數從wParam參數中獲得指向CRegisterSheet對象的指針,然後將該對象中的數據輸出到視圖中。
編譯並運行Register,試一試自己設計的標籤式對話框。
5.6 公用對話框
在使用Windows的過程中,用戶經常會遇到一些常用的有特定用途的對話框。例如,當選擇File->Open,會彈出一個文件選擇的對話框,用戶可以在其中選擇想要打開的文件。象文件選擇這樣的對話框,使用的非常普遍,因此Windows系統本身提供了對該對話框的支持,用戶不必自己設計文件選擇對話框。與文件選擇對話框類似的還有顏色選擇、字體選擇、打印和打印設置以及正文搜索和替換對話框。這五種對話框均由Windows支持,被稱爲公用對話框。MFC提供了一些公用對話框類,它們均是CDialog類的派生類,封裝了公用對話框的功能。表5.6列出了MFC的公用對話框類。
表5.6 公用對話框類
通用對話框類 |
用途 |
CColorDialog |
選擇顏色 |
CFileDialog |
選擇文件名,用於打開和保存文件 |
CFindReplaceDialog |
正文查找和替換 |
CFontDialog |
選擇字體 |
CPrintDialog |
打印和打印設置 |
通用對話框類使用方便,讀者只需知道怎樣創建對話框和訪問對話框的數據,不必關心它們的內部細節。
5.6.1 CColorDialog類
CColorDialog類用於實現Color(顏色選擇)公用對話框。Color對話框如圖5.12所示,在Windows的畫板程序中,如果用戶在顏色面板的某種顏色上雙擊鼠標,就會顯示一個Color對話框來讓用戶選擇顏色。
圖5.12 Color對話框
Color對話框的創建與一般的對話框沒什麼兩樣:首先是在堆棧上構建一個CColorDialog對象,然後調用CColorDialog::DoModal( )來啓動對話框。CColorDialog的構造函數爲
CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL );
參數clrInit用來指定初始的顏色選擇,dwFlags用來設置對話框,pParentWnd用於指定對話框的父窗口或擁有者窗口。
根據DoModal返回的是IDOK還是IDCANCEL可知道用戶是否確認了對顏色的選擇。DoModal返回後,調用CColorDialog::GetColor()可以返回一個COLORREF類型的結果來指示在對話框中選擇的顏色。COLORREF是一個32位的值,用來說明一個RGB顏色。GetColor返回的COLORREF的格式是0x00bbggrr,即低位三個字節分別包含了藍、綠、紅三種顏色的強度。
讀者將在後面的章節中看到顏色選擇對話框的例子。
5.6.2 CFileDialog類
CFileDialog類用於實現文件選擇對話框,以支持文件的打開和保存操作。用戶要打開或保存文件,就會和文件選擇對話框打交道,圖5.13顯示了一個標準的用於打開文件的文件選擇對話框。用MFC AppWizard建立的應用程序中自動加入了文件選擇對話框,在File菜單選Open或Save As命令會啓動它們。
圖5.13 文件選擇對話框
文件選擇對話框的創建過程與一般對話框的類似,首先是在堆棧上構建一個CFileDialog對象,然後調用CFileDialog::DoModal( )來啓動對話框。文件對話框的構造函數爲
CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );
如果參數bOpenFileDialog的值爲TRUE,將創建Open(打開文件)對話框,否則就創建Save As(保存文件)對話框。參數lpszDefExt用來指定缺省的文件擴展名。lpszFileName用於規定初始文件名。dwFlags用於設置對話框的一些屬性。lpszFilter指向一個過濾字符串,用戶如果只想選擇某種或某幾種類型的文件,就需要指定過濾字符串。參數pParentWnd是指向父窗口或擁有者窗口的指針。
過濾字符串有特定的格式,它實際上是由多個子串組成,每個子串由兩部分組成,第一部分是過濾器的字面說明,如“Text file (*.txt)”,第二部分是用於過濾的匹配字符串,如“*.txt”,子串的兩部分用豎線字符“ | ”分隔開。各子串之間也要用“ | ”分隔,且整個串的最後兩個字符必須是兩個連續的豎線字符“ || ”。一個典型的過濾字符串如下面所示:
char szFilter[]=
“All files (*.*)|*.*|Text files(*.txt)|*.txt|Word documents(*.doc)|*.doc||”;
若CFileDialog::DoModal返回的是IDOK,那麼可以用表5.7列出的CFileDialog類的成員函數來獲取與所選文件有關的信息。
表5.7 CFileDialog類輔助成員函數
函數名 |
用途 |
GetPathName |
返回一個包含有全路徑文件名的CString對象。 |
GetFileName |
返回一個包含有文件名(不含路徑)的CString對象。 |
GetFileExt |
返回一個只含文件擴展名的CString對象。 |
GetFileTitle |
返回一個只含文件名(不含擴展名)的CString對象。 |
5.6.3 CFindReplaceDialog類
CFindReplaceDialog類用於實現Find(搜索)和Replace(替換)對話框,這兩個對話框都是非模態對話框,用於在正文中搜索和替換指定的字符串。圖5.14顯示了一個Find對話框,圖5.15顯示了一個Replace對話框。
圖5.14 Find對話框
圖5.15 Replace對話框
由於Find和Replace對話框是非模式對話框,它們的創建方式與其它四類公用對話框不同。CFindReplaceDialog對象是用new操作符在堆中創建的,而不是象普通對話框那樣以變量的形式創建。要啓動Find/Replace對話框,應該調用CFindReplaceDialog::Create函數,而不是DoModal。Create函數的聲明是
BOOL Create( BOOL bFindDialogOnly, LPCTSTR lpszFindWhat, LPCTSTR lpszReplaceWith = NULL, DWORD dwFlags = FR_DOWN, CWnd* pParentWnd = NULL );
當參數bFindDialogOnly的值爲TRUE時,創建的是Find對話框,爲FALSE時創建的是Replace對話框。參數lpszFindWhat指定了要搜索的字符串,lpszReplaceWith指定了用於替換的字符串。dwFlags用來設置對話框,其缺省值是FR_DOWN(向下搜索),該參數可以是幾個FR_XXX常量的組合,用戶可以通過該參數來決定諸如是否要顯示Match case、Match Whole Word檢查框等設置。參數pParentWnd指明瞭對話框的父窗口或擁有者窗口。
Find/Replace對話框與其它公用對話框的另一個不同之處在於它在工作過程中可以重複同一操作而對話框不被關閉,這就方便了頻繁的搜索和替換。CFindReplaceDialog類只提供了一個界面,它並不會自動實現搜索和替換功能。CFindReplaceDialog使用了一種特殊的通知機制,當用戶按下了操作的按鈕後,它會向父窗口發送一個通知消息,父窗口應在該消息的消息處理函數中實現搜索和替換。
CFindReplaceDialog類提供了一組成員函數用來獲得與用戶操作有關的信息,如表5.8所示,這組函數一般應在通知消息處理函數中調用。
表5.8 CFindReplaceDialog類的輔助成員函數
函數名 |
用途 |
FindNext |
如果用戶點擊了Findnext按鈕,該函數返回TRUE。 |
GetNotifier |
返回一個指向當前CFindReplaceDialog對話框的指針。 |
GetFindString |
返回一個包含要搜索字符串的CString對象。 |
GetReplaceString |
返回一個包含替換字符串的CString對象。 |
IsTerminating |
如果對話框終止了,則返回TRUE。 |
MatchCase |
如果選擇了對話框中的Match case檢查框,則返回TRUE。 |
MatchWholeWord |
如果選擇了對話框中的Match Whole Word檢查框,則返回TRUE。 |
ReplaceAll |
如果用戶點擊了Replace All按鈕,該函數返回TRUE。 |
ReplaceCurrent |
如果用戶點擊了Replace按鈕,該函數返回TRUE。 |
SearchDown |
返回TRUE表明搜索方向向下,返回FALSE則向上。 |
CEditView類自動實現了Find和Replace對話框的功能,但MFC AppWizard並未提供相應的菜單命令。讀者可以在前面的Register工程的Edit菜單中加入&Find...和&Replace...兩項,並令其ID分別爲ID_EDIT_FIND和ID_EDIT_REPLACE,則Find/Replace對話框的功能就可以實現。
5.6.4 CFontDialog類
CFontDialog類支持Font(字體)對話框,用來讓用戶選擇字體。圖5.16顯示了一個Font對話框。Font對話框的創建過程與Color對話框的類似,首先是在堆棧上構建一個CFontDialog對象,然後調用CFontDialog::DoModal來啓動對話框。
圖5.16 Font對話框
CFontDialog類的構造函數如下所示
CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );
參數lplfInitial指向一個LOGFONG結構,用來初始化對話框中的字體設置。dwFlags用於設置對話框。pdcPrinter指向一個代表打印機的CDC對象,若設置該參數,則選擇的字體就爲打印機所用。pParentWnd用於指定對話框的父窗口或擁有者窗口。
若DoModal返回IDOK,那麼可以調用CFontDialog的成員函數來獲得所選字體的信息,這些函數在表5.9列出。
表5.9 CFontDialog類的輔助成員函數
函數名 |
用途 |
GetCurrentFont |
用來獲得所選字體的屬性。該函數有一個參數,該參數是指向LOGFONT結構的指針,函數將所選字體的各種屬性寫入這個LOGFONT結構中。 |
GetFaceName |
返回一個包含所選字體名字的CString對象。 |
GetStyleName |
返回一個包含所選字體風格名字的CString對象。 |
GetSize |
返回所選字體的尺寸(以10個象素爲單位)。 |
GetColor |
返回一個含有所選字體的顏色的COLORREF型值。 |
GetWeight |
返回所選字體的權值。 |
IsStrikeOut |
若用戶選擇了空心效果則返回TRUE,否則返回FALSE。 |
IsUnderline |
若用戶選擇了下劃線效果則返回TRUE,否則返回FALSE。 |
IsBold |
若用戶選擇了黑體風格則返回TRUE,否則返回FALSE。 |
IsItalic |
若用戶選擇了斜體風格則返回TRUE,否則返回FALSE。 |
.6.5 CPrintDialog類
CPrintDialog類支持Print(打印)和Print Setup(打印設置)對話框,通過這兩個對話框用戶可以進行與打印有關的操作。圖5.17顯示了一個Print對話框,圖5.18顯示了一個Print Setup對話框。
圖5.17 Print對話框
圖5.18 Print Setup對話框
Print和Print Setup對話框的創建過程與Color對話框類似。該類的構造函數是
CPrintDialog( BOOL bPrintSetupOnly, DWORD dwFlags = PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION, CWnd* pParentWnd = NULL );
參數bPrintSetupOnly的值若爲TRUE,則創建的是Print對話框,否則,創建的是Print Setup對話框。dwFlags用來設置對話框,缺省設置是打印出全部頁,禁止From和To編輯框(即不用確定要打印的頁的範圍),PD_USEDEVMODECOPIES使對話框判斷打印設備是否支持多份拷貝和校對打印(Collate),若不支持,就禁止相應的編輯控件和Collate檢查框。pParentWnd用來指定對話框的父窗口或擁有者窗口。
程序可以調用如表5.10所示的CPrintDialog的成員函數來獲得打印參數。
表5.10 CPrintDialog的輔助成員函數
函數名 |
用途 |
GetCopies |
返回要求的拷貝數。 |
GetDefaults |
在不打開對話框的情況下返回缺省打印機的缺省設置,返回的設置放在m_pd數據成員中。 |
GetDeviceName |
返回一個包含有打印機設備名的CString對象。 |
GetDevMode |
返回一個指向DEVMODE結構的指針,用來查詢打印機的設備初始化信息和設備環境信息。 |
GetDriverName |
返回一個包含有打印機驅動程序名的CString對象。 |
GetFromPage |
返回打印範圍的起始頁碼。 |
GetToPage |
返回打印範圍的結束頁碼。 |
GetPortName |
返回一個包含有打印機端口名的CString對象。 |
GetPrinterDC |
返回所選打印設備的一個 HDC 句柄。 |
PrintAll |
若要打印文檔的所有頁則返回TRUE。 |
PrintCollate |
若用戶選擇了Collate Copies檢查框(需要校對打印拷貝)則返回TRUE。 |
PrintRange |
如果用戶要打印文檔的一部分頁,則返回TRUE。 |
PrintSelection |
若用戶想打印當前選擇的部分文檔,則返回TRUE。 |
用缺省配置的MFC AppWizard建立的程序支持Print和Print Setup對話框,用戶可以在File菜單中啓動它們。
5.6.6 公用對話框的使用實例
現在,讓我們來測試一下公用對話框的使用。請讀者用AppWizard創建一個單文檔的MFC應用程序,名爲CommonDlg。注意別忘了在AppWizard的第一步中選Single document。
CommonDlg程序要對所有的公用對話框進行了測試。爲此,首先要提供用戶命令接口。請讀者在CommonDlg的菜單資源中插入一個名爲&Common的新菜單,這個菜單插在Help菜單之前。然後,在Common菜單中,請按表5.11創建菜單項。
表5.11 Common菜單的菜單項
Caption |
ID |
&Color... |
ID_COMMON_COLOR |
&Open file... |
ID_COMMON_OPENFILE |
&Save file... |
ID_COMMON_SAVEFILE |
&Font... |
ID_COMMON_FONT |
&Print... |
ID_COMMON_PRINT |
P&rint setup... |
ID_COMMON_PRINTSETUP |
F&ind... |
ID_COMMON_FIND |
&Replace... |
ID_COMMON_REPLACE |
接下來的工作是編寫測試程序的源代碼。首先,利用ClassWizard爲表5.11的菜單項創建消息處理函數,注意這些處理函數都是CCommonDlgView的成員。接着,請按清單5.10和5.11修改程序。限於篇幅,這裏僅列出與測試相關的部分源代碼。
清單5.10 頭文件CommonDlgView.h
class CCommonDlgView : public CView
{
. . . . . .
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
void DispPrintInfo(CPrintDialog& dlg);
protected:
CFont m_Font; //正文的字體
COLORREF m_ForeColor; //正文的前景色
COLORREF m_BackColor; //正文的背景色
CFindReplaceDialog *m_pFindReplaceDlg;
BOOL m_bFindOnly;
// Generated message map functions
protected:
//Find和Replace對話框通知消息處理函數
afx_msg LRESULT OnFindReplaceCmd(WPARAM, LPARAM lParam);
//{{AFX_MSG(CCommonDlgView)
afx_msg void OnCommonColor();
afx_msg void OnCommonFont();
afx_msg void OnCommonOpenfile();
afx_msg void OnCommonSavefile();
afx_msg void OnCommonPrint();
afx_msg void OnCommonPrintsetup();
afx_msg void OnCommonFind();
afx_msg void OnCommonReplace();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
. . . . . .
清單5.11 文件CCommonDlgView.cpp
#include "stdafx.h"
#include "CommonDlg.h"
#include "CommonDlgDoc.h"
#include "CommonDlgView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNCREATE(CCommonDlgView, CView)
//獲取對本進程唯一的消息編號
static const UINT nMsgFindReplace = ::RegisterWindowMessage(FINDMSGSTRING);
BEGIN_MESSAGE_MAP(CCommonDlgView, CView)
//{{AFX_MSG_MAP(CCommonDlgView)
ON_COMMAND(ID_COMMON_COLOR, OnCommonColor)
ON_COMMAND(ID_COMMON_FONT, OnCommonFont)
ON_COMMAND(ID_COMMON_OPENFILE, OnCommonOpenfile)
ON_COMMAND(ID_COMMON_SAVEFILE, OnCommonSavefile)
ON_COMMAND(ID_COMMON_PRINT, OnCommonPrint)
ON_COMMAND(ID_COMMON_PRINTSETUP, OnCommonPrintsetup)
ON_COMMAND(ID_COMMON_FIND, OnCommonFind)
ON_COMMAND(ID_COMMON_REPLACE, OnCommonReplace)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
ON_REGISTERED_MESSAGE(nMsgFindReplace, OnFindReplaceCmd)
END_MESSAGE_MAP()
CCommonDlgView::CCommonDlgView()
{
// TODO: add construction code here
//缺省前景色爲黑色,背景色爲白色,字體爲系統字體
m_ForeColor=0;
m_BackColor=0xFFFFFF;
m_Font.CreateStockObject(SYSTEM_FONT);
m_pFindReplaceDlg=NULL;
}
void CCommonDlgView::OnDraw(CDC* pDC)
{
CCommonDlgDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
int x,y;
CFont *pOldFont;
TEXTMETRIC TM;
int textHeight;
//設置正文的字體
pOldFont=pDC->SelectObject(&m_Font);
//設置正文的前景色和背景色
pDC->SetTextColor(m_ForeColor);
pDC->SetBkColor(m_BackColor);
//計算每行正文的高度
pDC->GetTextMetrics(&TM);
textHeight=TM.tmHeight+TM.tmExternalLeading;
//輸出正文
x=5;y=5;
pDC->TextOut(x,y,"ABCDEFG");
y+=textHeight;
pDC->TextOut(x,y,"abcdefg");
//恢復原來的字體
pDC->SelectObject(pOldFont);
}
void CCommonDlgView::OnCommonColor()
{
// TODO: Add your command handler code here
CColorDialog dlg;
if(dlg.DoModal()==IDOK)
{
m_BackColor=dlg.GetColor();
//重繪視圖
Invalidate();
UpdateWindow();
}
}
void CCommonDlgView::OnCommonFont()
{
// TODO: Add your command handler code here
CFontDialog dlg;
if(dlg.DoModal()==IDOK)
{
LOGFONT LF;
//獲取所選字體的信息
dlg.GetCurrentFont(&LF);
m_ForeColor=dlg.GetColor();
//建立新的字體
m_Font.DeleteObject();
m_Font.CreateFontIndirect(&LF);
Invalidate();
UpdateWindow();
}
}
void CCommonDlgView::OnCommonOpenfile()
{
// TODO: Add your command handler code here
//過濾字符串
char szFileFilter[]=
"Cpp files(*.cpp)|*.cpp|"
"Header files(*.h)|*.h|"
"All files(*.*)|*.*||";
CFileDialog dlg(TRUE, //Open對話框
"cpp", //缺省擴展名
"*.cpp",
OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, //文件必須存在
szFileFilter,
this);
if(dlg.DoModal()==IDOK)
{
CString str="The full path name is:";
str+=dlg.GetPathName();
AfxMessageBox(str);
}
}
void CCommonDlgView::OnCommonSavefile()
{
// TODO: Add your command handler code here
char szFileFilter[]=
"Cpp files(*.cpp)|*.cpp|"
"Header files(*.h)|*.h|"
"All files(*.*)|*.*||";
CFileDialog dlg(FALSE, //Save對話框
"cpp",
"*.cpp",
OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
szFileFilter,
this);
if(dlg.DoModal()==IDOK)
{
CString str="The file name is:";
str+=dlg.GetFileName();
AfxMessageBox(str);
}
}
void CCommonDlgView::OnCommonPrint()
{
// TODO: Add your command handler code here
CPrintDialog dlg(FALSE, PD_ALLPAGES); //Print對話框
//設置Print對話框的屬性
dlg.m_pd.nCopies=2;
dlg.m_pd.nMinPage=1;
dlg.m_pd.nMaxPage=50;
dlg.m_pd.nFromPage=1;
dlg.m_pd.nToPage=50;
if(dlg.DoModal()==IDOK)
DispPrintInfo(dlg);
}
void CCommonDlgView::OnCommonPrintsetup()
{
// TODO: Add your command handler code here
CPrintDialog dlg(TRUE) //Print Setup對話框
if(dlg.DoModal()==IDOK)
DispPrintInfo(dlg);
}
void CCommonDlgView::DispPrintInfo(CPrintDialog& dlg)
{
CString str;
CString temp;
str+="Driver name:";
str+=dlg.GetDriverName();
str+="/nDevice name:";
str+=dlg.GetDeviceName();
str+="/nPort name:";
str+=dlg.GetPortName();
str+="/nNumber of copies:";
temp.Format("%d",dlg.GetCopies());
str+=temp;
str+="/nCollate:";
str+=dlg.PrintCollate()?"Yes":"No";
str+="/nPrint all:";
str+=dlg.PrintAll()?"Yes":"No";
str+="/nPrint range:";
str+=dlg.PrintRange()?"Yes":"No";
str+="/nSelection:";
str+=dlg.PrintSelection()?"Yes":"No";
str+="/nFrom page:";
temp.Format("%d",dlg.GetFromPage());
str+=temp;
str+="/nTo page:";
temp.Format("%d",dlg.GetToPage());
str+=temp;
AfxMessageBox(str);
}
void CCommonDlgView::OnCommonFind()
{
// TODO: Add your command handler code here
//判斷是否已存在一個對話框
if(m_pFindReplaceDlg)
{
if(m_bFindOnly)
{
//若Find對話框已打開,則使之成爲活動窗口
m_pFindReplaceDlg->SetActiveWindow();
return;
}
else
//關閉Replace對話框
m_pFindReplaceDlg->SendMessage(WM_CLOSE);
}
m_bFindOnly=TRUE;
//創建Find對話框
m_pFindReplaceDlg=new CFindReplaceDialog;
m_pFindReplaceDlg->Create(TRUE,NULL,NULL,FR_DOWN,this);
}
void CCommonDlgView::OnCommonReplace()
{
// TODO: Add your command handler code here
//判斷是否已存在一個對話框
if(m_pFindReplaceDlg)
{
if(!m_bFindOnly)
{
//若Replace對話框已打開,則使之成爲活動窗口
m_pFindReplaceDlg->SetActiveWindow();
return;
}
else
//關閉Find對話框
m_pFindReplaceDlg->SendMessage(WM_CLOSE);
}
m_bFindOnly=FALSE;
//創建Replace對話框
m_pFindReplaceDlg=new CFindReplaceDialog;
m_pFindReplaceDlg->Create(FALSE,NULL,NULL,FR_DOWN,this);
}
//Find和Replace對話框通知消息處理函數
LRESULT CCommonDlgView::OnFindReplaceCmd(WPARAM, LPARAM lParam)
{
//判斷對話框是否被關閉
if(m_pFindReplaceDlg->IsTerminating())
m_pFindReplaceDlg=NULL;
return 0;
}
讓我們先來看看對Color對話框的測試。在CCommonDlgView::OnCommonColor中創建了一個Color對話框,在此處該對話框的用途是爲視圖中顯示的正文指定背景色。在CCommonDlgView的構造函數中將背景色m_BackColor的初值設置爲白色(0x00000000)。若DoModal返回IDOK,則調用CColorDialog::GetColor獲取用戶選擇的顏色並將之保存在m_BackColor成員中。然後,調用Invalidate和UpdateWindow函數以重繪視圖。這兩個函數的說明如下:
void Invalidate( BOOL bErase = TRUE );
該函數的作用是使整個窗口客戶區無效。窗口的客戶區無效意味着需要重繪,例如,如果一個被其它窗口遮住的窗口變成了前臺窗口,那麼原來被遮住的部分就是無效的,需要重繪。這時Windows會在應用程序的消息隊列中放置WM_PAINT消息。MFC爲窗口類提供了WM_PAINT的消息處理函數OnPaint,OnPaint負責重繪窗口。視圖類有一些例外,在視圖類的OnPaint函數中調用了OnDraw函數,實際的重繪工作由OnDraw來完成。參數bErase爲TRUE時,重繪區域內的背景將被擦除,否則,背景將保持不變。void UpdateWindow( );
該函數的作用是使窗口立即重繪。調用Invalidate等函數後窗口不會立即重繪,這是由於WM_PAINT消息的優先級很低,它需要等消息隊列中的其它消息發送完後才能被處理。調用UpdateWindow函數可使WM_PAINT被直接發送到目標窗口,從而導致窗口立即重繪。
在CCommonView::OnDraw函數中調用了CDC::SetBkColor來設置背景色。CDC類用於繪圖,在後面的幾章裏將會對其作詳細介紹。CDC::TextOut函數用於輸出正文。兩個函數的說明如下:
virtual COLORREF SetBkColor( COLORREF crColor );
用於設置背景色。參數crColor指定了背景色的RGB值。返回的是原來的背景色。BOOL TextOut( int x, int y, const CString& str );
在指定的位置輸出正文。參數x和y指定了輸出起點的橫向和縱向座標。str參數是輸出的字符串。若該函數調用成功則返回TRUE。
對文件選擇對話框的測試比較簡單。在CCommonDlgView::OnCommonOpenfile和CCommonDlgView:OnCommonSavefile函數中,分別創建了一個Open對話框和一個Save對話框。在創建Open對話框時,在CFileDialog的構造函數中規定了OFN_FILEMUSTEXIST屬性,這樣當用戶試圖打開一個不存在的文件時,對話框會發出錯誤信息並讓用戶從新選擇文件。在創建Save對話框時,在CFileDialog的構造函數中規定了OFN_OVERWRITEPROMPT屬性,這樣,當用戶試圖覆蓋一個已存在的文件時,對話框會詢問用戶是否真的要覆蓋該文件。
若用戶確認了對文件的選擇,那麼在文件選擇對話框關閉後,程序會將所選文件的文件名或全路徑文件名輸出到屏幕上。
Find和Replace對話框的創建工作分別由CCommonDlgView::OnCommonFind和CCommonDlgView::OnCommonReplace完成。
在OnCommonFind函數中,首先判斷是否已經打開了一個Find/Replace對話框。這個判斷是完全必要的,因爲Find/Replace對話框是非模態對話框,打開一個對話框後,用戶有可能通過菜單再次執行Find或Replace命令。成員m_pFindReplaceDlg是指向CFindReplaceDialog對象的指針,若該指針不爲空,則說明對話框已打開。接着,根據成員m_bFindOnly來判斷原先打開的是否是Find對話框,如果原先打開的是一個Find對話框,則此時不必創建新的對話框,只需激活已打開的Find對話框就行了;如果原先打開的是一個Replace對話框,則應該先關閉該對話框,然後再創建Find對話框。然後,給m_bFindOnly賦TRUE值,以表明現在打開的是一個Find對話框。最後,創建一個非模態的Find對話框,請注意其過程與創建模態對話框的不同之處:
對話框對象是用new操作符在堆上創建的,而不是以變量的形式創建。
對話框的啓動是靠調用Create函數實現的,而不是DoModal函數。
調用CWnd::SetActiveWindow以激活窗口。調用CWnd::SendMessage(WM_CLOSE)來關閉窗口,這是因爲WM_CLOSE消息會導致CWnd::DestroyWindow函數的調用。
OnCommonReplace函數的過程與OnCommonFind函數類似。在該函數中,對m_bFindOnly賦值FALSE以表明打開的是Replace對話框。
Find/Replace對話框通知消息的處理函數是CCommonDlgView::OnFindReplaceCmd,這個消息處理函數及消息映射均是手工加入的。請注意在CommonDlgView.cpp文件的開頭部分定義了一個靜態全局變量nMsgFindReplace
static const UINT nMsgFindReplace = :: RegisterWindowMessage( FINDMSGSTRING );
nMsgFindReplace變量用於存放消息碼,這個消息是由函數RegisterWindowMessage提供的,該函數的聲明爲
UINT RegisterWindowMessage(LPCTSTR lpString);
參數lpString是一個消息字符串。調用RegisterWindowMessage函數會返回一個Windows註冊消息,註冊消息的編碼在系統中是唯一的。當有多個應用程序需要處理同一個消息時,應調用該函數註冊消息。如果消息是本應用程序專有的,則不必註冊。如果兩個應用程序使用相同的字符串註冊消息,則會返回相同的消息,這樣,通過該消息,兩個應用程序可以進行通信。
註冊消息的消息映射宏是ON_REGISTERED_MESSAGE,在CommonDlgView的消息映射中可以找到它。
在函數OnFindReplaceCmd中應該進行實際的搜索和替換工作,但在本例中該函數什麼工作也不作。該函數只是判斷一下對話框是否被關閉,若是,則給m_pFindReplaceDlg賦NULL值,以表明對話框已不存在了。
Font對話框的創建由函數CCommonView:: OnCommonFont完成。該函數收集了用戶選擇的字體的信息,並利用這些信息創建新的字體。成員m_ForeColor用來保存所選字體的顏色,成員m_Font是一個CFont對象,用來保存用戶選擇的字體。在CCommonView的構造函數中,m_ForeColor被初始化成黑色(0xFFFFFF),m_Font被初始化爲系統字體。系統字體的獲得是通過調用CGdiObject::CreateStockObject(SYSTEM_FONT)實現的,該函數用於獲得系統庫存的繪圖對象,包括字體、畫筆、刷子、調色板等。
在OnCommonFont函數中,主要調用了下列函數:
調用CFontDialog:: GetCurrentFont以獲得用戶選擇字體的信息,該函數的聲明爲
void GetCurrentFont( LPLOGFONT lplf );
參數lplf是一個指向LOGFONT結構的指針,LOGFONT結構用來存放與字體有關的信息。調用CFontDialog::GetColor來獲得所選字體的顏色(前景色)。
調用CGdiObject:: DeleteObject()來刪除存放在CFont對象m_Font中的老字體。
調用CFont::CreateFontIndirect以創建一種字體,該函數的聲明是
BOOL CreateFontIndirect(const LOGFONT* lpLogFont );
參數lpLogFont是一個指向LOGFONT結構的指針,函數根據該結構提供的信息來初始化Cfont對象。調用CWnd::Invalidate和CWnd::UpdateWindow重繪視圖。
在CCommonView::OnDraw函數中,利用選擇的字體和顏色輸出兩行正文。當視圖需要重繪時,OnDraw就會被調用。在OnDraw函數中主要調用了下列函數:
在輸出正文前,調用CDC::SelectObject指定輸出正文的字體,輸出完成後,調用CDC::SelectObject恢復被替換的字體。SelectObject有五個版本,用於爲繪圖指定畫筆、刷子、字體、位圖等繪圖對象。在用該函數指定繪圖對象時,應該把被替換的對象保存起來,在繪圖完成後,需要再次調用該函數恢復被替換的繪圖對象。如果不進行恢復,則可能會使設備對象CDC中含有非法的句柄。指定字體的SelectObject函數的聲明是
virtual CFont* SelectObject( CFont* pFont );
參數pFont是指向CFont對象的指針。函數返回一個CFont對象的指針,指向被替代的字體。調用CDC::SetTextColor來指定正文顯示的前景色,該函數的聲明爲
virtual COLORREF SetTextColor( COLORREF crColor );
參數crColor指定了RGB顏色。函數返回的是原來的正文顏色。調用CDC:: GetTextMetrics函數獲得與繪圖字體有關的各種信息,該函數的聲明爲
BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;
參數lpMetrics是一個指向TEXTMETRIC結構的指針,該結構包含有字體的信息,其中tmHeight成員說明了字體的高度,tmExternalLeading成員說明了行與行之間的空白應該是多少。把這兩個值相加就得到了每行正文的高度。調用CDC::TextOut在指定位置輸出正文。
在CCommonDlgView::OnCommonPrint和CCommonDlgView::OnCommonPrintsetup()函數中,分別創建了一個Print對話框和Print Setup對話框。在創建Print對話框時,通過CPrintDialog對象的m_pd成員,對對話框進行了一些初始化,這包括對拷貝份數、打印範圍等的設置。在兩個對話框DoModal返回IDOK後,均調用CCommonDlgView::DispPrintInfo報告打印信息。DispPrintInfo函數的代碼較簡單,這裏就不作解釋了。