7.3 使用布局控件进行编程

现在我们开始使用布局控件进行窗口布局,首先,创建一个顶层的布局控件(任何类型的布局控件都可以),使用wxWindow::SetSizer函数将它和你的顶层窗口绑定.现在你可以在这个顶层布局控件中放置你的窗口或者其它控件元素了.如果你想让你的顶层窗口的大小适合所有控件所需要的大小,你可以调用wxSizer::Fit函数,将那个顶层窗口的指针作为其参数.想要顶层窗口在以后的执行过程中尺寸永远不小于初始尺寸,可以使用wxSizer:: SetSizeHints函数,将顶层窗口的指针作为参数,这将使得wxWindow::SetSizeHints函数以合适的参数被调用.

除了使用上面介绍的方法依次调用三个函数以外,你还可以直接通过调用wxWindow::SetSizerAndFit函数来达到同样的效果,这会使得上面的三个函数依次被调用.

如果在你的frame窗口里使用了panel,你可能不知道到底该给frame还是panel指定布局控件.这个问题应该这样看,如果你的frame窗口中只有一个panel,所有其它的窗口和控件都是panel的子窗口,那么wxWidgets已经知道怎样将这个panel以合适的大小和位置放置在frame上了,因此你只需要给panel绑定一个布局控件,以便其可以对所有panel的子窗口进行布局.而如果你的frame窗口中有多个panel,那么首先你不得不为frame绑定一个布局控件以便对panel进行布局,然后针对每个panel还应该绑定一个布局控件,以便对 panel中的子窗口进行布局.

接下来的小节里,我们来依次描述一下每一种布局控件类型以及使用它们的方法:

使用wxBoxSizer进行编程

wxBoxSizer可以将它的容器子元素进行横向或者纵向的排列(具体的排列方式在构造函数中指定).如果采用横向排列的方法,则子元素在纵向上可以指定居中,顶部对齐,底部对齐,如果采用纵向排列的方法,子元素在横向上可以指定居中,左对齐或者右对齐的方式.前一小节提到过的缩放因子用来指示在主要方向上的缩放,比如对于横向排列来说,缩放因子指的就是在横向上子元素的缩放比例. 下图演示了上一小节最后一幅图采用纵向排列的样子.

你可以使用wxBoxSizer的Add方法增加一个子元素:

// 增加一个窗口
void Add(wxWindow* window, int stretch = 0, int flags = 0,
         int border = 0);
// 增加一个布局控件
void Add(wxSizer* window, int stretch = 0, int flags = 0,
         int border = 0);

第一个参数是要增加的窗口或者布局控件的指针

第二个参数是前面说过的缩放因子

第三个参数是一个比特位列表,用来指示新增的子元素的对齐和边界的行为.对齐比特位用来指示当垂直排列的布局控件的宽度发生改变时子元素的水平对齐方式,或者是水平排列的布局控件的高度改变时子元素的垂直对齐方式,默认的值为 wxALIGN_LEFT | wxALIGN_TOP,可选的值列举在下表中:

0 子元素保留原始大小.
wxGROW 子元素随这布局控件一起改变大小. 等同于wxEXPAND.
wxSHAPED 子元素保持原有比例按缩放因子缩放.
wxALIGN_LEFT 左对齐.
wxALIGN_RIGHT 右对齐.
wxALIGN_TOP 顶端对齐.
wxALIGN_BOTTOM 底部对齐.
wxALIGN_CENTER_HORIZONTAL 水平居中.
wxALIGN_CENTER_VERTICAL 垂直居中.
wxALIGN_CENTER 水平或者垂直居中. wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL.
wxLEFT 边界间隔位于子元素左面.
wxRIGHT 边界间隔位于子元素右面.
wxTOP 边界间隔位于子元素上面.
wxBOTTOM 边界间隔位于子元素下面.
wxALL 边界间隔位于子元素四周.wxLEFT | wxRIGHT | wxTOP | wxBOTTOM.

第四个参数指定边界间隔的大小

当然你也可以直接增加一段空白,下面演示了增加空白区域的几种方法:

// 增加一段空白 (旧方法)
void Add(int width, int height, int stretch = 0, int flags = 0,
         int border = 0);
// 增加一段固定大小的空白
void AddSpacer(int size);
// 增加一个可缩放的空白
void AddStretchSpacer(int stretch = 1);

上面第二种方法相当于调用Add(size, size, 0),第三种则相当于调用Add(0, 0, stretch).

我们来举这样一个例子,一个对话框包含一个多行文本框和两个位于底端的按钮.我们可以以这样的角度去看待这些窗口,首先是一个顶层的垂直布局,包含一个多行文本框和一个底层的子布局控件,这个子布局控件是一个水平的布局控件,它包含一个OK按钮,被放置在左面,和一个Cancel按钮,被放置在右面.当对话框的大小发生变化的时候,我们希望多行文本框随着对话框大小的变化而变化,而按钮则保持它们原来的大小,并且在水平方向上居中排列,如下图所示:

