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();
}