# 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自带的向导例子.它包含四个页面,如下图所示(页面索引并没有显示在对话框上,这样说只是为了清晰).
![](img/mhtEEF1%281%29.tmp)
![](img/mhtEF03%281%29.tmp)
![](img/mhtEF06%281%29.tmp)
![](img/mhtEF09%281%29.tmp)
第一个页面非常简单,它不需要实现任何派生类,只是简单的创建了一个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 全书小结