# 12.3 wxWizard
使用向導是將一堆復雜的選項變成一系列相對簡單的對話框的一個好方法.它通常用來幫助應用程序的使用新手們開始使用某個特定的功能,比如,搜集建立新工程需要的信息,導出數據等等.向導中的選項通常都在應用程序別的用戶界面上有體現,但是提供一個向導以便用戶可以專注于那些為了完成某個任務必須要設置的選項.
向導控件通常在一個窗口內提供一系列的類似對話框的窗口,這些窗口的左邊都擁有一副圖片(可以是一樣的也可以是不一樣的),而底部則通常用一系列導航按鈕,隨著用戶的選擇進入預先設置好的下一頁面,下一頁面的索引是隨用戶的選擇而變化的,因此不是所有的頁面都會在一次向導執行過程中全部顯示.
當標準的向導導航按鈕被按下時,將會產生對應的事件,向導類或者其派生類可以捕獲相應的事件.
要顯示一個向導,你需要先創建一個向導(或者其派生類),然后創建子頁面(作為向導的子窗口).你可以使用 wxWizardPageSimple類(或其派生類)來創建向導頁面,然后使用wxWizardPageSimple::Chain函數將其和一個向導綁定.或者,如果你需要動態調整頁面的順序,你可以使用wxWizardPage的派生類,重載其GetPrev和GetNext函數. 然后將每一個頁面增加到GetPageAreaSizer返回的布局控件中,以便向導控件自動將自己的大小調整到最大頁面的大小.
向導控件只額外定義了wxWIZARD_EX_HELPBUTTON擴展類型,以便在標準向導導航按鈕中增加幫助按鈕.注意這是一個擴展類型,需要在Create之前使用SetExtraStyle來進行設置.
wxWizard事件
wxWizard產生wxWizardEvent類型的事件,如下表所示,這些事件將首先被發送到當前頁面,如果頁面沒有定義處理函數, 則發送到向導本身.除了EVT_WIZARD_FINISHED事件以外,別的事件都可以調用wxWizardEvent::GetPage函數返回當前頁面.
| EVT_WIZARD_PAGE_CHANGED(id, func) | 當導航頁面已發生變化的時候產生,使用wxWizardEvent::GetDirection函數判斷方向(true為向前). |
|:--- |:--- |
| EVT_WIZARD_PAGE_CHANGING(id, func) | 當導航頁面即將變化的時候產生,這個事件可以被Veto以便取消這個事件.同樣可以使用wxWizardEvent::GetDirection判斷方向(true為向前). |
| EVT_WIZARD_CANCEL(id, func) | 用戶點擊取消按鈕的時候產生,這個事件可以被Veto(使得事件導致的操作無效). |
| EVT_WIZARD_HELP(id, func) | 用戶點擊幫助按鈕的時候產生. |
| EVT_WIZARD_FINISHED(id, func) | 用戶點擊完成按鈕的時候產生.這個事件產生的時間是在向導對話框剛剛關閉以后. |
wxWizard的成員函數
GetPageAreaSizer函數返回用戶管理所有頁面的布局控件.你需要將所有的頁面增加到這個布局控件中,或者將某一個可以通過 GetNext函數訪問到其它所有頁面的頁面增加到布局控件中,以便向導控件可以知道最大的頁面的大小.如果你沒有這樣作,你需要在顯示向導時在第一個頁面顯示之前調用其FitToPage函數,如果wxWizardPage::GetNext不能訪問到所有的頁面,你需要對每個頁面調用 FitToPage函數.
GetCurrentPage函數返回當前活動的頁面,如果RunWizard函數還沒有被執行則返回NULL.
GetPageSize當前設置的頁面大小. SetPageSize則用來設置所有頁面使用的頁面大小,不過最好還是將頁面增加到GetPageAreaSizer布局控件中來決定頁面大小比較合適.
調用RunWizard,傳遞要顯示的第一個頁面作為參數,以便將向導置于執行狀態.如果向導執行成功這個函數返回True,如果用戶取消了向導則返回False.
可以用SetBorder函數設置向導邊界的大小,默認為0\.
wxWizard使用舉例
我們來看看wxWidgets自帶的向導例子.它包含四個頁面,如下圖所示(頁面索引并沒有顯示在對話框上,這樣說只是為了清晰).




