wxWidgets 中的布局管理
最后修改于 2023 年 10 月 18 日
一个典型的应用程序由各种小部件组成。这些小部件被放置在容器小部件中。程序员必须管理应用程序的布局。这不是一项容易的任务。
在 wxWidgets 中,我们有两种选择
- 绝对定位。
- Sizers。
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。当我们使用绝对定位时,我们需要了解几件事
- 如果我们调整窗口大小,小部件的大小和位置不会改变。
- 应用程序在各种平台上看起来不同(通常很糟糕)。
- 更改应用程序中的字体可能会破坏布局。
- 如果我们决定更改我们的布局,我们必须完全重做你的布局,这既乏味又耗时。
可能存在一些情况,我们可能会使用绝对定位,例如,在简单的教程中。我们不想让例子太难,所以我们经常使用绝对定位来解释某个主题。但大多数情况下,在现实世界的程序中,程序员使用 sizers。
在我们的例子中,我们有一个文本编辑器的简单骨架。如果我们调整窗口的大小,我们的 wxTextCtrl
的大小不会像我们期望的那样改变。
#include <wx/wx.h> #include <wx/menu.h> class Absolute : public wxFrame { public: Absolute(const wxString& title); wxMenuBar *menubar; wxMenu *file; wxMenu *edit; wxMenu *help; wxTextCtrl *textctrl; };
#include "absolute.h" Absolute::Absolute(const wxString& title) : wxFrame(NULL, -1, title, wxDefaultPosition, wxSize(350, 250)) { wxPanel *panel = new wxPanel(this, -1); menubar = new wxMenuBar; file = new wxMenu; edit = new wxMenu; help = new wxMenu; menubar->Append(file, wxT("&File")); menubar->Append(edit, wxT("&Edit")); menubar->Append(help, wxT("&Help")); SetMenuBar(menubar); textctrl = new wxTextCtrl(panel, -1, wxT(""), wxDefaultPosition, wxSize(250, 150), wxTE_MULTILINE); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "absolute.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { Absolute *absolute = new Absolute(wxT("Absolute")); absolute->Show(true); return true; }
此示例使用绝对定位。我们在面板小部件上定位一个 wxTextCtrl
小部件。
textctrl = new wxTextCtrl(panel, -1, wxT(""), wxDefaultPosition, wxSize(250, 150), wxTE_MULTILINE);
我们在 wxTextCtrl
小部件的构造函数中进行绝对定位。在我们的例子中,我们为该小部件提供了默认位置。宽度为 250px,高度为 150px。


文本控件的大小在调整窗口大小时不会改变。
使用 sizers
wxWidgets 中的 Sizers 确实解决了我们通过绝对定位提到的所有这些问题。我们可以在这些 sizers 中进行选择。
wxBoxSizer
wxStaticBoxSizer
wxGridSizer
wxFlexGridSizer
wxGridBagSizer


#include <wx/wx.h> class Sizer : public wxFrame { public: Sizer(const wxString& title); wxMenuBar *menubar; wxMenu *file; wxMenu *edit; wxMenu *help; wxTextCtrl *textctrl; };
#include "sizer.h" Sizer::Sizer(const wxString& title) : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(250, 180)) { menubar = new wxMenuBar; file = new wxMenu; edit = new wxMenu; help = new wxMenu; menubar->Append(file, wxT("&File")); menubar->Append(edit, wxT("&Edit")); menubar->Append(help, wxT("&Help")); SetMenuBar(menubar); textctrl = new wxTextCtrl(this, -1, wxT(""), wxPoint(-1, -1), wxSize(250, 150)); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "sizer.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { Sizer *sizer = new Sizer(wxT("Sizer")); sizer->Show(true); return true; }
wxTextCtrl
放置在 wxFrame
小部件中。wxFrame
小部件有一个特殊的内置 sizer。我们只能在 wxFrame
容器内放置一个小部件。子小部件占用所有未分配给边框、菜单、工具栏和状态栏的空间。
wxBoxSizer
此 sizer 允许我们将几个小部件放入一行或一列中。我们可以将另一个 sizer 放入一个现有的 sizer 中。这样我们就可以创建非常复杂的布局。
wxBoxSizer(int orient) wxSizerItem* Add(wxWindow* window, int proportion = 0, int flag = 0, int border = 0)
方向可以是 wxVERTICAL
或 wxHORIZONTAL
。将小部件添加到 wxBoxSizer
中是通过 Add
方法完成的。为了理解它,我们需要看看它的参数。
proportion 参数定义了小部件将在定义的方向上如何变化的比例。假设我们有三个比例为 0、1 和 2 的按钮。它们被添加到水平的 wxBoxSizer
中。比例为 0 的按钮根本不会改变。比例为 2 的按钮将在水平维度上比比例为 1 的按钮变化两倍。
使用 flag 参数,您可以在 wxBoxSizer
中进一步配置小部件的行为。我们可以控制小部件之间的边框。我们在像素中添加小部件之间的一些空间。为了应用边框,我们需要定义将使用边框的边。我们可以使用 |
运算符将它们组合起来,例如 wxLEFT | wxBOTTOM
。我们可以在这些标志之间进行选择
wxLEFT
wxRIGHT
wxBOTTOM
wxTOP
wxALL