下面列出了实现上述对话框所使用的代码:

MyDialog::MyDialog(wxWindow *parent, wxWindowID id,
                     const wxString &title )
        :  wxDialog(parent, id, title,
                     wxDefaultPosition, wxDefaultSize,
                     wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    wxBoxSizer  *topSizer = new wxBoxSizer( wxVERTICAL );
    // 创建一个最小大小为 100x60的多行文本框
    topSizer->Add(
        new wxTextCtrl( this, wxID_ANY, "My text.",
            wxDefaultPosition, wxSize(100,60), wxTE_MULTILINE),
        1,         // 垂直方向可缩放,缩放因子为1
        wxEXPAND|  // 水平方向可缩放
        wxALL,     // 四周都由空白边框
        10 );      // 空白边框大小为10
    wxBoxSizer *buttonSizer = new wxBoxSizer( wxHORIZONTAL );
    buttonSizer->Add(
       new wxButton( this, wxID_OK, "OK" ),
       0,          // 水平方向不可缩放
       wxALL,     // 四周有空白边框:(注意默认为顶部对齐)
       10 );      // 空白边框大小为10
    buttonSizer->Add(
       new wxButton( this, wxID_CANCEL, "Cancel" ),
       0,         // 水平方向不可缩放
       wxALL,     // 四周有空白边框:(注意默认为顶部对齐)
       10 );      // 空白边框大小为10
    topSizer->Add(
       buttonSizer,
       0,                // 垂直方向不可缩放
       wxALIGN_CENTER ); // 无边框并且居中对齐
    SetSizer( topSizer ); // 绑定对话框和布局控件
    topSizer->Fit( this );          // 调用对话框大小
    topSizer->SetSizeHints( this ); // 设置对话框最小大小
}

使用wxStaticBoxSizer编程

wxStaticBoxSizer是一个继承自wxBoxSizer的布局控件,除了实现wxBoxSizer的功能,另外还在整个布局的范围以外增加了一个静态的边框wxStaticBox,这个wxStaticBox需要手动创建并且在wxStaticBoxSizer的构造函数中作为参数传入,Add函数和wxBoxSizer的Add函数用法相同.

下图演示了使用wxStaticBoxSizer对一个复选框进行布局的样子:

对应的代码:

MyDialog::MyDialog(wxWindow *parent, wxWindowID id,
                   const wxString &title )
        : wxDialog(parent, id, title,
                   wxDefaultPosition, wxDefaultSize,
                   wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    // 创建一个顶层布局控件
    wxBoxSizer* topLevel = new wxBoxSizer(wxVERTICAL);
    // 创建静态文本框和静态文本框布局控件
    wxStaticBox* staticBox = new wxStaticBox(this,
        wxID_ANY, wxT("General settings"));
    wxStaticBoxSizer* staticSizer = new wxStaticBoxSizer(staticBox,
        wxVERTICAL);
    topLevel->Add(staticSizer, 0,
        wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
    // 在其中增加一个复选框
    wxCheckBox* checkBox = new wxCheckBox( this, ID_CHECKBOX,
        wxT("&Show splash screen"), wxDefaultPosition, wxDefaultSize);
    staticSizer->Add(checkBox, 0, wxALIGN_LEFT |wxALL, 5);
    SetSizer(topLevel);
    topLevel->Fit(this);
    topLevel->SetSizeHints(this);
}

使用wxGridSizer编程

wxGridSizer布局控件可以以二维表的方式排列它的子元素,这个二维表的每个表格的大小都是相同的,都等于最长的那个表格的长度和最高的那个表格的高度.创建一个wxGridSizer需要指定它的行数和列数,以及一个额外的行间距和列间距.Add方法和wxBoxSizer的用法相同.

下图演示了一个两行三列的网格布局控件,由于有一个很大的按钮,导致这个格的大小很大,从而导致所有的表格的大小都跟着变大:

代码如下:

MyDialog::MyDialog(wxWindow *parent, wxWindowID id,
                   const wxString &title )
        : wxDialog(parent, id, title,
                   wxDefaultPosition, wxDefaultSize,
                   wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    // 创建一个顶层网格布局控件
    wxGridSizer* gridSizer = new wxGridSizer(2, 3, 0, 0);
    SetSizer(gridSizer);
    wxButton* button1 = new wxButton(this, ID_BUTTON1, wxT("One"));
    gridSizer->Add(button1, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button2 = new wxButton(this, ID_BUTTON2, wxT("Two (the second button)"));
    gridSizer->Add(button2, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button3 = new wxButton(this, ID_BUTTON3, wxT("Three"));
    gridSizer->Add(button3, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button4 = new wxButton(this, ID_BUTTON4, wxT("Four"));
    gridSizer->Add(button4, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button5 = new wxButton(this, ID_BUTTON5, wxT("Five"));
    gridSizer->Add(button5, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button6 = new wxButton(this, ID_BUTTON6, wxT("Six"));
    gridSizer->Add(button6, 0, wxALIGN_CENTER_HORIZONTAL|
                               wxALIGN_CENTER_VERTICAL|wxALL, 5);
    gridSizer->Fit(this);
    gridSizer->SetSizeHints(this);
}

使用wxFlexGridSizer编程

wxFlexGridSizer同样采用二维表来对其子元素进行布局,和wxGridSizer不同的是,它不要求所有的表格的大小都是一样的,只要求同一列上所有表格的宽度是相同的并且同一行上所有表格的高度是相同的,也就是说,行的高度或者列的宽度仅由这一行或者这一列上的子元素决定.另外还可以给行和列指定是否缩放,这意味着当整个布局控件的大小发生变化的时候,可以指定某些行或者列随着整个布局控件的缩放而缩放.

创建一个wxFlexGridSizer可以指定行数,列数额外的垂直间距和水平间距.调用Add函数的方法和wxBoxSizer相同.

下图演示了一个使用wxFlexGridSizer进行布局的对话框的样子,正如你看到的那样,和wxGridSizer相比整个布局显的更紧凑了,因为中间很宽的那一列不再影响其它列的宽度了.

初始情况下,我们看不出来设置第一列可以改变大小的效果,不过如果我们如下图所示的那样改变这个对话框的水平方向的大小,,我们就可以看到第一列占用了额外增加的空间,并且第一列的子元素也为居中方式显式.

代码如下:

MyDialog::MyDialog(wxWindow *parent, wxWindowID id,
                   const wxString &title )
        : wxDialog(parent, id, title,
                   wxDefaultPosition, wxDefaultSize,
                   wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    //创建一个复杂网格布局控件
    wxFlexGridSizer* flexGridSizer = new wxFlexGridSizer(2, 3, 0, 0);
    this->SetSizer(flexGridSizer);
    //让第一列可变大小
    flexGridSizer->AddGrowableCol(0);
    wxButton* button1 = new wxButton(this, ID_BUTTON1, wxT("One"));
    flexGridSizer->Add(button1, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button2 = new wxButton(this, ID_BUTTON2, wxT("Two (the second button)"));
    flexGridSizer->Add(button2, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button3 = new wxButton(this, ID_BUTTON3, wxT("Three"));
    flexGridSizer->Add(button3, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button4 = new wxButton(this, ID_BUTTON4, wxT("Four"));
    flexGridSizer->Add(button4, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button5 = new wxButton(this, ID_BUTTON5, wxT("Five"));
    flexGridSizer->Add(button5, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    wxButton* button6 = new wxButton(this, ID_BUTTON6, wxT("Six"));
    flexGridSizer->Add(button6, 0, wxALIGN_CENTER_HORIZONTAL|
                                   wxALIGN_CENTER_VERTICAL|wxALL, 5);
    flexGridSizer->Fit(this);
    flexGridSizer->SetSizeHints(this);
}

使用wxGridBagSizer编程

这种布局控件用来模拟现实世界中的那种固定位置和大小的基于布局控件的布局.它将它的子元素按照一个虚拟的网格进行排列,不过子元素的位置是通过wxGBPosition对象指定的,对象的大小使用wxGBSpan指定,对象的大小不仅限于一个网格.

创建wxGridBagSizer的可选参数包括垂直和水平方向的间隔(默认为0),Add函数需要提供的参数包括子元素的位置和大小,另外的可选标记和边框大小参数的意义和wxBoxSizer是一样的.

下图演示了一个使用wxGridBagSizer进行布局的例子,我们指定了其中一个按钮的大小为两个单元列,我们还指定了第二行和第三列的大小是可以变化的,这样当我们改变对话框的大小的时候,就会出现如下面另外一幅图的效果.

相关代码如下:

MyDialog::MyDialog(wxWindow *parent, wxWindowID id,
                   const wxString &title )
        : wxDialog(parent, id, title,
                   wxDefaultPosition, wxDefaultSize,
                   wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
    wxGridBagSizer* gridBagSizer = new wxGridBagSizer();
    SetTopSizer(gridBagSizer);
    wxButton* b1 = new wxButton(this, wxID_ANY, wxT("One (0,0)"));
    gridBagSizer->Add(b1, wxGBPosition(0, 0));
    wxButton* b2 = new wxButton(this, wxID_ANY, wxT("Two (2,2)"));
    gridBagSizer->Add(b2, wxGBPosition(2, 2), wxGBSpan(1, 2),
                      wxGROW);
    wxButton* b3 = new wxButton(this, wxID_ANY, wxT("Three (3,2)"));
    gridBagSizer->Add(b3, wxGBPosition(3, 2));
    wxButton* b4 = new wxButton(this, wxID_ANY, wxT("Four (3,3)"));
    gridBagSizer->Add(b4, wxGBPosition(3, 3));
    gridBagSizer->AddGrowableRow(3);
    gridBagSizer->AddGrowableCol(2);
    gridBagSizer->Fit(this);
    gridBagSizer->SetSizeHints(this);
}