第一個頁面非常簡單,它不需要實現任何派生類,只是簡單的創建了一個wxWizardPageSimple類的實例,然后在其中增加了一個靜態文本標簽,如下所示:
```
#include "wx/wizard.h"
wxWizard *wizard = new wxWizard(this, wxID_ANY,
wxT("Absolutely Useless Wizard"),
wxBitmap(wiztest_xpm),
wxDefaultPosition,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
// 第一頁
wxWizardPageSimple *page1 = new wxWizardPageSimple(wizard);
wxStaticText *text = new wxStaticText(page1, wxID_ANY,
wxT("This wizard doesn't help you\nto do anything at all.\n")
wxT("\n")
wxT("The next pages will present you\nwith more useless controls."),
wxPoint(5,5));
```
第二頁則實現了一個wxWizardPage的派生類,重載了其GetPrev和GetNext函數.前者總是返回第一頁,而后者則可以根據用戶的選擇返回下一頁或者最后一頁.其聲明和實現如下所示:
```
// 演示怎樣動態改變頁面順序
// 第二頁
class wxCheckboxPage : public wxWizardPage
{
public:
wxCheckboxPage(wxWizard *parent,
wxWizardPage *prev,
wxWizardPage *next)
: wxWizardPage(parent)
{
m_prev = prev;
m_next = next;
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(
new wxStaticText(this, wxID_ANY, wxT("Try checking the box below and\n")
wxT("then going back and clearing it")),
0, // 不需要垂直拉伸
wxALL,
5 // 邊界寬度
);
m_checkbox = new wxCheckBox(this, wxID_ANY,
wxT("&Skip the next page"));
mainSizer->Add(
m_checkbox,
0, // 不需要垂直拉伸
wxALL,
5 // 邊界寬度
);
SetSizer(mainSizer);
mainSizer->Fit(this);
}
// 重載wxWizardPage函數
virtual wxWizardPage *GetPrev() const { return m_prev; }
virtual wxWizardPage *GetNext() const
{
return m_checkbox->GetValue() ? m_next->GetNext() : m_next;
}
private:
wxWizardPage *m_prev,
*m_next;
wxCheckBox *m_checkbox;
};
```
第三頁實現了一個wxRadioboxPage類,它攔截取消向導和頁面改變事件.如果你視圖在這個頁面取消向導,它會詢問你是否真的要取消,如果你選擇否,則它將使用事件的Veto函數來取消這個操作.OnWizardPageChanging函數則攔截所有的頁面改變事件,并根據當前單選框的選項來確定是否允許頁面改變.在實際應用程序中,你可以使用這種技術來確保向導在某一頁的時候必須填充某些必須的域,否則不可以前進到下一頁或者你可以出于某種原因阻止用戶返回以前的頁面.代碼列舉如下:
```
// 我們演示了另外一個稍微復雜一些的例子,通過攔截相應事件阻止用戶向前或者向后翻頁
// 或者讓用戶確認取消操作.
// 第三頁
class wxRadioboxPage : public wxWizardPageSimple
{
public:
// 方向枚舉值
enum
{
Forward, Backward, Both, Neither
};
wxRadioboxPage(wxWizard *parent) : wxWizardPageSimple(parent)
{
// 應該和上面的枚舉值對應
static wxString choices[] = { wxT("forward"), wxT("backward"), wxT("both"), wxT
("neither") };
m_radio = new wxRadioBox(this, wxID_ANY, wxT("Allow to proceed:"),
wxDefaultPosition, wxDefaultSize,
WXSIZEOF(choices), choices,
1, wxRA_SPECIFY_COLS);
m_radio->SetSelection(Both);
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(
m_radio,
0, // 不伸縮
wxALL,
5 // 邊界
);
SetSizer(mainSizer);
mainSizer->Fit(this);
}
// 事件處理函數
void OnWizardCancel(wxWizardEvent& event)
{
if ( wxMessageBox(wxT("Do you really want to cancel?"), wxT("Question"),
wxICON_QUESTION | wxYES_NO, this) != wxYES )
{
// 不確認,取消
event.Veto();
}
}
void OnWizardPageChanging(wxWizardEvent& event)
{
int sel = m_radio->GetSelection();
if ( sel == Both )
return;
if ( event.GetDirection() && sel == Forward )
return;
if ( !event.GetDirection() && sel == Backward )
return;
wxMessageBox(wxT("You can't go there"), wxT("Not allowed"),
wxICON_WARNING | wxOK, this);
event.Veto();
}
private:
wxRadioBox *m_radio;
DECLARE_EVENT_TABLE()
};
```
第四頁也是最后一頁,wxValidationPage,演示了重載transferDataFromWindow函數以便對復選框控件進行數據校驗的方法.transferDataFromWindow在無論向前或者向后按鈕被點擊的時候都會被調用,而且如果這個函數返回失敗,將會取消向前或者向后操作.和所有的對話框用法一樣,你可以不必重載transferDataFromWindow函數而是給對應的控件設置一個驗證器.這個頁面還演示了怎樣更改作為向導構造函數的一個參數的默認的左圖片.下面是相關的代碼:
```
// 第四頁
class wxValidationPage : public wxWizardPageSimple
{
public:
wxValidationPage(wxWizard *parent) : wxWizardPageSimple(parent)
{
m_bitmap = wxBitmap(wiztest2_xpm);
m_checkbox = new wxCheckBox(this, wxID_ANY,
wxT("&Check me"));
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(
new wxStaticText(this, wxID_ANY,
wxT("You need to check the checkbox\n")
wxT("below before going to the next page\n")),
0,
wxALL,
5
);
mainSizer->Add(
m_checkbox,
0,
wxALL,
5
);
SetSizer(mainSizer);
mainSizer->Fit(this);
}
virtual bool TransferDataFromWindow()
{
if ( !m_checkbox->GetValue() )
{
wxMessageBox(wxT("Check the checkbox first!"),
wxT("No way"),
wxICON_WARNING | wxOK, this);
return false;
}
return true;
}
private:
wxCheckBox *m_checkbox;
};
```
下面的代碼用于將所有的頁面放在一起并且開始執行這個向導:
```
void MyFrame::OnRunWizard(wxCommandEvent& event)
{
wxWizard *wizard = new wxWizard(this, wxID_ANY,
wxT("Absolutely Useless Wizard"),
wxBitmap(wiztest_xpm),
wxDefaultPosition,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
// 向導頁面既可以是一個預定義對象的實例
wxWizardPageSimple *page1 = new wxWizardPageSimple(wizard);
wxStaticText *text = new wxStaticText(page1, wxID_ANY,
wxT("This wizard doesn't help you\nto do anything at all.\n")
wxT("\n")
wxT("The next pages will present you\nwith more useless controls."),
wxPoint(5,5)
);
// ... 也可以是一個派生類的實例
wxRadioboxPage *page3 = new wxRadioboxPage(wizard);
wxValidationPage *page4 = new wxValidationPage(wizard);
// 一種方便的設置頁面順序的方法
wxWizardPageSimple::Chain(page3, page4);
// 另外一種設置頁面順序的方法
wxCheckboxPage *page2 = new wxCheckboxPage(wizard, page1, page3);
page1->SetNext(page2);
page3->SetPrev(page2);
// 允許向導設置自適應的大小.
wizard->GetPageAreaSizer()->Add(page1);
if ( wizard->RunWizard(page1) )
{
wxMessageBox(wxT("The wizard successfully completed"),
wxT("That's all"), wxICON_INFORMATION | wxOK);
}
wizard->Destroy();
}
```
當向導被完成或者取消的時候,MyFrame攔截了相關的事件,在這個例子中,只是簡單的將其結果顯示在frame窗口的狀態條上.當然你也可以在向導類中攔截相應的事件.
完整版本的代碼可以在附錄J,"代碼列表"或者隨書光盤的examples/chap12目錄中找到.
- 第一章 介紹
- 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 全書小結