#include <wx/wx.h> class Border : public wxFrame { public: Border(const wxString& title); };
#include "border.h" Border::Border(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 200)) { wxColour col1, col2; col1.Set(wxT("#4f5049")); col2.Set(wxT("#ededed")); wxPanel *panel = new wxPanel(this, -1); panel->SetBackgroundColour(col1); wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); wxPanel *midPan = new wxPanel(panel, wxID_ANY); midPan->SetBackgroundColour(col2); vbox->Add(midPan, 1, wxEXPAND | wxALL, 20); panel->SetSizer(vbox); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "border.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { Border *border = new Border(wxT("Border")); border->Show(true); return true; }
在此示例中,我们创建了两个面板。第二个面板周围有一些空间。
vbox->Add(midPan, 1, wxEXPAND | wxALL, 20);
我们在 midPan
面板周围放置了一个 20 像素的边框。wxALL
标志将边框大小应用于所有四个边。如果我们使用 wxEXPAND
标志,该小部件将使用为其分配的所有空间。
最后,我们还可以定义小部件的对齐方式。我们使用以下标志来完成
wxALIGN_LEFT
wxALIGN_RIGHT
wxALIGN_TOP
wxALIGN_BOTTOM
wxALIGN_CENTER_VERTICAL
wxALIGN_CENTER_HORIZONTAL
wxALIGN_CENTER
假设我们想将两个按钮放置在窗口的右下方。
#include <wx/wx.h> class Align : public wxFrame { public: Align(const wxString& title); };
#include "align.h" Align::Align(const wxString& title) : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(300, 200)) { wxPanel *panel = new wxPanel(this, -1); wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); wxBoxSizer *hbox1 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL); wxButton *ok = new wxButton(panel, -1, wxT("Ok")); wxButton *cancel = new wxButton(panel, -1, wxT("Cancel")); hbox1->Add(new wxPanel(panel, -1)); vbox->Add(hbox1, 1, wxEXPAND); hbox2->Add(ok); hbox2->Add(cancel); vbox->Add(hbox2, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 10); panel->SetSizer(vbox); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "align.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { Align *align = new Align(wxT("Align")); align->Show(true); return true; }
我们创建了三个 sizers。一个垂直的 sizer 和两个水平的 sizers。我们将这两个水平的 sizers 放入垂直的 sizer 中。
hbox1->Add(new wxPanel(panel, -1)); vbox->Add(hbox1, 1, wxEXPAND);
我们把一个 wxPanel
放在第一个水平 sizer 中。我们将比例设置为 1
并设置 wxEXPAND
标志。这样 sizer 将占用除 hbox2
之外的所有空间。
vbox->Add(hbox2, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 10);
我们将按钮放入 hbox2
sizer 中。 hbox2
右对齐,我们还在按钮的底部和右侧放置了一些空间。

