# 9.2 一個例子:PersonalRecordDialog
正如我們在前面的章節看到的那樣,對話框通常有兩種:模式對話框和非模式對話框.我們將創建一個定制的模式的對話框,因為這是最常用的一種對話框,而且需要注意的方面也會更少.因為應用程序調用ShowModal函數以后,直到這個函數返回,應用程序將禁止除了_這個窗口以及這個窗口產生的別的模式對話框窗口_以外的所有別的窗口處理任何用戶輸入,而將所有的用戶交互限制在我們這個模式對話框的小世界里面.
創建一個自定義對話框的許多步驟,可以通過使用一個對話框編輯器而變得簡單.比如wxDesigner或者DialogBlocks (譯者注:當然,它們都是收費的.一個不收費的開源的對話框編輯器是wxGlade,效果好像也不錯).如果使用了對話框編輯器,那么剩余的需要你自己寫代碼的部分的復雜程度將和你的對話框的復雜程序相關.不過在這里,我們將以全部手寫的方式創建這個對話框,以便于演示創建定制對話框的一些基本原理,但是我們強烈推薦你使用任何一種對話框編輯器因為它將節省你大量的時間.
我們將通過這樣的一個對話框來演示創建自定義對話框的步驟: 用戶需要使用這個對話框輸入它的姓名,年齡和性別以及它是否想投票.這個對話框叫做PersonalRecordDialog,它的樣子如下圖所示:

其中的Reset按鈕將所有控件的值復位到它們的默認值.Ok按鈕關閉對話框并且以wxID_OK值從ShowModal函數返回. Cancel按鈕關閉對話框并且以wxID_CANCEL值從ShowModal函數返回,也不會用用戶輸入的值來更新對話框的內部變量.而Help按鈕則顯示一段文本用來大概描述這個對話框(當然,在實際的程序中,這個按鈕應該調用一個更漂亮的格式化過的幫助文件).
一個好的用戶界面應該不允許用戶進行當前上下文中不允許的操作.在這個例子中,如果年齡的值小于18,則投票按鈕應該是不可用的(根據英國或者美國的法律).
派生一個新類
下面的代碼定義了這個新的對話框類:PersonalRecordDialog,我們使用DECLARE_CLASS宏來提供運行期類型信息,而使用DECLARE_EVENT_TABLE宏來定義了一個事件表.
```
/*!
* PersonalRecordDialog類定義
*/
class PersonalRecordDialog: public wxDialog
{
DECLARE_CLASS( PersonalRecordDialog )
DECLARE_EVENT_TABLE()
public:
// 構造函數
PersonalRecordDialog( );
PersonalRecordDialog( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& caption = wxT("Personal Record"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU );
//用來初始化內部變量
void Init();
// 創建窗體
bool Create( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& caption = wxT("Personal Record"),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU );
// 創建普通控件和布局控件
void CreateControls();
};
```
依照wxWidgets的慣例,我們同時支持了單步創建和兩步窗口的方式,單步創建使用的是一個復雜的構造函數,而兩步創建則使用的是一個簡單的額構造函數和一個復雜的Create函數.
設計數據存儲
我們有四個數據要存儲:姓名(字符串),年齡(整數),性別(bool)和是否投票(bool).因為我們想用選擇框來作為性別的用戶界面,所以我們將性別的類型更改為整數類型,但是它可以對外表現為bool類型. 現在讓我們來給PersonalRecordDialog類增加這些成員:
```
// 數據成員
wxString m_name;
int m_age;
int m_sex;
bool m_vote;
// 姓名訪問控制
void SetName(const wxString& name) { m_name = name; }
wxString GetName() const { return m_name; }
// 年齡訪問控制
void SetAge(int age) { m_age = age; }
int GetAge() const { return m_age; }
// 性別訪問控制 (男性 = false, 女性 = true)
void SetSex(bool sex) { sex ? m_sex = 1 : m_sex = 0; }
bool GetSex() const { return m_sex == 1; }
// 是否投票?
void SetVote(bool vote) { m_vote = vote; }
bool GetVote() const { return m_vote; }
```
編碼產生控件和布局
現在我們增加一個CreateControls函數來創建控件,這個函數將被Create函數調用,它將增加一個靜態文本框,按鈕,一個 wxSpinCtrl控件和一個wxTextCtrl控件,一個wxChoice控件,一個wxCheckBox控件,參見上圖中的最終效果.
我們使用了布局控件來進行布局,這是為什么它比你想像中的顯得復雜了一點的原因(你應該還記得布局控件,我們在第7章對齊進行了描述, 簡單的說就是讓你的布局擁有隨主窗口的大小,語言以及平臺的變化而變化的能力).當然你也可以使用別的方法來進行布局,比如你可以使用從 wxWidgets資源文件(XRC)中讀取布局的方法.
我們來大概刷新一下你關于布局控件的記憶,布局控件把所有的控件分為一個一個的小格子,每個格子剛好可以放置一個控件,這些控件雖然可以擁有非常復雜的布局繼承關系,但是它們的窗口繼承關系非常單純,都是繼承自同一個父窗口.你可以參考第7章中的第二幅圖來刷新你的記憶.
在CreateControls函數中,我們使用了一個垂直盒子布局控件嵌套了另外一個垂直盒子布局控件以便使對話框產生邊界.一個水平的盒子布局控件用來放置wxSpinCtrl, wxChoice和wxCheckBox,以及另外一個水平盒子布局控件來放置四個按鈕.
```
/*!
* PersonalRecordDialog控件創建
*/
void PersonalRecordDialog::CreateControls()
{
// 一個頂層的布局控件
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
this->SetSizer(topSizer);
// 第二個頂層布局控件用來產生邊界
wxBoxSizer* boxSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(boxSizer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
// 一個友善的提示文本
wxStaticText* descr = new wxStaticText( this, wxID_STATIC,
wxT("Please enter your name, age and sex, and specify whether you wish to\nvote in
a general election."), wxDefaultPosition, wxDefaultSize, 0 );
boxSizer->Add(descr, 0, wxALIGN_LEFT|wxALL, 5);
// 空格
boxSizer->Add(5, 5, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
// 產生靜態文本
wxStaticText* nameLabel = new wxStaticText ( this, wxID_STATIC,
wxT("&Name:"), wxDefaultPosition, wxDefaultSize, 0 );
boxSizer->Add(nameLabel, 0, wxALIGN_LEFT|wxALL, 5);
// 一個用于輸入用戶名的文本框
wxTextCtrl* nameCtrl = new wxTextCtrl ( this, ID_NAME, wxT("Emma"), wxDefaultPosition,
wxDefaultSize, 0 );
boxSizer->Add(nameCtrl, 0, wxGROW|wxALL, 5);
// 一個水平布局控件用來放置年齡,性別和是否投票
wxBoxSizer* ageSexVoteBox = new wxBoxSizer(wxHORIZONTAL);
boxSizer->Add(ageSexVoteBox, 0, wxGROW|wxALL, 5);
// 年齡控件的標簽
wxStaticText* ageLabel = new wxStaticText ( this, wxID_STATIC,
wxT("&Age:"), wxDefaultPosition, wxDefaultSize, 0 );
ageSexVoteBox->Add(ageLabel, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// 用于輸入年齡的spin控件
wxSpinCtrl* ageSpin = new wxSpinCtrl ( this, ID_AGE,
wxEmptyString, wxDefaultPosition, wxSize(60, -1),
wxSP_ARROW_KEYS, 0, 120, 25 );
ageSexVoteBox->Add(ageSpin, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// 性別標簽
wxStaticText* sexLabel = new wxStaticText ( this, wxID_STATIC,
wxT("&Sex:"), wxDefaultPosition, wxDefaultSize, 0 );
ageSexVoteBox->Add(sexLabel, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// 性別選擇框
wxString sexStrings[] = {
wxT("Male"),
wxT("Female")
};
wxChoice* sexChoice = new wxChoice ( this, ID_SEX,
wxDefaultPosition, wxSize(80, -1), WXSIZEOF(sexStrings),
sexStrings, 0 );
sexChoice->SetStringSelection(wxT("Female"));
ageSexVoteBox->Add(sexChoice, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// 增加一個可拉升的空白區域
// 以便讓投票選項出現在右邊
ageSexVoteBox->Add(5, 5, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxCheckBox* voteCheckBox = new wxCheckBox( this, ID_VOTE,
wxT("&Vote"), wxDefaultPosition, wxDefaultSize, 0 );
voteCheckBox ->SetValue(true);
ageSexVoteBox->Add(voteCheckBox, 0,
wxALIGN_CENTER_VERTICAL|wxALL, 5);
// OK和Cancel按鈕之間的分割線
wxStaticLine* line = new wxStaticLine ( this, wxID_STATIC,
wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
boxSizer->Add(line, 0, wxGROW|wxALL, 5);
// 用來放置四個按鈕的水平盒子布局控件
wxBoxSizer* okCancelBox = new wxBoxSizer(wxHORIZONTAL);
boxSizer->Add(okCancelBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
// Reset按鈕
wxButton* reset = new wxButton( this, ID_RESET, wxT("&Reset"),
wxDefaultPosition, wxDefaultSize, 0 );
okCancelBox->Add(reset, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// Ok按鈕
wxButton* ok = new wxButton ( this, wxID_OK, wxT("&OK"),
wxDefaultPosition, wxDefaultSize, 0 );
okCancelBox->Add(ok, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// Cancel按鈕
wxButton* cancel = new wxButton ( this, wxID_CANCEL,
wxT("&Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
okCancelBox->Add(cancel, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
// Help按鈕
wxButton* help = new wxButton( this, wxID_HELP, wxT("&Help"),
wxDefaultPosition, wxDefaultSize, 0 );
okCancelBox->Add(help, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
}
```
數據傳輸和驗證
現在我們已經大概建立起來了這個對話框,但是控件和它的內部成員還沒有連接起來,怎樣完成這種連接呢?
當對話框第一次顯示的時候,wxWidgets會調用InitDialog函數,這個函數產生一個wxEVT_INIT_DIALOG事件.這個事件默認的處理函數是調用transferDataToWindow函數.要把數據從控件傳回變量中,你可以在用戶確認輸入的時候調用 transferDataFromWindow函數,wxWidgets定義了一個默認的wxID_OK命令事件的處理函數,這個函數會在 EndModal函數調用之前調用transferDataFromWindow函數.
因此,你可以通過重載transferDataToWindow和transferDataFromWindow函數以實現數據傳輸,對于我們這個對話框來說,你可以使用象下面這樣的代碼:
```
/*!
* 傳輸數據到控件
*/
bool PersonalRecordDialog::TransferDataToWindow()
{
wxTextCtrl* nameCtrl = (wxTextCtrl*) FindWindow(ID_NAME);
wxSpinCtrl* ageCtrl = (wxSpinCtrl*) FindWindow(ID_SAGE);
wxChoice* sexCtrl = (wxChoice*) FindWindow(ID_SEX);
wxCheckBox* voteCtrl = (wxCheckBox*) FindWindow(ID_VOTE);
nameCtrl->SetValue(m_name);
ageCtrl->SetValue(m_age);
sexCtrl->SetSelection(m_sex);
voteCtrl->SetValue(m_vote);
return true;
}
/*!
* 傳輸數據到變量
*/
bool PersonalRecordDialog::TransferDataFromWindow()
{
wxTextCtrl* nameCtrl = (wxTextCtrl*) FindWindow(ID_NAME);
wxSpinCtrl* ageCtrl = (wxSpinCtrl*) FindWindow(ID_SAGE);
wxChoice* sexCtrl = (wxChoice*) FindWindow(ID_SEX);
wxCheckBox* voteCtrl = (wxCheckBox*) FindWindow(ID_VOTE);
m_name = nameCtrl->GetValue();
m_age = ageCtrl->GetValue();
m_sex = sexCtrl->GetSelection();
m_vote = voteCtrl->GetValue();
return true;
}
```
然而,還有一個更容易的方法.wxWidgets支持驗證器,所謂驗證器是一個把數據變量和對應的控件聯系起來的對象.雖然不總是可以使用,但是只要可以使用,總是可以節省你大量的時間和代碼來進行數據的傳輸和驗證.在我們這個例子中,我們可以通過下面的代碼來代替上面的額兩個函數:
```
FindWindow(ID_NAME)->SetValidator(
wxTextValidator(wxFILTER_ALPHA, & m_name));
FindWindow(ID_AGE)->SetValidator(
wxGenericValidator(& m_age));
FindWindow(ID_SEX)->SetValidator(
wxGenericValidator(& m_sex);
FindWindow(ID_VOTE)->SetValidator(
wxGenericValidator(& m_vote);
```
這幾行代碼可以放在CreateControls函數的最后以便代替前面的兩個函數,作為一個額外的好處,這樣寫代碼還將增加校驗以便用戶不可以在姓名框中輸入數字.
驗證器可以執行兩個任務,除了可以傳輸數據,它們還可以對數據進行校驗.并且在數據不合法的時候進行錯誤提示.在這個例子中,除了姓名框以外,別的輸入控件都沒有設置校驗.wxGenericValidator是一個很簡單的驗證器,它只負責傳輸數據,不對數據進行校驗,也正因為如此,它支持更多的基本控件.別的驗證器則象wxTextValidator一樣,提供了各種的校驗方法,比如wxTextValidator就可以阻止那些不合法的按鍵事件傳入文本框控件.在這個例子中,我們只是使用了標準校驗類型wxFILTER_ALPHA,但是通過驗證器的SetIncludes和 SetExcludes函數,我們還可以指定具體哪些字符是允許的哪些是不允許的.
我們還需要略微深入一點的來討論wxWidgets怎樣處理驗證過程以便你更好的理解這兒到底發生了什么.正如我們看到的那樣,默認的 OnOk函數調用了TransferDataToWindow函數,但是在調用這個函數之前,它還調用了另外一個函數Validate,下面的代碼是默認的OnOK的代碼:
```
void wxDialog::OnOK(wxCommandEvent& event)
{
if ( Validate() && TransferDataFromWindow() )
{
if ( IsModal() )
EndModal(wxID_OK); // 如果是模式對話框
else
{
SetReturnCode(wxID_OK);
this->Show(false); // 如果是非模式對話框
}
}
}
```
而默認的Validate函數的實現是遍歷對話框所有的直接子窗口(如果設置了 wxWS_EX_VALIDATE_RECURSIVELY類型的化,也會調用子窗口的子窗口),依次調用它們的Validate函數,如果任何一個調用失敗,則整個Validate過程失敗,那么對話框將不會被關閉,并且驗證器自己會提供一個消息框以說明失敗的原因.
類似的,TransferDataToWindow和TransferDataFromWindow也將自動調用所有子控件的對應的函數.一個驗證器可以不做校驗,但是必須要可以支持作數據傳輸.
一個驗證器其實是一個事件處理類,wxWidgets的事件處理機制在將事件傳遞給控件本身之前,會檢查這個控件是否被設置了驗證器,如果已經設置了,則首先將這個事件傳遞給驗證器,以便驗證器可以通過Veto等方法阻止不合法的按鍵事件.在這種情況下,通常驗證器應該調用Beep函數發出一聲告警信號以便提示用戶某個鍵已經被按下了但是是非法的.
wxWidgets提供的兩種驗證器可能是不足夠的,尤其是在你想創建自己的控件的時候,這種情況下你可能希望能夠創建自己的驗證器. 你需要從wxValidator類派生一個新類,這個新類必須帶有一個自拷貝的構造函數和一個Clone函數用來返回自己的一份拷貝,還要實現自己的數據傳輸和驗證機制.通常一個驗證器都需要存儲一個指向某個C++變量的指針,并且構造函數中允許指定不同的模式以實現不同的驗證機制.你可以參考 wxWidgets源代碼中的include/wx/valtext.h文件和src/common/valtext.cpp文件以了解怎樣實現一個自己的驗證器,也可以參考第12章,"高級窗口類"中的"制作你自己的控件"這一小節.
處理事件
在這個例子中,wxWidgets默認的處理對于OK和Cancel按鈕來說已經是足夠,不需要額外添加任何代碼,只需要將其設置為對應的wxID_OK和wxID_CANCEL的標識符.不過,在自定義對話框中,進行相應的事件處理是很經常的事情.在這個例子中我們還有一個Reset按鈕,當這個按鈕被點擊時,我們需要復位所有控件的值為它們的默認值.因此我們需要增加OnResetClick事件處理函數,以及一個對應的事件表條目. 這個處理函數的實現是非常簡單的,首先我們調用我們自定義的Init成員函數來初始化所有的成員,然后調用TransferDataToWindow函數將數據傳輸到控件,如下所示:
```
BEGIN_EVENT_TABLE( PersonalRecordDialog, wxDialog )
...
EVT_BUTTON( ID_RESET, PersonalRecordDialog::OnResetClick)
...
END_EVENT_TABLE()
void PersonalRecordDialog::OnResetClick( wxCommandEvent& event )
{
Init();
TransferDataToWindow();
}
```
處理UI更新
GUI程序員設計代碼的一大挑戰是確保所有控件在其不可用的時候都是被禁用的.沒有什么比彈出一個對話框告訴用戶"這個按鈕目前是不可用的"更能破壞程序的緊湊性的了.如果一個選項是不可用的,那么它看上去就應該是不可用的,用戶點擊它或者對它進行任何輸入應該都是沒有效果的.因此,我們應該在這種情況發生的時候,盡可能快的更新控件的狀態使其不可用或者重新可用.
在我們這個例子里面,如果用戶輸入的年齡小于18歲,我們必須禁用投票按鈕,使得這個選項對用戶來說是不可用的.你的第一想法可能是要增加一個對于spin控件的事件的更新事件處理函數,并且在其中根據它的值來禁用或者可用投票復選框控件.當然,對于簡單的情況來說,這樣作完全沒有問題但是試想一下,如果有很多種控件存在這種復雜的關聯,或者更壞的情況,有時候我們根本不可能在影響某個控件的條件發生改變的時候獲得事件通知,比如說我們的剪貼按鈕,可能要隨著剪貼板的有無數據情況來進行可用或者不可用的操作,而剪貼板是系統變量,它可能受別的應用程序的影響,因為我們自己的應用程序很難在其發生改變的時候收到通知事件.這種情況下唉該怎么辦呢?
為了解決這個問題,wxWidgets提供了一個稱為wxUpdateUIEvent的事件類,這個事件實在系統空閑的時候(所有其它的事件都已經處理完的時候)發送給應用程序的.你可以使用EVT_UPDATE_UI宏來攔截這個事件,對應于每一個要和別的控件關聯改變狀態的控件,你需要在事件表中對應增加一個條目,每一個對應的處理函數中,可以檢測這個世界當前的狀況,從而改變某個控件當前的狀態:允許或者禁止,選中或者未選中等, 這種機制允許你將某個控件相關的所有因素在某個特定的時機放在一個函數中處理.你可以松一口氣了,因為你不必記住在每一個相關連的事件發生改變的時候去更新對應的控件了.
下面是我們用來處理UI更新事件的相關代碼,要注意在代碼中我們不能直接判斷m_age成員,因為這個成員只有在用戶點擊了OK按鈕以后才會將控件的數據傳遞過來.
```
BEGIN_EVENT_TABLE( PersonalRecordDialog, wxDialog )
...
EVT_UPDATE_UI( ID_VOTE, PersonalRecordDialog::OnVoteUpdate )
...
END_EVENT_TABLE()
void PersonalRecordDialog::OnVoteUpdate( wxUpdateUIEvent& event )
{
wxSpinCtrl* ageCtrl = (wxSpinCtrl*) FindWindow(ID_AGE);
if (ageCtrl->GetValue() < 18)
{
event.Enable(false);
event.Check(false);
}
else
event.Enable(true);
}
```
會不會有大量的這種界面更新事件而導致程序的性能降低呢?首先,這種情況是存在的,不過你不必過分擔心這個問題,wxWidgets已經作了大量的優化工作,使得這種系統開銷將至了最低點.不過如果你的程序真的對性能有很大的要求并且真實感覺是因為這個界面更新的原因導致損失了性能,你還是可以通過wxUpdateUIEvent相關文檔中提到的SetMode函數和SetUpdateInterval函數來對這種行為進行進一步的控制, 以減少界面更新事件的消耗的時間.
增加幫助信息
至少有三種幫助信息你可以給你的對話框提供.
* 工具提示
* 上下文敏感幫助
* 聯機幫助
當然,你還可以使用更多更先進的沒有被wxWidgets顯式支持的技術. 我們已經在對話框上邊放置了一段用來大概描述這個對話框用途的文本,對于更復雜的對呼框來說,你可以考慮使用wxHtmlWindow代替普通的文本以便可以加載一個HTML文件來提供更復雜的幫助信息.或者,你還可以在每個按鈕旁邊放置一個小按鈕用來提供針對性的幫助信息.
下面我們來說一說wxWidgets明確支持的三種幫助形式:
工具提示
工具提示是在鼠標劃過某個控件的時候彈出的一個小窗口,窗口里包含對這個控件用途的一個簡短的提示.你可以使用SetToolTip函數來設置控件對應的工具提示.不過,由于對于一些有經驗的使用者來說,這個提示可能會顯得有些討厭,因此你應該給你的應用程序增加一個全局的設置以便不顯示這些工具提示(這里的意思是說,這個設置使得在對話框創建的時候不調用SetToolTip函數).
上下文敏感幫助
上下文敏感幫助提供的彈出窗口和工具提示窗口是非常相似的,不過這個窗口并不會自動探出來,而需要用戶在點擊了某個特殊按鈕以后再點擊對應的控件,或者在某個控件獲得焦點的時候按F1鍵(windows平臺適用)的時候才會彈出.在windows平臺上,你可以在創建對話框的時候指定 wxDIALOG_EX_CONTEXTHELP類型以便在標題欄上出現這個上下文敏感幫助按鈕,在其它平臺上,你可以創建一個 wxContextHelpButton類型的按鈕,這個按鈕通常應該位于OK和Cancel按鈕的旁邊.然后,在對話框初始話的時候,增加下面的代碼:
```
#include "wx/cshelp.h"
wxHelpProvider::Set(new wxSimpleHelpProvider);
這將告訴wxWidgets怎樣提供上下文敏感幫助,然后調用SetHelpText來設置某個控件的上下文敏感幫助文本.下面是我們例子中設置這兩中幫助的代碼:
void PersonalRecordDialog::SetDialogHelp()
{
wxString nameHelp = wxT("Enter your full name.");
wxString ageHelp = wxT("Specify your age.");
wxString sexHelp = wxT("Specify your gender, male or female.");
wxString voteHelp = wxT("Check this if you wish to vote.");
FindWindow(ID_NAME)->SetHelpText(nameHelp);
FindWindow(ID_NAME)->SetToolTip(nameHelp);
FindWindow(ID_AGE)->SetHelpText(ageHelp);
FindWindow(ID_AGE)->SetToolTip(ageHelp);
FindWindow(ID_SEX)->SetHelpText(sexHelp);
FindWindow(ID_SEX)->SetToolTip(sexHelp);
FindWindow(ID_VOTE)->SetHelpText(voteHelp);
FindWindow(ID_VOTE)->SetToolTip(voteHelp);
}
```
如果你希望自己控制上下文敏感幫助,而不想通過對話框的上下文敏感幫助按鈕或者wxContextHelpButton按鈕,你可以在任何事件處理函數中使用下面的代碼:
```
wxContextHelp contextHelp(window);
```
這將使wxWidgets進入一個檢測左鍵單擊的死循環,當單擊事件發生時,wxWidgets給對應的控件發送wxEVT_HELP事件,你可以攔截這個事件以便彈出你自己的幫助窗口.
你不必將自己限制于wxWidgets實現的存儲顯式幫助文本的方式,你可以創建自己的wxHelpProvider派生類,進而實現自己的GetHelp, SetHelp, AddHelp, RemoveHelp和ShowHelp函數.
聯機幫助
大多數應用程序都會在一個幫助文件中提供使用應用程序的詳細說明.wxWidgets也對應于這種應用提供了幾個對應的控件,這些控件都是wxHelpControllerBase的派生類.參考第20章,"優化你的應用程序"來獲得這方面更詳細的信息.
針對我們這個應用程序而言,我們只是簡單的在用戶點擊幫助按鈕的時候顯式一個消息框.
```
BEGIN_EVENT_TABLE( PersonalRecordDialog, wxDialog )
...
EVT_BUTTON( wxID_HELP, PersonalRecordDialog::OnHelpClick )
...
END_EVENT_TABLE()
void PersonalRecordDialog::OnHelpClick( wxCommandEvent& event )
{
// 通常我們需要用下面注釋的代碼提供聯機幫助
/*
wxGetApp().GetHelpController().DisplaySection(wxT("Personal record dialog"));
*/
// 在這個例子中,我們只簡單的提供一個消息框
wxString helpText =
wxT("Please enter your full name, age and gender.\n")
wxT("Also indicate your willingness to vote in general elections.\n\n")
wxT("No non-alphabetical characters are allowed in the name field.\n")
wxT("Try to be honest about your age.");
wxMessageBox(helpText,
wxT("Personal Record Dialog Help"),
wxOK|wxICON_INFORMATION, this);
}
```
完整的例子
這個例子完整的代碼列舉在附錄J,"代碼列表"中,你也可以在附送光盤的examples/chap09找到.
調用這個對話框
現在我們需要調用這個對話框來完成所有的編碼,我們可以使用下面的代碼來調用這個對話框:
```
PersonalRecordDialog dialog(NULL, ID_PERSONAL_RECORD,
wxT("Personal Record"));
dialog.SetName(wxEmptyString);
dialog.SetAge(30);
dialog.SetSex(0);
dialog.SetVote(true);
if (dialog.ShowModal() == wxID_OK)
{
wxString name = dialog.GetName();
int age = dialog.GetAge();
bool sex = dialog.GetSex();
bool vote = dialog.GetVote();
}
```
- 第一章 介紹
- 1.1 為什么要使用wxWidgets?
- 1.2 wxWidgets的歷史
- 1.3 wxWidgets社區
- 1.4 wxWidgets和面向對象編程
- 1.5 wxWidgets的體系結構
- 1.6 許可協議
- 第一章小結
- 第二章 開始使用
- 2.1 一個小例子
- 2.2 應用程序類
- 2.3 Frame窗口類
- 2.4 事件處理函數
- 2.5 Frame窗口的構造函數
- 2.6 完整的例子
- 2.7 wxWidgets程序一般執行過程
- 2.8 編譯和運行程序
- 第二章小結
- 第三章 事件處理
- 3.1 事件驅動編程
- 3.2 事件表和事件處理過程
- 3.3 過濾某個事件
- 3.4 掛載事件表
- 3.5 動態事件處理方法
- 3.6 窗口標識符
- 3.7 自定義事件
- 第三章小結
- 第四章 窗口的基礎知識
- 4.1 窗口解析
- 4.2 窗口類概覽
- 4.3 基礎窗口類
- 4.4 頂層窗口
- 4.5 容器窗口
- 4.6 非靜態控件
- 4.7 靜態控件
- 4.8 菜單
- 4.9 控制條
- 第四章小結
- 第五章繪畫和打印
- 5.1 理解設備上下文
- 5.2 繪畫工具
- 5.3 設備上下文中的繪畫函數
- 5.4 使用打印框架
- 5.5 使用wxGLCanvas繪制三維圖形
- 第五章小節
- 第六章處理用戶輸入
- 6.1 鼠標輸入
- 6.2 處理鍵盤事件
- 6.3 處理游戲手柄事件
- 第六章小結
- 第七章使用布局控件進行窗口布局
- 7.1 窗口布局基礎
- 7.2 窗口布局控件
- 7.3 使用布局控件進行編程
- 7.4 更多關于布局的話題
- 第七章小結
- 第八章使用標準對話框
- 8.1信息對話框
- 8.2 文件和目錄對話框
- 8.3 選擇和選項對話框
- 8.4 輸入對話框
- 8.5 打印對話框
- 第八章小結
- 第九章創建定制的對話框
- 9.1 創建定制對話框的步驟
- 9.2 一個例子:PersonalRecordDialog
- 9.3 在小型設備上調整你的對話框
- 9.4 一些更深入的話題
- 9.5 使用wxWidgets資源文件
- 第九章小結
- 第十章使用圖像編程
- 10.1 wxWidgets中圖片相關的類
- 10.2 使用wxBitmap編程
- 10.3 使用wxIcon編程
- 10.4 使用wxCursor編程
- 10.5 使用wxImage編程
- 10.6 圖片列表和圖標集
- 10.7 自定義wxWidgets提供的小圖片
- 第十章小結
- 第十一章剪貼板和拖放操作
- 11.1 數據對象
- 11.2 使用剪貼板
- 11.3 實現拖放操作
- 第十一章小結
- 第十二章高級窗口控件
- 12.1 wxTreeCtrl
- 12.2 wxListCtrl
- 12.3 wxWizard
- 12.4 wxHtmlWindow
- 12.5 wxGrid
- 12.6 wxTaskBarIcon
- 12.7 編寫自定義的控件
- 第十二章小結
- 第十三章數據結構類
- 13.1 為什么沒有使用STL?
- 13.2 字符串類型
- 13.3 wxArray
- 13.4 wxList和wxNode
- 13.5 wxHashMap
- 13.6 存儲和使用日期和時間
- 13.7 其它常用的數據類型
- 第十三章小結
- 第十四章文件和流操作
- 14.1 文件類和函數
- 14.2 流操作相關類
- 第十四章小結
- 第十五章內存管理,調試和錯誤處理
- 15.1 內存管理基礎
- 15.2 檢測內存泄漏和其它錯誤
- 15.3 構建自防御的程序
- 15.4 錯誤報告
- 15.5 提供運行期類型信息
- 15.6 使用wxModule
- 15.7 加載動態鏈接庫
- 15.8 異常處理
- 15.9 調試提示
- 第十五章小結
- 第十六章編寫國際化程序
- 16.1 國際化介紹
- 16.2 從翻譯說起
- 16.3 字符編碼和Unicode
- 16.4 數字和日期
- 16.5 其它媒介
- 16.6 一個小例子
- 第十六章小結
- 第十七章編寫多線程程序
- 17.1 什么時候使用多線程,什么時候不要使用
- 17.2 使用wxThread
- 17.3 用于線程同步的對象
- 17.4 多線程的替代方案
- 第十七章小結
- 第十八章使用wxSocket編程
- 18.1 Socket類和功能概覽
- 18.2 Socket及其基本處理介紹
- 18.3 Socket標記
- 18.4 使用Socket流
- 18.5 替代wxSocket
- 第十八章小結
- 第十九章使用文檔/視圖框架
- 19.1 文檔/視圖基礎
- 19.2 文檔/視圖框架的其它能力
- 19.3 實現Undo/Redo的策略
- 第十九章小結
- 第二十章完善你的應用程序
- 20.1 單個實例和多個實例
- 20.2 更改事件處理機制
- 20.3 降低閃爍
- 20.4 實現聯機幫助
- 20.5 解析命令行參數
- 20.6 存儲應用程序資源
- 20.7 調用別的應用程序
- 20.8 管理應用程序設置
- 20.9 應用程序安裝
- 20.10 遵循用戶界面設計規范
- 20.11 全書小結