转到类
在下面的示例中,我们介绍了几个重要的想法。
#include <wx/wx.h> class GotoClass : public wxFrame { public: GotoClass(const wxString& title); };
#include "gotoclass.h" GotoClass::GotoClass(const wxString& title) : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(450, 400)) { wxPanel *panel = new wxPanel(this, -1); wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); wxBoxSizer *hbox1 = new wxBoxSizer(wxHORIZONTAL); wxStaticText *st1 = new wxStaticText(panel, wxID_ANY, wxT("Class Name")); hbox1->Add(st1, 0, wxRIGHT, 8); wxTextCtrl *tc = new wxTextCtrl(panel, wxID_ANY); hbox1->Add(tc, 1); vbox->Add(hbox1, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10); vbox->Add(-1, 10); wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL); wxStaticText *st2 = new wxStaticText(panel, wxID_ANY, wxT("Matching Classes")); hbox2->Add(st2, 0); vbox->Add(hbox2, 0, wxLEFT | wxTOP, 10); vbox->Add(-1, 10); wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL); wxTextCtrl *tc2 = new wxTextCtrl(panel, wxID_ANY, wxT(""), wxPoint(-1, -1), wxSize(-1, -1), wxTE_MULTILINE); hbox3->Add(tc2, 1, wxEXPAND); vbox->Add(hbox3, 1, wxLEFT | wxRIGHT | wxEXPAND, 10); vbox->Add(-1, 25); wxBoxSizer *hbox4 = new wxBoxSizer(wxHORIZONTAL); wxCheckBox *cb1 = new wxCheckBox(panel, wxID_ANY, wxT("Case Sensitive")); hbox4->Add(cb1); wxCheckBox *cb2 = new wxCheckBox(panel, wxID_ANY, wxT("Nested Classes")); hbox4->Add(cb2, 0, wxLEFT, 10); wxCheckBox *cb3 = new wxCheckBox(panel, wxID_ANY, wxT("Non-Project Classes")); hbox4->Add(cb3, 0, wxLEFT, 10); vbox->Add(hbox4, 0, wxLEFT, 10); vbox->Add(-1, 25); wxBoxSizer *hbox5 = new wxBoxSizer(wxHORIZONTAL); wxButton *btn1 = new wxButton(panel, wxID_ANY, wxT("Ok")); hbox5->Add(btn1, 0); wxButton *btn2 = new wxButton(panel, wxID_ANY, wxT("Close")); hbox5->Add(btn2, 0, wxLEFT | wxBOTTOM , 5); vbox->Add(hbox5, 0, wxALIGN_RIGHT | wxRIGHT, 10); panel->SetSizer(vbox); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "gotoclass.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { GotoClass *gotoclass = new GotoClass(wxT("GotoClass")); gotoclass->Show(true); return true; }
这是一个使用 wxBoxSizer
的复杂示例。布局很简单。我们创建一个垂直的 sizer。然后我们将五个水平的 sizers 放入其中。
vbox->Add(hbox3, 1, wxLEFT | wxRIGHT | wxEXPAND, 10); vbox->Add(-1, 25);
我们已经知道,我们可以通过将 flag 参数与 border 参数结合来控制小部件之间的距离。但有一个真正的约束。在 Add
方法中,我们只能为所有给定边指定一个边框。在我们的例子中,我们向右和向左给出 10 像素。但我们不能给底部 25 像素。我们可以做的是在底部给出 10 像素,或者 0 像素。如果我们省略 wxBOTTOM
。因此,如果我们需要不同的值,我们可以添加一些额外的空间。使用 Add
方法,我们可以插入小部件和空间。
vbox->Add(hbox5, 0, wxALIGN_RIGHT | wxRIGHT, 10);
我们将两个按钮放在窗口的右侧。我们怎么做?要实现这一点,有三件事很重要:比例、对齐标志和 wxEXPAND
标志。比例必须为零。当调整窗口大小时,按钮不应改变其大小。我们不能指定 wxEXPAND
标志。按钮仅占用为其分配的区域。最后,我们必须指定 wxALIGN_RIGHT
标志。水平 sizer 从窗口的左侧延伸到右侧。因此,如果我们指定 wxALIGN_RIGHT
标志,则按钮将放置在右侧。正是我们想要的。

wxGridSizer
wxGridSizer
在二维表格中布局小部件。表格中的每个单元格都具有相同的大小。
wxGridSizer(int rows, int cols, int vgap, int hgap)
在构造函数中,我们指定表格中的行数和列数。以及单元格之间的垂直和水平空间。
在我们的例子中,我们创建了一个计算器的骨架。这是一个 wxGridSizer
的完美例子。
#include <wx/wx.h> class GridSizer : public wxFrame { public: GridSizer(const wxString& title); wxMenuBar *menubar; wxMenu *file; wxBoxSizer *sizer; wxGridSizer *gs; wxTextCtrl *display; };
#include "gridsizer.h" GridSizer::GridSizer(const wxString& title) : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(270, 220)) { menubar = new wxMenuBar; file = new wxMenu; SetMenuBar(menubar); sizer = new wxBoxSizer(wxVERTICAL); display = new wxTextCtrl(this, -1, wxT(""), wxPoint(-1, -1), wxSize(-1, -1), wxTE_RIGHT); sizer->Add(display, 0, wxEXPAND | wxTOP | wxBOTTOM, 4); gs = new wxGridSizer(5, 4, 3, 3); gs->Add(new wxButton(this, -1, wxT("Cls")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("Bck")), 0, wxEXPAND); gs->Add(new wxStaticText(this, -1, wxT("")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("Close")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("7")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("8")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("9")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("/")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("4")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("5")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("6")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("*")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("1")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("2")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("3")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("-")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("0")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT(".")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("=")), 0, wxEXPAND); gs->Add(new wxButton(this, -1, wxT("+")), 0, wxEXPAND); sizer->Add(gs, 1, wxEXPAND); SetSizer(sizer); SetMinSize(wxSize(270, 220)); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "gridsizer.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { GridSizer *gs = new GridSizer(wxT("GridSizer")); gs->Show(true); return true; }
在我们的例子中,我们为 wxFrame
设置了一个垂直的 sizer。我们将一个静态文本和一个网格 sizer 放入垂直的 sizer 中。
注意我们如何设法在 Bck
和 Close
按钮之间放置一个空格。我们只是在那里放了一个空的 wxStaticText
。
gs->Add(new wxButton(this, -1, wxT("Cls")), 0, wxEXPAND);
我们多次调用 Add
方法。小部件按添加的顺序放置在表格中。首先填写第一行,然后第二行等。

wxFlexGridSizer
此 sizer 类似于 wxGridSizer
。它也在二维表格中布局其小部件。它为其增加了一些灵活性。wxGridSizer
单元格的大小相同。wxFlexGridSizer
中的所有单元格在同一行中具有相同的高度。所有单元格在同一列中具有相同的宽度。但并非所有行和列都具有相同的高度或宽度。
wxFlexGridSizer(int rows, int cols, int vgap, int hgap)
rows
和 cols
指定 sizer 中的行数和列数。vgap
和 hgap
在两个方向上都添加了小部件之间的一些空间。
很多时候,开发人员必须为数据输入和修改开发对话框。我发现 wxFlexGridSizer
适合此任务。开发人员可以使用此 sizer 轻松设置对话框窗口。也可以使用 wxGridSizer
来完成此操作,但由于每个单元格都有相同大小的约束,因此它看起来不会很好。
#include <wx/wx.h> class FlexGridSizer : public wxFrame { public: FlexGridSizer(const wxString& title); };
#include "flexgridsizer.h" FlexGridSizer::FlexGridSizer(const wxString& title) : wxFrame(NULL, -1, title, wxPoint(-1, -1), wxSize(270, 220)) { wxPanel *panel = new wxPanel(this, -1); wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL); wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 2, 9, 25); wxStaticText *thetitle = new wxStaticText(panel, -1, wxT("Title")); wxStaticText *author = new wxStaticText(panel, -1, wxT("Author")); wxStaticText *review = new wxStaticText(panel, -1, wxT("Review")); wxTextCtrl *tc1 = new wxTextCtrl(panel, -1); wxTextCtrl *tc2 = new wxTextCtrl(panel, -1); wxTextCtrl *tc3 = new wxTextCtrl(panel, -1, wxT(""), wxPoint(-1, -1), wxSize(-1, -1), wxTE_MULTILINE); fgs->Add(thetitle); fgs->Add(tc1, 1, wxEXPAND); fgs->Add(author); fgs->Add(tc2, 1, wxEXPAND); fgs->Add(review, 1, wxEXPAND); fgs->Add(tc3, 1, wxEXPAND); fgs->AddGrowableRow(2, 1); fgs->AddGrowableCol(1, 1); hbox->Add(fgs, 1, wxALL | wxEXPAND, 15); panel->SetSizer(hbox); Centre(); }
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit(); };
#include "main.h" #include "flexgridsizer.h" IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { FlexGridSizer *fgs = new FlexGridSizer(wxT("FlexGridSizer")); fgs->Show(true); return true; }
在我们的例子中,我们创建了一个简单的对话框。它可用于将数据插入数据库。
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL); ... hbox->Add(fgs, 1, wxALL | wxEXPAND, 15);
我们创建一个水平框 sizer 以在小部件表周围放置一些空间(15 像素)。
fgs->Add(thetitle);
我们像使用 gridsizer 一样将小部件添加到 sizer。
fgs->AddGrowableRow(2, 1); fgs->AddGrowableCol(1, 1);
我们使第三行和第二列可增长。这样,当我们调整窗口大小时,我们使文本控件增长。前两个文本控件将在水平方向上增长,第三个文本控件将在两个方向上增长。我们一定不要忘记使小部件可扩展 (wxEXPAND
),以便它真正起作用。

wxWidgets 教程的这一部分专门用于布局管理。