ZetCode GUI 教程(九)
原文:ZetCode
wxWidgets 中的布局管理
典型的应用由各种小部件组成。 这些小部件放置在容器小部件内。 程序员必须管理应用的布局。 这不是一件容易的事。
在 wxWidgets 中,我们有两个选择:
- 绝对定位。
- 大小调整器。
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。 使用绝对定位时,我们必须了解以下几点:
- 如果我们调整窗口大小,则小部件的大小和位置不会改变。
- 在各种平台上,应用看起来有所不同(通常很差)。
- 在我们的应用中更改字体可能会破坏布局。
- 如果我们决定更改布局,则必须完全重做您的布局,这既繁琐又耗时。
在某些情况下,例如在简单的教程中,我们可能会使用绝对定位。 我们不想让示例太难,所以我们经常使用绝对定位来解释某个主题。 但是大多数情况下,在现实世界的程序中,程序员使用大小调整器。
在我们的示例中,我们有一个简单的文本编辑器框架。 如果我们调整窗口大小,则wxTextCtrl
的大小不会像我们期望的那样改变。
absolute.h
#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;
};
absolute.cpp
#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);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp {
public:
virtual bool OnInit();
};
main.cpp
#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
小部件的构造器中进行绝对定位。 在我们的情况下,我们为小部件提供默认位置。 宽度为 250 像素,高度为 150 像素。
图:调整大小之前
图:调整大小之后
调整窗口大小时,文本控件的大小不会更改。
使用大小调整器
wxWidgets 中的大小调整器确实解决了所有这些问题,我们通过绝对定位提到了这些问题。 我们可以在这些大小调整器中进行选择。
wxBoxSizer
wxStaticBoxSizer
wxGridSizer
wxFlexGridSizer
wxGridBagSizer
图:调整大小之前
图:调整大小之后
sizer.h
#include <wx/wx.h>
class Sizer : public wxFrame
{
public:
Sizer(const wxString& title);
wxMenuBar *menubar;
wxMenu *file;
wxMenu *edit;
wxMenu *help;
wxTextCtrl *textctrl;
};
sizer.cpp
#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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#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
小部件具有特殊的内置大小调整器。 我们只能在wxFrame
容器中放入一个小部件。 子窗口小部件占据了所有空间,边框,菜单,工具栏和状态栏未分配该空间。
wxBoxSizer
这个 sizer 使我们能够将几个小部件放在一行或一列中。 我们可以将另一个调整器放到现有的调整器中。 这样,我们可以创建非常复杂的布局。
wxBoxSizer(int orient)
wxSizerItem* Add(wxWindow* window, int proportion = 0, int flag = 0, int border = 0)
方向可以是wxVERTICAL
或wxHORIZONTAL
。 通过Add()
方法将小部件添加到wxBoxSizer
中。 为了理解它,我们需要查看它的参数。
比例参数定义控件在定义的方向上如何变化的比例。 假设我们有比例为 0、1 和 2 的树按钮。它们被添加到水平wxBoxSizer
中。 比例为 0 的按钮完全不会改变。 在水平方向上比例为 2 的按钮的变化比比例为 1 的按钮大两倍。
使用flag
参数,您可以进一步在wxBoxSizer
中配置小部件的行为。 我们可以控制小部件之间的边界。 我们在小部件之间添加一些像素间距。 为了应用边框,我们需要定义要使用边框的边。 我们可以将它们与|
运算符,例如wxLEFT | wxBOTTOM
结合使用。 我们可以在这些标志之间进行选择:
wxLEFT
wxRIGHT
wxBOTTOM
wxTOP
wxALL
图:面板周围的边框
border.h
#include <wx/wx.h>
class Border : public wxFrame
{
public:
Border(const wxString& title);
};
border.cpp
#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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#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
假设我们要在窗口的右下方放置两个按钮。
align.h
#include <wx/wx.h>
class Align : public wxFrame
{
public:
Align(const wxString& title);
};
align.cpp
#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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "align.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Align *align = new Align(wxT("Align"));
align->Show(true);
return true;
}
我们创建三个大小调整器。 一台垂直分级机和两台水平分级机。 我们将这两个水平大小调整器放到垂直大小调整器中。
hbox1->Add(new wxPanel(panel, -1));
vbox->Add(hbox1, 1, wxEXPAND);
我们将wxPanel
放入第一个水平缩放器中。 我们将比例设置为1
并设置一个wxEXPAND
标志。 这样,大小调整器将占据hbox2
以外的所有空间。
vbox->Add(hbox2, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 10);
我们已将按钮放入hbox2
大小调整器中。 hbox2
右对齐,我们还在按钮的底部和右侧留了一些空间。
图:对齐按钮
GotoClass
在下面的示例中,我们介绍了几个重要的想法。
gotoclass.h
#include <wx/wx.h>
class GotoClass : public wxFrame
{
public:
GotoClass(const wxString& title);
};
gotoclass.cpp
#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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "gotoclass.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
GotoClass *gotoclass = new GotoClass(wxT("GotoClass"));
gotoclass->Show(true);
return true;
}
这是使用wxBoxSizer
的复杂示例。 布局僵硬。 我们创建一个垂直大小调整器。 然后,我们将五个水平大小调整器放入其中。
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
标志。 水平大小调整器从窗口的左侧扩展到右侧。 因此,如果我们指定wxALIGN_RIGHT
标志,则按钮将放置在右侧。 正是我们想要的。
图:GotoClass
wxGridSizer
wxGridSizer
在二维表中布置小部件。 表格中的每个单元格都具有相同的大小。
wxGridSizer(int rows, int cols, int vgap, int hgap)
在构造器中,我们指定表中的行数和列数。 以及我们细胞之间的垂直和水平空间。
在我们的示例中,我们创建了计算器的骨架。 这是wxGridSizer
的完美示例。
gridsizer.h
#include <wx/wx.h>
class GridSizer : public wxFrame
{
public:
GridSizer(const wxString& title);
wxMenuBar *menubar;
wxMenu *file;
wxBoxSizer *sizer;
wxGridSizer *gs;
wxTextCtrl *display;
};
gridsizer.cpp
#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(4, 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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "gridsizer.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
GridSizer *gs = new GridSizer(wxT("GridSizer"));
gs->Show(true);
return true;
}
在我们的示例中,我们为wxFrame
设置了垂直大小调整器。 我们将静态文本和网格大小调整器放入垂直大小调整器中。
注意我们如何在Bck
和Close
按钮之间放置空格。 我们只是在其中放一个空的wxStaticText
。
gs->Add(new wxButton(this, -1, wxT("Cls")), 0, wxEXPAND);
我们多次调用Add()
方法。 将小部件按顺序放置在表中,然后将它们添加。 第一行先填充,然后第二行等。
图:GridSizer
wxFlexGridSizer
该大小调整器类似于wxGridSizer
。 它还确实将其小部件布置在二维表中。 它增加了一些灵活性。 wxGridSizer
细胞大小相同。 wxFlexGridSizer
中的所有单元格都具有相同的高度。 一列中所有单元格的宽度均相同。 但是,所有行和列不一定都具有相同的高度或宽度。
wxFlexGridSizer(int rows, int cols, int vgap, int hgap)
rows
和cols
指定大小调整器中的行数和列数。 vgap
和hgap
在两个方向的小部件之间添加了一些空间。
很多时候,开发者必须开发用于数据输入和修改的对话框。 我发现wxFlexGridSizer
适用于此类任务。 开发者可以使用此大小调整器轻松设置对话框窗口。 也可以使用wxGridSizer
完成此操作,但由于每个单元格的大小相同,因此看起来不太好。
flexgridsizer.h
#include <wx/wx.h>
class FlexGridSizer : public wxFrame
{
public:
FlexGridSizer(const wxString& title);
};
flexgridsizer.cpp
#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();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#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);
我们创建一个水平框大小调整器,以便在小部件表周围放置一些空间(15 像素)。
fgs->Add(thetitle);
完全与GridSizer
一样,将小部件添加到大小调整器中。
fgs->AddGrowableRow(2, 1);
fgs->AddGrowableCol(1, 1);
我们使第三行和第二列可增长。 这样,当调整窗口大小时,我们使文本控件变大。 前两个文本控件将在水平方向上增长,第三个文本控件将在两个方向上增长。 我们一定不要忘记使小部件可扩展(wxEXPAND
)以使其真正起作用。
图:FlexGridSizer
wxWidgets 教程的这一部分专门用于布局管理。
wxWidgets 中的事件
事件是每个 GUI 应用不可或缺的一部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成,例如互联网连接,窗口管理器或计时器。 当应用启动时,将创建一个主循环。 该应用位于主循环中,并等待事件生成。 当我们退出应用时,主循环退出。
定义
事件是来自底层框架(通常是 GUI 工具箱)的应用级信息。事件循环是一种程序结构,用于等待并调度器中的事件或消息。 事件循环反复查找事件以对其进行处理。调度器是将事件映射到事件处理器的过程。 事件处理器是对事件做出反应的方法。
事件对象是与事件关联的对象。 通常是一个窗口。事件类型是已生成的唯一事件。
一个简单的事件示例
在 wxWidgets 中处理事件的传统方法是使用静态事件表。这受 Microsoft 基础类(MFC)的影响。 一种更灵活,更现代的方法是使用Connect()
方法。 我们在整个 wxWidgets 教程中都使用它。
事件表
在下一个示例中,我们显示一个使用事件表的示例。
button.h
#include <wx/wx.h>
class MyButton : public wxFrame
{
public:
MyButton(const wxString& title);
void OnQuit(wxCommandEvent& event);
private:
DECLARE_EVENT_TABLE()
};
button.cpp
#include "button.h"
MyButton::MyButton(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
wxButton *button = new wxButton(panel, wxID_EXIT,
wxT("Quit"), wxPoint(20, 20));
Centre();
}
void MyButton::OnQuit(wxCommandEvent& WXUNUSED(event))
{
Close(true);
}
BEGIN_EVENT_TABLE(MyButton, wxFrame)
EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
END_EVENT_TABLE()
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "button.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
MyButton *button = new MyButton(wxT("Button"));
button->Show(true);
return true;
}
在我们的示例中,我们创建一个简单的按钮。 通过单击按钮,我们关闭应用。
private:
DECLARE_EVENT_TABLE()
在头文件中,我们使用DECLARE_EVENT_TABLE()
宏声明一个事件表。
BEGIN_EVENT_TABLE(MyButton, wxFrame)
EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
END_EVENT_TABLE()
我们通过将每个事件映射到适当的成员函数来实现事件表。
使用Connect()
的示例
我们将讨论移动事件。 移动事件保存有关移动更改事件的信息。 当我们将窗口移到新位置时,将生成一个移动事件。 表示移动事件的类为wxMoveEvent
。 wxEVT_MOVE
是事件类型。
move.h
#include <wx/wx.h>
class Move : public wxFrame
{
public:
Move(const wxString& title);
void OnMove(wxMoveEvent & event);
wxStaticText *st1;
wxStaticText *st2;
};
move.cpp
#include "move.h"
Move::Move(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
wxPanel *panel = new wxPanel(this, -1);
st1 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 10));
st2 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 30));
Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
Centre();
}
void Move::OnMove(wxMoveEvent& event)
{
wxPoint size = event.GetPosition();
st1->SetLabel(wxString::Format(wxT("x: %d"), size.x ));
st2->SetLabel(wxString::Format(wxT("y: %d"), size.y ));
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "move.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Move *move = new Move(wxT("Move event"));
move->Show(true);
return true;
}
该示例显示窗口的当前位置。
Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
在这里,我们将wxEVT_MOVE
事件类型与OnMove()
方法连接在一起。
wxPoint size = event.GetPosition();
OnMove()
方法中的事件参数是特定于特定事件的对象。 在我们的例子中,它是wxMoveEvent
类的实例。 该对象保存有关事件的信息。 我们可以通过调用事件的GetPosition()
方法找出当前位置。
图:移动事件
事件传播
事件有两种类型:基本事件和命令事件。 它们的传播方式不同。 事件传播是事件从子小部件传播到父小部件和祖父小部件等的事件。基本事件不传播。 命令事件确实传播。 例如,wxCloseEvent
是一个基本事件。 此事件传播到父窗口小部件没有任何意义。
默认情况下,在事件处理器中捕获的事件停止传播。 要继续传播,我们必须调用Skip()
方法。
propagate.h
#include <wx/wx.h>
class Propagate : public wxFrame
{
public:
Propagate(const wxString& title);
void OnClick(wxCommandEvent& event);
};
class MyPanel : public wxPanel
{
public:
MyPanel(wxFrame *frame, int id);
void OnClick(wxCommandEvent& event);
};
class MyButton : wxButton
{
public:
MyButton(MyPanel *panel, int id, const wxString &label);
void OnClick(wxCommandEvent& event);
};
propagate.cpp
#include <iostream>
#include "propagate.h"
const int ID_BUTTON = 1;
Propagate::Propagate(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
MyPanel *panel = new MyPanel(this, -1);
new MyButton(panel, ID_BUTTON, wxT("Ok"));
Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Propagate::OnClick));
Centre();
}
void Propagate::OnClick(wxCommandEvent& event)
{
std::cout << "event reached frame class" << std::endl;
event.Skip();
}
MyPanel::MyPanel(wxFrame *frame, int id)
: wxPanel(frame, id)
{
Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnClick));
}
void MyPanel::OnClick(wxCommandEvent& event)
{
std::cout << "event reached panel class" << std::endl;
event.Skip();
}
MyButton::MyButton(MyPanel *mypanel, int id, const wxString& label)
: wxButton(mypanel, id, label, wxPoint(15, 15))
{
Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyButton::OnClick));
}
void MyButton::OnClick(wxCommandEvent& event)
{
std::cout << "event reached button class" << std::endl;
event.Skip();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "propagate.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Propagate *prop = new Propagate(wxT("Propagate"));
prop->Show(true);
return true;
}
在我们的示例中,面板上有一个按钮。 面板放置在框架小部件中。 我们为所有小部件定义一个处理器。
event reached button class
event reached panel class
event reached frame class
当我们点击按钮时,我们得到了这个。 事件从按钮传播到面板和框架。
尝试省略一些Skip()
方法,看看会发生什么。
取消事件
有时我们需要停止处理事件。 为此,我们称方法Veto()
。
veto.h
#include <wx/wx.h>
class Veto : public wxFrame
{
public:
Veto(const wxString& title);
void OnClose(wxCloseEvent& event);
};
veto.cpp
#include "veto.h"
Veto::Veto(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(Veto::OnClose));
Centre();
}
void Veto::OnClose(wxCloseEvent& event)
{
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Are you sure to quit?"), wxT("Question"),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
int ret = dial->ShowModal();
dial->Destroy();
if (ret == wxID_YES) {
Destroy();
} else {
event.Veto();
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "veto.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Veto *veto = new Veto(wxT("Veto"));
veto->Show(true);
return true;
}
在我们的示例中,我们处理wxCloseEvent
。 当我们单击标题栏上的 X 按钮,按 Alt + F4
或从系统菜单中选择关闭时,将称为此事件。 在许多应用中,如果要进行一些更改,我们希望防止意外关闭窗口。 为此,我们必须连接wxEVT_CLOSE_WINDOW
事件类型。
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Are you sure to quit?"), wxT("Question"),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
在关闭事件期间,我们显示一个消息对话框。
if (ret == wxID_YES) {
Destroy();
} else {
event.Veto();
}
根据返回值,我们销毁窗口或否决事件。 注意,要关闭窗口,我们必须调用Destroy()
方法。 通过调用Close()
方法,我们将陷入无尽的循环。
窗口标识符
窗口标识符是在事件系统中唯一确定窗口标识的整数。 有三种创建窗口 ID 的方法:
- 让系统自动创建一个 ID。
- 使用标准标识符。
- 创建我们自己的 ID。
每个小部件都有一个 id 参数。 这是事件系统中的唯一编号。 如果我们使用多个小部件,则必须在它们之间进行区分。
wxButton(parent, -1)
wxButton(parent, wxID_ANY)
如果为 ID 参数提供 -1 或wxID_ANY
,则让 wxWidgets 自动为我们创建一个 ID。 自动创建的 ID 始终为负,而用户指定的 ID 必须始终为正。 当我们不需要更改窗口小部件状态时,通常使用此选项。 例如,静态文本在应用的生命周期内将永远不会更改。 如果需要,我们仍然可以获取 ID。 有一种方法GetId()
,它将为我们确定 ID。
应尽可能使用标准标识符。 标识符可以在某些平台上提供一些标准的图形或行为。
ident.h
#include <wx/wx.h>
class Ident : public wxFrame
{
public:
Ident(const wxString& title);
};
ident.cpp
#include "ident.h"
Ident::Ident(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(200, 150))
{
wxPanel *panel = new wxPanel(this, -1);
wxGridSizer *grid = new wxGridSizer(2, 3);
grid->Add(new wxButton(panel, wxID_CANCEL), 0, wxTOP | wxLEFT, 9);
grid->Add(new wxButton(panel, wxID_DELETE), 0, wxTOP, 9);
grid->Add(new wxButton(panel, wxID_SAVE), 0, wxLEFT, 9);
grid->Add(new wxButton(panel, wxID_EXIT));
grid->Add(new wxButton(panel, wxID_STOP), 0, wxLEFT, 9);
grid->Add(new wxButton(panel, wxID_NEW));
panel->SetSizer(grid);
Centre();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "ident.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Ident *ident = new Ident(wxT("Identifiers"));
ident->Show(true);
return true;
}
在我们的示例中,我们在按钮上使用标准标识符。 在 Linux 上,按钮带有小图标。
图:标识符
在本章中,我们讨论了 wxWidgets 中的事件。
wxWidgets 中的对话框
对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。
对话框本质上有两种:预定义对话框和自定义对话框。
预定义对话框
预定义对话框是 wxWidgets 工具包中可用的对话框。 这些是用于常见编程任务的对话框,例如显示文本,接收输入,加载和保存文件等。它们可以节省程序员的时间,并通过使用某些标准行为来增强功能。
MessageDialog
消息对话框用于向用户显示消息。 它们是可定制的。 我们可以更改将在对话框中显示的图标和按钮。
Messages.h
#include <wx/wx.h>
class Messages : public wxFrame
{
public:
Messages(const wxString& title);
void ShowMessage1(wxCommandEvent & event);
void ShowMessage2(wxCommandEvent & event);
void ShowMessage3(wxCommandEvent & event);
void ShowMessage4(wxCommandEvent & event);
};
const int ID_INFO = 1;
const int ID_ERROR = 2;
const int ID_QUESTION = 3;
const int ID_ALERT = 4;
Messages.cpp
#include "Messages.h"
Messages::Messages(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(210, 110))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
wxGridSizer *gs = new wxGridSizer(2, 2, 2, 2);
wxButton *btn1 = new wxButton(panel, ID_INFO, wxT("Info"));
wxButton *btn2 = new wxButton(panel, ID_ERROR, wxT("Error"));
wxButton *btn3 = new wxButton(panel, ID_QUESTION, wxT("Question"));
wxButton *btn4 = new wxButton(panel, ID_ALERT, wxT("Alert"));
Connect(ID_INFO, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage1));
Connect(ID_ERROR, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage2));
Connect(ID_QUESTION, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage3));
Connect(ID_ALERT, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage4));
gs->Add(btn1, 1, wxEXPAND);
gs->Add(btn2, 1);
gs->Add(btn3, 1);
gs->Add(btn4, 1);
hbox->Add(gs, 0, wxALL, 15);
panel->SetSizer(hbox);
Center();
}
void Messages::ShowMessage1(wxCommandEvent& event)
{
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Download completed"), wxT("Info"), wxOK);
dial->ShowModal();
}
void Messages::ShowMessage2(wxCommandEvent& event)
{
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Error loading file"), wxT("Error"), wxOK | wxICON_ERROR);
dial->ShowModal();
}
void Messages::ShowMessage3(wxCommandEvent& event)
{
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Are you sure to quit?"), wxT("Question"),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
dial->ShowModal();
}
void Messages::ShowMessage4(wxCommandEvent& event)
{
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Unallowed operation"), wxT("Exclamation"),
wxOK | wxICON_EXCLAMATION);
dial->ShowModal();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Messages.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Messages *msgs = new Messages(wxT("Messages"));
msgs->Show(true);
return true;
}
在我们的示例中,我们创建了四个按钮并将它们放入网格大小调整器中。 这些按钮将显示四个不同的对话框窗口。 我们通过指定不同的样式标志来创建它们。
wxMessageDialog *dial = new wxMessageDialog(NULL,
wxT("Error loading file"), wxT("Error"), wxOK | wxICON_ERROR);
dial->ShowModal();
消息对话框的创建很简单。 通过提供NULL
作为父级,我们将对话框设置为顶级窗口。 这两个字符串提供了消息文本和对话框标题。 通过指定wxOK
和wxICON_ERROR
标志,我们显示一个 OK 按钮和一个错误图标。 为了在屏幕上显示对话框,我们调用ShowModal()
方法。
wxFileDialog
这是打开和保存文件的常用对话框。
openfile.h
#include <wx/wx.h>
class Openfile : public wxFrame
{
public:
Openfile(const wxString& title);
void OnOpen(wxCommandEvent& event);
wxTextCtrl *tc;
};
openfile.cpp
#include "openfile.h"
Openfile::Openfile(const wxString & title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{
wxMenuBar *menubar = new wxMenuBar;
wxMenu *file = new wxMenu;
file->Append(wxID_OPEN, wxT("&Open"));
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);
Connect(wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(Openfile::OnOpen));
tc = new wxTextCtrl(this, -1, wxT(""), wxPoint(-1, -1),
wxSize(-1, -1), wxTE_MULTILINE);
Center();
}
void Openfile::OnOpen(wxCommandEvent& event)
{
wxFileDialog * openFileDialog = new wxFileDialog(this);
if (openFileDialog->ShowModal() == wxID_OK){
wxString fileName = openFileDialog->GetPath();
tc->LoadFile(fileName);
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "openfile.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Openfile *open = new Openfile(wxT("Openfile"));
open->Show(true);
return true;
}
在我们的示例中,我们显示一个打开文件菜单项和一个简单的多行文本控件。 如果单击打开文件菜单项,则会显示wxFileDialog
。 我们可以将一些简单的文本文件加载到文本控件中。
tc = new wxTextCtrl(this, -1, wxT(""), wxPoint(-1, -1),
wxSize(-1, -1), wxTE_MULTILINE);
我们将文本文件加载到此文本控件中。
wxFileDialog * openFileDialog = new wxFileDialog(this);
在这里,我们创建一个wxFileDialog
。 我们使用默认参数。 (打开文件对话框是默认对话框。)
if (openFileDialog->ShowModal() == wxID_OK){
wxString fileName = openFileDialog->GetPath();
tc->LoadFile(fileName);
}
在这里,我们显示对话框。 我们获得选定的文件名,并将文件加载到文本控件中。
图:Linux 上的wxFileDialog
wxFontDialog
这是选择字体的常用对话框。
fontdialog.h
#include <wx/wx.h>
class ChangeFont : public wxFrame
{
public:
ChangeFont(const wxString& title);
void OnOpen(wxCommandEvent& event);
wxStaticText *st;
};
const int ID_FONTDIALOG = 1;
fontdialog.cpp
#include <wx/fontdlg.h>
#include "fontdialog.h"
ChangeFont::ChangeFont(const wxString & title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{
wxPanel *panel = new wxPanel(this, -1);
wxMenuBar *menubar = new wxMenuBar;
wxMenu *file = new wxMenu;
file->Append(ID_FONTDIALOG, wxT("&Change font"));
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);
Connect(ID_FONTDIALOG, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(ChangeFont::OnOpen));
st = new wxStaticText(panel, wxID_ANY, wxT("The Agoge"),
wxPoint(20, 20));
Center();
}
void ChangeFont::OnOpen(wxCommandEvent& WXUNUSED(event))
{
wxFontDialog *fontDialog = new wxFontDialog(this);
if (fontDialog->ShowModal() == wxID_OK) {
st->SetFont(fontDialog->GetFontData().GetChosenFont());
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "fontdialog.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
ChangeFont *change = new ChangeFont(wxT("Change font"));
change->Show(true);
return true;
}
在此示例中,我们将更改静态文本示例的字体。
st = new wxStaticText(panel, wxID_ANY, wxT("The Agoge"),
wxPoint(20, 20));
在这里,我们在面板上显示静态文本。 我们将使用wxFontDialog
更改其字体。
wxFontDialog *fontDialog = new wxFontDialog(this);
if (fontDialog->ShowModal() == wxID_OK) {
st->SetFont(fontDialog->GetFontData().GetChosenFont());
}
在这些代码行中,我们显示字体对话框。 然后我们得到选择的字体。 最后,我们更改之前创建的静态文本的字体。
图:字体对话框
自定义对话框
在下一个示例中,我们创建一个自定义对话框。 图像编辑应用可以更改图片的颜色深度。 为了提供这种功能,我们可以创建一个合适的自定义对话框。
customdialog.h
#include <wx/wx.h>
class CustomDialog : public wxDialog
{
public:
CustomDialog(const wxString& title);
};
customdialog.cpp
#include "customdialog.h"
CustomDialog::CustomDialog(const wxString & title)
: wxDialog(NULL, -1, title, wxDefaultPosition, wxSize(250, 230))
{
wxPanel *panel = new wxPanel(this, -1);
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
wxStaticBox *st = new wxStaticBox(panel, -1, wxT("Colors"),
wxPoint(5, 5), wxSize(240, 150));
wxRadioButton *rb = new wxRadioButton(panel, -1,
wxT("256 Colors"), wxPoint(15, 30), wxDefaultSize, wxRB_GROUP);
wxRadioButton *rb1 = new wxRadioButton(panel, -1,
wxT("16 Colors"), wxPoint(15, 55));
wxRadioButton *rb2 = new wxRadioButton(panel, -1,
wxT("2 Colors"), wxPoint(15, 80));
wxRadioButton *rb3 = new wxRadioButton(panel, -1,
wxT("Custom"), wxPoint(15, 105));
wxTextCtrl *tc = new wxTextCtrl(panel, -1, wxT(""),
wxPoint(95, 105));
wxButton *okButton = new wxButton(this, -1, wxT("Ok"),
wxDefaultPosition, wxSize(70, 30));
wxButton *closeButton = new wxButton(this, -1, wxT("Close"),
wxDefaultPosition, wxSize(70, 30));
hbox->Add(okButton, 1);
hbox->Add(closeButton, 1, wxLEFT, 5);
vbox->Add(panel, 1);
vbox->Add(hbox, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, 10);
SetSizer(vbox);
Centre();
ShowModal();
Destroy();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "customdialog.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
CustomDialog *custom = new CustomDialog(wxT("CustomDialog"));
custom->Show(true);
return true;
}
本示例是基于对话框的应用。 我们说明了如何创建自定义对话框。
class CustomDialog : public wxDialog
自定义对话框基于wxDialog
类。
wxStaticBox *st = new wxStaticBox(panel, -1, wxT("Colors"),
wxPoint(5, 5), wxSize(240, 150));
wxRadioButton *rb = new wxRadioButton(panel, -1,
wxT("256 Colors"), wxPoint(15, 30), wxDefaultSize, wxRB_GROUP);
请注意,必须先创建wxStaticBox
小部件,然后再包含该小部件,并且这些小部件应该是静态框的同级,而不是子级。
ShowModal();
Destroy();
为了在屏幕上显示对话框,我们调用ShowModal()
方法。 要从内存中清除对话框,我们调用Destroy()
方法。
图:自定义对话框
wxWidgets 教程的这一部分专门用于对话框。
wxWidgets 小部件
在本章中,我们将展示 wxWidgets 中提供的几个小部件的小示例。 小部件是我们应用的构建块。 wxWidgets 包含大量有用的小部件。小部件是基本的 GUI 对象。 一个小部件为 wxWidgets 工具包命名。 该术语在 UNIX 系统上使用。 在 Windows 上,小部件通常称为控件。
wxCheckBox
wxCheckBox
是具有两种状态的窗口小部件:打开和关闭。 这是一个带有标签的盒子。 标签可以设置在框的右侧或左侧。 如果选中此复选框,则在方框中用勾号表示。 复选框可用于在启动时显示或隐藏启动画面,切换工具栏的可见性等。
checkbox.h
#include <wx/wx.h>
class CheckBox : public wxFrame
{
public:
CheckBox(const wxString& title);
void OnToggle(wxCommandEvent& event);
wxCheckBox *m_cb;
};
const int ID_CHECKBOX = 100;
checkbox.cpp
#include "checkbox.h"
CheckBox::CheckBox(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
m_cb = new wxCheckBox(panel, ID_CHECKBOX, wxT("Show title"),
wxPoint(20, 20));
m_cb->SetValue(true);
Connect(ID_CHECKBOX, wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(CheckBox::OnToggle));
Centre();
}
void CheckBox::OnToggle(wxCommandEvent& WXUNUSED(event))
{
if (m_cb->GetValue()) {
this->SetTitle(wxT("CheckBox"));
} else {
this->SetTitle(wxT(" "));
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "checkbox.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
CheckBox *cb = new CheckBox(wxT("CheckBox"));
cb->Show(true);
return true;
}
在我们的示例中,我们在窗口上显示一个复选框。 我们通过单击复选框来切换窗口的标题。
m_cb = new wxCheckBox(panel, ID_CHECKBOX, wxT("Show title"),
wxPoint(20, 20));
m_cb->SetValue(true);
我们创建一个复选框。 默认情况下,标题是可见的。 因此,我们通过调用方法SetValue()
来选中该复选框。
Connect(ID_CHECKBOX, wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(CheckBox::OnToggle));
如果单击复选框,则会生成wxEVT_COMMAND_CHECKBOX_CLICKED
事件。 我们将此事件连接到用户定义的OnToggle()
方法。
if (m_cb->GetValue()) {
this->SetTitle(wxT("CheckBox"));
} else {
this->SetTitle(wxT(" "));
}
在OnToggle()
方法内部,我们检查复选框的状态。 如果选中,我们将在标题栏中显示"CheckBox"
字符串,否则我们将清除标题。
图:wxCheckBox
wxBitmapButton
位图按钮是显示位图的按钮。 位图按钮可以具有其他三个状态。 选定,集中并显示。 我们可以为这些状态设置特定的位图。
bitmapbutton.h
#include <wx/wx.h>
#include <wx/slider.h>
class BitmapButton : public wxFrame
{
public:
BitmapButton(const wxString& title);
wxSlider *slider;
wxBitmapButton *button;
int pos;
void OnScroll(wxScrollEvent& event);
};
const int ID_SLIDER = 100;
bitmapbutton.cpp
#include "bitmapbutton.h"
BitmapButton::BitmapButton(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
wxImage::AddHandler( new wxPNGHandler );
wxPanel *panel = new wxPanel(this);
slider = new wxSlider(panel, ID_SLIDER, 0, 0, 100,
wxPoint(10, 30), wxSize(140, -1));
button = new wxBitmapButton(panel, wxID_ANY, wxBitmap(wxT("mute.png"),
wxBITMAP_TYPE_PNG), wxPoint(180, 20));
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(BitmapButton::OnScroll));
Center();
}
void BitmapButton::OnScroll(wxScrollEvent& event)
{
pos = slider->GetValue();
if (pos == 0) {
button->SetBitmapLabel(wxBitmap(wxT("mute.png"), wxBITMAP_TYPE_PNG));
} else if (pos > 0 && pos <= 30 ) {
button->SetBitmapLabel(wxBitmap(wxT("min.png"), wxBITMAP_TYPE_PNG));
} else if (pos > 30 && pos < 80 ) {
button->SetBitmapLabel(wxBitmap(wxT("med.png"), wxBITMAP_TYPE_PNG));
} else {
button->SetBitmapLabel(wxBitmap(wxT("max.png"), wxBITMAP_TYPE_PNG));
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "bitmapbutton.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
BitmapButton *bb = new BitmapButton(wxT("BitmapButton"));
bb->Show(true);
return true;
}
在我们的示例中,我们有一个滑块和一个位图按钮。 我们模拟了音量控制。 通过拖动滑块的手柄,我们可以更改按钮上的位图。
wxImage::AddHandler( new wxPNGHandler );
我们将使用 PNG 图像,因此必须初始化 PNG 图像处理器。
button = new wxBitmapButton(panel, wxID_ANY, wxBitmap(wxT("mute.png"),
wxBITMAP_TYPE_PNG), wxPoint(180, 20));
我们创建一个位图按钮。 我们指定位图类型,在本例中为wxBITMAP_TYPE_PNG
pos = slider->GetValue();
我们得到滑块值。 根据此值,我们为按钮设置一个位图。 我们有四个音量状态:静音,最小,中和最大。 要更改按钮上的位图,我们调用SetBitmapLabel()
方法。
图:wxBitmapButton
wxToggleButton
wxToggleButton
是具有两种状态的按钮:已按下和未按下。 通过单击可以在这两种状态之间切换。 在某些情况下此功能非常合适。
togglebutton.h
#include <wx/wx.h>
#include <wx/tglbtn.h>
class ToggleButton : public wxFrame
{
public:
ToggleButton(const wxString& title);
void OnToggleRed(wxCommandEvent& event);
void OnToggleGreen(wxCommandEvent& event);
void OnToggleBlue(wxCommandEvent& event);
protected:
wxToggleButton *m_tgbutton1;
wxToggleButton *m_tgbutton2;
wxToggleButton *m_tgbutton3;
wxPanel *m_panel;
wxColour *colour;
};
const int ID_TGBUTTON1 = 101;
const int ID_TGBUTTON2 = 102;
const int ID_TGBUTTON3 = 103;
togglebutton.cpp
#include "togglebutton.h"
ToggleButton::ToggleButton(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
colour = new wxColour(0, 0, 0);
m_tgbutton1 = new wxToggleButton(panel, ID_TGBUTTON1,
wxT("Red"), wxPoint(20, 20));
m_tgbutton2 = new wxToggleButton(panel, ID_TGBUTTON2,
wxT("Green"), wxPoint(20, 70));
m_tgbutton3 = new wxToggleButton(panel, ID_TGBUTTON3,
wxT("Blue"), wxPoint(20, 120));
Connect(ID_TGBUTTON1, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleRed));
Connect(ID_TGBUTTON2, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleGreen));
Connect(ID_TGBUTTON3, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleBlue));
m_panel = new wxPanel(panel, wxID_NEW, wxPoint(150, 20),
wxSize(110, 110), wxSUNKEN_BORDER);
m_panel->SetBackgroundColour(colour->GetAsString());
}
void ToggleButton::OnToggleRed(wxCommandEvent& WXUNUSED(event))
{
unsigned char green = colour->Green();
unsigned char blue = colour->Blue();
if ( colour->Red() ) {
colour->Set(0, green, blue);
} else {
colour->Set(255, green, blue);
}
m_panel->SetBackgroundColour(colour->GetAsString());
}
void ToggleButton::OnToggleGreen(wxCommandEvent& WXUNUSED(event))
{
unsigned char red = colour->Red();
unsigned char blue = colour->Blue();
if ( colour->Green() ) {
colour->Set(red, 0, blue);
} else {
colour->Set(red, 255, blue);
}
m_panel->SetBackgroundColour(colour->GetAsString());
}
void ToggleButton::OnToggleBlue(wxCommandEvent& WXUNUSED(event))
{
unsigned char red = colour->Red();
unsigned char green = colour->Green();
if ( colour->Blue() ) {
colour->Set(red, green, 0);
} else {
colour->Set(red, green, 255);
}
m_panel->SetBackgroundColour(colour->GetAsString());
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "togglebutton.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
ToggleButton *button = new ToggleButton(wxT("ToggleButton"));
button->Centre();
button->Show(true);
return true;
}
在我们的示例中,我们显示了三个切换按钮和一个面板。 我们将面板的背景色设置为黑色。 切换按钮将切换颜色值的红色,绿色和蓝色部分。 背景颜色取决于我们按下的切换按钮。
colour = new wxColour(0, 0, 0);
这是初始颜色值。 红色,绿色和蓝色均不等于黑色。 从理论上讲,黑色毕竟不是颜色。
m_tgbutton1 = new wxToggleButton(panel, ID_TGBUTTON1,
wxT("Red"), wxPoint(20, 20));
在这里,我们创建一个切换按钮。
Connect(ID_TGBUTTON1, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleRed));
如果单击切换按钮,则会生成wxEVT_COMMAND_TOGGLEBUTTON_CLICKED
事件。 我们为此事件连接事件处理器。 注意,我们没有将事件连接到按钮方法,而是连接到wxFrame
。 小部件,它是切换按钮的高级父级。 之所以可以这样做,是因为命令事件会传播到其父对象。 在我们的例子中,按钮 -> 面板 -> 框。 如果要将事件连接到按钮,则必须创建派生的按钮类,这意味着需要做更多的工作。
if ( colour->Blue() ) {
colour->Set(red, green, 0);
} else {
colour->Set(red, green, 255);
}
在事件处理器中,我们设置各自的wxColour
参数。
m_panel->SetBackgroundColour(colour->GetAsString());
我们设置面板的背景。
图:wxToggleButton
wxStaticLine
此小部件在窗口上显示一条简单的线。 它可以是水平或垂直的。
staticline.h
#include <wx/wx.h>
class Staticline : public wxDialog
{
public:
Staticline(const wxString& title);
};
staticline.cpp
#include "staticline.h"
#include <wx/stattext.h>
#include <wx/statline.h>
Staticline::Staticline(const wxString& title) : wxDialog(NULL, wxID_ANY, title,
wxDefaultPosition, wxSize(360, 350))
{
wxFont font(10, wxDEFAULT, wxNORMAL, wxBOLD);
wxStaticText *heading = new wxStaticText(this, wxID_ANY, wxT("The Central Europe"),
wxPoint(30, 15));
heading->SetFont(font);
wxStaticLine *sl1 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 50),
wxSize(300,1));
wxStaticText *st1 = new wxStaticText(this, wxID_ANY, wxT("Slovakia"),
wxPoint(25, 80));
wxStaticText *st2 = new wxStaticText(this, wxID_ANY, wxT("Hungary"),
wxPoint(25, 100));
wxStaticText *st3 = new wxStaticText(this, wxID_ANY, wxT("Poland"),
wxPoint(25, 120));
wxStaticText *st4 = new wxStaticText(this, wxID_ANY, wxT("Czech Republic"),
wxPoint(25, 140));
wxStaticText *st5 = new wxStaticText(this, wxID_ANY, wxT("Germany"),
wxPoint(25, 160));
wxStaticText *st6 = new wxStaticText(this, wxID_ANY, wxT("Slovenia"),
wxPoint(25, 180));
wxStaticText *st7 = new wxStaticText(this, wxID_ANY, wxT("Austria"),
wxPoint(25, 200));
wxStaticText *st8 = new wxStaticText(this, wxID_ANY, wxT("Switzerland"),
wxPoint(25, 220));
wxStaticText *st9 = new wxStaticText(this, wxID_ANY, wxT("5 379 000"),
wxPoint(220, 80), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st10 = new wxStaticText(this, wxID_ANY, wxT("10 084 000"),
wxPoint(220, 100), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st11 = new wxStaticText(this, wxID_ANY, wxT("38 635 000"),
wxPoint(220, 120), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st12 = new wxStaticText(this, wxID_ANY, wxT("10 240 000"),
wxPoint(220, 140), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st13 = new wxStaticText(this, wxID_ANY, wxT("82 443 000"),
wxPoint(220, 160), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st14 = new wxStaticText(this, wxID_ANY, wxT("2 001 000"),
wxPoint(220, 180), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st15 = new wxStaticText(this, wxID_ANY, wxT("8 032 000"),
wxPoint(220, 200), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st16 = new wxStaticText(this, wxID_ANY, wxT("7 288 000"),
wxPoint(220, 220), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticLine *sl2 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 260),
wxSize(300, 1));
wxStaticText *sum = new wxStaticText(this, wxID_ANY, wxT("164 102 000"),
wxPoint(220, 280));
wxFont sum_font = sum->GetFont();
sum_font.SetWeight(wxBOLD);
sum->SetFont(sum_font);
this->Centre();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "staticline.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Staticline *sl = new Staticline(wxT("The Central Europe"));
sl->ShowModal();
sl->Destroy();
return true;
}
在前面的示例中,我们显示了欧洲中部国家及其人口。 wxStaticLine
的使用在视觉上更具吸引力。
wxStaticLine *sl1 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 50),
wxSize(300,1));
在这里,我们创建一条水平静态线。 宽度为 300 像素。 高度为 1 像素。
图:wxStaticLine
wxStaticText
wxStaticText
小部件显示一行或多行只读文本。
statictext.h
#include <wx/wx.h>
class StaticText : public wxFrame
{
public:
StaticText(const wxString& title);
};
statictext.cpp
#include "statictext.h"
StaticText::StaticText(const wxString& title)
: wxFrame(NULL, wxID_ANY, title)
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
wxString text = wxT("'Cause sometimes you feel tired,\n\
feel weak, and when you feel weak,\
you feel like you wanna just give up.\n\
But you gotta search within you,\
you gotta find that inner strength\n\
and just pull that shit out of you\
and get that motivation to not give up\n\
and not be a quitter,\
no matter how bad you wanna just fall flat on your face and collapse.");
wxStaticText *st = new wxStaticText(panel, wxID_ANY, text,
wxPoint(10, 10), wxDefaultSize, wxALIGN_CENTRE);
this->SetSize(600, 110);
this->Centre();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "statictext.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
StaticText *st = new StaticText(wxT("StaticText"));
st->Show(true);
return true;
}
在我们的示例中,我们在窗口上显示了 Eminem 的 Till I Collapse 歌词的一部分。
wxStaticText *st = new wxStaticText(panel, wxID_ANY, text,
wxPoint(10, 10), wxDefaultSize, wxALIGN_CENTRE);
在这里,我们创建wxStaticText
小部件。 静态文本与中心对齐。
图:wxStaticText
wxSlider
wxSlider
是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以为特定任务选择一个值。 有时使用滑块比仅提供数字或使用旋转控件更自然。
Slider.h
#include <wx/wx.h>
#include <wx/slider.h>
class MyPanel : public wxPanel
{
public:
MyPanel(wxFrame *parent);
void OnPaint(wxPaintEvent& event);
void OnScroll(wxScrollEvent& event);
wxSlider *slider;
int fill;
};
class Slider : public wxFrame
{
public:
Slider(const wxString& title);
MyPanel *panel;
};
const int ID_SLIDER = 100;
Slider.cpp
#include "Slider.h"
Slider::Slider(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition,
wxSize(270, 200))
{
panel = new MyPanel(this);
Center();
}
MyPanel::MyPanel(wxFrame * parent)
: wxPanel(parent, wxID_ANY)
{
fill = 0;
slider = new wxSlider(this, ID_SLIDER, 0, 0, 140, wxPoint(50, 30),
wxSize(-1, 140), wxSL_VERTICAL);
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(MyPanel::OnScroll));
Connect(wxEVT_PAINT, wxPaintEventHandler(MyPanel::OnPaint));
}
void MyPanel::OnScroll(wxScrollEvent& event)
{
fill = slider->GetValue();
Refresh();
}
void MyPanel::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxPen pen(wxColour(212, 212, 212));
dc.SetPen(pen);
dc.DrawRectangle(wxRect(140, 30, 80, 140));
wxBrush brush1(wxColour(197, 108, 0));
dc.SetBrush(brush1);
dc.DrawRectangle(wxRect(140, 30, 80, fill));
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Slider.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Slider *slider = new Slider(wxT("Slider"));
slider->Show(true);
return true;
}
在我们的示例中,我们显示一个滑块小部件。 通过拉动滑块的手柄,我们可以控制面板的背景色。 在这样的示例中,使用滑块比使用例如滑块更自然。 自旋控件。
slider = new wxSlider(this, ID_SLIDER, 0, 0, 140, wxPoint(50, 30),
wxSize(-1, 140), wxSL_VERTICAL);
我们创建一个垂直滑块。 初始值为 0,最小值为 0,最大值为 140。我们不显示刻度线,也不显示标签。
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(MyPanel::OnScroll));
在这里,我们将wxEVT_COMMAND_SLIDER_UPDATED
事件连接到OnScroll()
用户定义的方法。
Connect(wxEVT_PAINT, wxPaintEventHandler(MyPanel::OnPaint));
我们还将进行一些绘制,因此将OnPaint()
方法连接到wxEVT_PAINT
事件。
fill = slider->GetValue();
Refresh();
在OnScroll()
方法中,我们将获得当前的滑块值。 我们调用Refresh()
方法,该方法将生成wxEVT_PAINT
事件。
dc.DrawRectangle(wxRect(140, 30, 80, 140));
...
dc.DrawRectangle(wxRect(140, 30, 80, fill));
在OnPaint()
事件处理器内,我们绘制了两个矩形。 第一种方法是绘制带有灰色边框的白色矩形。 第二种方法绘制带有某种褐色的矩形。 矩形的高度由fill
值控制,该值由滑块控件设置。
图:wxSlider
在 wxWidgets 教程的这一部分中,我们介绍了各种小部件。
wxWidgets 小部件 II
在本章中,我们将继续介绍其他各种小部件。 我们将提到wxListBox
,wxNotebook
和wxScrolledWindow
。
wxListBox
wxListBox
小部件用于显示和使用项目列表。 顾名思义,它是一个矩形,里面有一个字符串列表。 我们可以使用它来显示 MP3 文件,书籍名称,较大项目的模块名称或朋友名称的列表。 可以在两种不同的状态下创建wxListBox
。 在单选状态或多选状态下。 单一选择状态是默认状态。 wxListBox
中有两个重要事件。 第一个是wxEVT_COMMAND_LISTBOX_SELECTED
事件。 当我们在wxListBox
中选择一个字符串时,将生成此事件。 第二个事件是wxEVT_COMMAND_LISTBOX_DOUBLE_CLICKED
事件。 当我们双击wxListBox
中的项目时会生成该文件。 wxListBox
内部的元素数量在 GTK 平台上受到限制。 根据文档,当前大约有 2000 个元素。 元素从零开始编号。 如果需要,滚动条会自动显示。
Listbox.h
#include <wx/wx.h>
#include <wx/listbox.h>
class MyPanel : public wxPanel
{
public:
MyPanel(wxPanel *parent);
void OnNew(wxCommandEvent& event);
void OnRename(wxCommandEvent& event);
void OnClear(wxCommandEvent& event);
void OnDelete(wxCommandEvent& event);
wxListBox *m_lb;
wxButton *m_newb;
wxButton *m_renameb;
wxButton *m_clearb;
wxButton *m_deleteb;
};
class Listbox : public wxFrame
{
public:
Listbox(const wxString& title);
void OnDblClick(wxCommandEvent& event);
wxListBox *listbox;
MyPanel *btnPanel;
};
const int ID_RENAME = 1;
const int ID_LISTBOX = 5;
Listbox.cpp
#include "listbox.h"
#include <wx/textdlg.h>
Listbox::Listbox(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 200))
{
wxPanel * panel = new wxPanel(this, -1);
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
listbox = new wxListBox(panel, ID_LISTBOX,
wxPoint(-1, -1), wxSize(-1, -1));
hbox->Add(listbox, 3, wxEXPAND | wxALL, 20);
btnPanel = new MyPanel(panel);
hbox->Add(btnPanel, 2, wxEXPAND | wxRIGHT, 10);
Connect(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED,
wxCommandEventHandler(Listbox::OnDblClick));
panel->SetSizer(hbox);
Center();
}
MyPanel::MyPanel(wxPanel * parent)
: wxPanel(parent, wxID_ANY)
{
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
Listbox *lb = (Listbox *) parent->GetParent();
m_lb = lb->listbox;
m_newb = new wxButton(this, wxID_NEW, wxT("New"));
m_renameb = new wxButton(this, ID_RENAME, wxT("Rename"));
m_deleteb = new wxButton(this, wxID_DELETE, wxT("Delete"));
m_clearb = new wxButton(this, wxID_CLEAR, wxT("Clear"));
Connect(wxID_NEW, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnNew) );
Connect(ID_RENAME, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnRename) );
Connect(wxID_CLEAR, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnClear) );
Connect(wxID_DELETE, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnDelete) );
vbox->Add(-1, 20);
vbox->Add(m_newb);
vbox->Add(m_renameb, 0, wxTOP, 5);
vbox->Add(m_deleteb, 0, wxTOP, 5);
vbox->Add(m_clearb, 0, wxTOP, 5);
SetSizer(vbox);
}
void MyPanel::OnNew(wxCommandEvent& event)
{
wxString str = wxGetTextFromUser(wxT("Add new item"));
if (str.Len() > 0)
m_lb->Append(str);
}
void MyPanel::OnClear(wxCommandEvent& event)
{
m_lb->Clear();
}
void MyPanel::OnRename(wxCommandEvent& event)
{
wxString text;
wxString renamed;
int sel = m_lb->GetSelection();
if (sel != -1) {
text = m_lb->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}
if (!renamed.IsEmpty()) {
m_lb->Delete(sel);
m_lb->Insert(renamed, sel);
}
}
void MyPanel::OnDelete(wxCommandEvent& event)
{
int sel = m_lb->GetSelection();
if (sel != -1) {
m_lb->Delete(sel);
}
}
void Listbox::OnDblClick(wxCommandEvent& event)
{
wxString text;
wxString renamed;
int sel = listbox->GetSelection();
if (sel != -1) {
text = listbox->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}
if (!renamed.IsEmpty()) {
listbox->Delete(sel);
listbox->Insert(renamed, sel);
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Listbox.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Listbox *listbox = new Listbox(wxT("Listbox"));
listbox->Show(true);
return true;
}
listbox = new wxListBox(panel, ID_LISTBOX,
wxPoint(-1, -1), wxSize(-1, -1));
这是列表框窗口小部件的构造器。
在我们的示例中,我们有一个列表框和四个按钮。 这些按钮用于添加,重命名,删除和清除列表框中的所有项目。
wxString str = wxGetTextFromUser(wxT("Add new item"));
if (str.Len() > 0)
m_lb->Append(str);
要向列表框中添加新字符串,我们将显示一个wxGetTextFromUser
对话框。 我们调用Append()
方法将字符串附加到列表框。
m_lb->Clear();
清除所有项目是最简单的操作。 我们只调用Clear()
方法。
int sel = m_lb->GetSelection();
if (sel != -1) {
m_lb->Delete(sel);
}
要删除一个项目,我们找出选定的项目。 然后我们调用Delete()
方法。
重命名项目需要几个步骤。
wxString text;
wxString renamed;
我们定义了两个局部变量。
int sel = listbox->GetSelection();
if (sel != -1) {
text = listbox->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}
我们获取选定的字符串并将其保存到重命名的变量中。
if (!renamed.IsEmpty()) {
m_lb->Delete(sel);
m_lb->Insert(renamed, sel);
}
我们检查重命名的变量是否为空。 这是为了避免插入空字符串。 然后,我们删除旧项目并插入新项目。
图:列表框
wxNotebook
wxNotebook
小部件将多个窗口与相应的选项卡连接在一起。 您可以使用以下样式标志来定位wxNotebook
小部件:
wxNB_LEFT
wxNB_RIGHT
wxNB_TOP
wxNB_BOTTOM
默认位置为wxNB_TOP
。
Notebook.h
#include <wx/wx.h>
#include <wx/notebook.h>
#include <wx/grid.h>
class Notebook : public wxFrame
{
public:
Notebook(const wxString& title);
void OnQuit(wxCommandEvent& event);
};
class MyGrid : public wxGrid
{
public:
MyGrid(wxNotebook *parent);
};
Notebook.cpp
#include "Notebook.h"
Notebook::Notebook(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(400, 350))
{
wxNotebook *nb = new wxNotebook(this, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxNB_BOTTOM);
wxMenuBar *menubar = new wxMenuBar;
wxMenu *file = new wxMenu;
file->Append(wxID_EXIT, wxT("Quit"), wxT(""));
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);
Connect(wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(Notebook::OnQuit));
MyGrid *grid1 = new MyGrid(nb);
MyGrid *grid2 = new MyGrid(nb);
MyGrid *grid3 = new MyGrid(nb);
nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));
CreateStatusBar();
Center();
}
void Notebook::OnQuit(wxCommandEvent& event)
{
Close(true);
}
MyGrid::MyGrid(wxNotebook * parent)
: wxGrid(parent, wxID_ANY)
{
CreateGrid(30, 30);
SetRowLabelSize(50);
SetColLabelSize(25);
SetRowLabelAlignment(wxALIGN_RIGHT, wxALIGN_CENTRE);
SetLabelFont(wxFont(9, wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
for (int i = 0; i < 30 ; i++) {
this->SetRowSize(i, 25);
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Notebook.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Notebook *notebook = new Notebook(wxT("Notebook"));
notebook->Show(true);
return true;
}
在此示例中,我们创建了带有三个网格的笔记本小部件。 笔记本小部件位于底部。
wxNotebook *nb = new wxNotebook(this, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxNB_BOTTOM);
在这里,我们创建笔记本小部件。
nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));
我们将三个网格对象添加到笔记本小部件中。
图:Notebook
小部件
wxScrolledWindow
这是容器小部件之一。 当我们的区域大于窗口可以显示的区域时,此功能将非常有用。 在我们的示例中,我们演示了这种情况。 我们将大图像放入窗口。 当窗口小于我们的图像时,将自动显示滚动条。
scrolledwindow.h
#include <wx/wx.h>
class ScrWindow : public wxFrame
{
public:
ScrWindow(const wxString& title);
};
scrolledwindow.cpp
#include "scrolledwindow.h"
ScrWindow::ScrWindow(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{
wxImage::AddHandler(new wxJPEGHandler);
wxScrolledWindow *sw = new wxScrolledWindow(this);
wxBitmap bmp(wxT("castle.jpg"), wxBITMAP_TYPE_JPEG);
wxStaticBitmap *sb = new wxStaticBitmap(sw, -1, bmp);
int width = bmp.GetWidth();
int height = bmp.GetHeight();
sw->SetScrollbars(10, 10, width/10, height/10);
sw->Scroll(50,10);
Center();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "scrolledwindow.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
ScrWindow *sw = new ScrWindow(wxT("ScrolledWindow"));
sw->Show(true);
return true;
}
在我们的示例中,我们显示了 Spis 城堡的图片。
wxImage::AddHandler(new wxJPEGHandler);
要处理 JPG 图像,我们必须启动wxJPEGHandler
。
wxScrolledWindow *sw = new wxScrolledWindow(this);
wxBitmap bmp(wxT("castle.jpg"), wxBITMAP_TYPE_JPEG);
wxStaticBitmap *sb = new wxStaticBitmap(sw, -1, bmp);
我们创建一个滚动窗口,并在其中放置一个静态位图。
sw->SetScrollbars(10, 10, width/10, height/10);
我们设置滚动条。
sw->Scroll(50,10);
我们稍微滚动窗口。
在本章中,我们继续介绍 wxWidgets 库中的小部件。
wxWidgets 中的拖放
Wikipedia 将拖放定义为单击虚拟对象并将其拖动到其他位置或另一个虚拟对象的动作(或支持该动作)。 通常,它可用于调用多种动作,或在两个抽象对象之间创建各种类型的关联。
拖放操作使我们能够直观地完成复杂的事情。
在拖放中,我们基本上将一些数据从数据源拖到数据目标。 我们处理以下对象:
- 数据对象
- 数据源
- 数据目标
对于拖放&文本,wxWidgets 具有预定义的wxTextDropTarget
类。
在下面的示例中,我们将文件名从上方的列表控件拖放到底部的控件中。
textdrop.h
#include <wx/wx.h>
#include <wx/dnd.h>
class TextDrop : public wxFrame
{
public:
TextDrop(const wxString& title);
void OnSelect(wxCommandEvent& event);
void OnDragInit(wxListEvent& event);
wxGenericDirCtrl *m_gdir;
wxListCtrl *m_lc1;
wxListCtrl *m_lc2;
};
class MyTextDropTarget : public wxTextDropTarget
{
public:
MyTextDropTarget(wxListCtrl *owner);
virtual bool OnDropText(wxCoord x, wxCoord y,
const wxString& data);
wxListCtrl *m_owner;
};
textdrop.cpp
#include "textdrop.h"
#include <wx/treectrl.h>
#include <wx/dirctrl.h>
#include <wx/dir.h>
#include <wx/splitter.h>
TextDrop::TextDrop(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{
wxSplitterWindow *spl1 = new wxSplitterWindow(this, -1);
wxSplitterWindow *spl2 = new wxSplitterWindow(spl1, -1);
m_gdir = new wxGenericDirCtrl(spl1, -1, wxT("/home/"),
wxPoint(-1, -1), wxSize(-1, -1), wxDIRCTRL_DIR_ONLY);
m_lc1 = new wxListCtrl(spl2, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxLC_LIST);
m_lc2 = new wxListCtrl(spl2, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxLC_LIST);
MyTextDropTarget *mdt = new MyTextDropTarget(m_lc2);
m_lc2->SetDropTarget(mdt);
Connect(m_lc1->GetId(), wxEVT_COMMAND_LIST_BEGIN_DRAG,
wxListEventHandler(TextDrop::OnDragInit));
wxTreeCtrl *tree = m_gdir->GetTreeCtrl();
spl2->SplitHorizontally(m_lc1, m_lc2);
spl1->SplitVertically(m_gdir, spl2);
Connect(tree->GetId(), wxEVT_COMMAND_TREE_SEL_CHANGED,
wxCommandEventHandler(TextDrop::OnSelect));
Center();
}
MyTextDropTarget::MyTextDropTarget(wxListCtrl *owner)
{
m_owner = owner;
}
bool MyTextDropTarget::OnDropText(wxCoord x, wxCoord y,
const wxString& data)
{
m_owner->InsertItem(0, data);
return true;
}
void TextDrop::OnSelect(wxCommandEvent& event)
{
wxString filename;
wxString path = m_gdir->GetPath();
wxDir dir(path);
bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
int i = 0;
m_lc1->ClearAll();
m_lc2->ClearAll();
while ( cont )
{
m_lc1->InsertItem(i, filename);
cont = dir.GetNext(&filename);
i++;
}
}
void TextDrop::OnDragInit(wxListEvent& event)
{
wxString text = m_lc1->GetItemText(event.GetIndex());
wxTextDataObject tdo(text);
wxDropSource tds(tdo, m_lc1);
tds.DoDragDrop(wxDrag_CopyOnly);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "textdrop.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
TextDrop *td = new TextDrop(wxT("TextDrop"));
td->Show(true);
return true;
}
在我们的示例中,我们将窗口分为三个部分。 这是通过wxSplitterWindow
小部件完成的。 在窗口的左侧,我们有一个通用的目录控件。 我们显示文件系统下所有可用的目录。 右侧有两个窗口。 第一个显示所选目录下的所有文件。 第二个用于拖动文件。
MyTextDropTarget *mdt = new MyTextDropTarget(m_lc2);
m_lc2->SetDropTarget(mdt);
在这里,我们定义了文本放置目标。
wxString text = m_lc1->GetItemText(event.GetIndex());
wxTextDataObject tdo(text);
wxDropSource tds(tdo, m_lc1);
tds.DoDragDrop(wxDrag_CopyOnly);
在OnDragInit()
方法中,我们定义了一个文本数据对象和一个放置源对象。 我们称为DoDragDrop()
方法。 wxDrag_CopyOnly
常数仅允许复制数据。
bool MyTextDropTarget::OnDropText(wxCoord x, wxCoord y,
const wxString& data)
{
m_owner->InsertItem(0, data);
return true;
}
在放置操作期间,我们将文本数据插入到列表控件中。
图:拖放
在本章中,我们介绍了 wxWidgets 中的拖放操作。
wxWidgets 中的设备上下文
GDI(图形设备接口)是用于处理图形的界面。 它用于与图形设备(例如监视器,打印机或文件)进行交互。 GDI 允许程序员在屏幕或打印机上显示数据,而不必担心特定设备的详细信息。 GDI 使程序员与硬件隔离。
图:GDI 结构
从程序员的角度来看,GDI 是用于处理图形的一组类和方法。 GDI 由 2D 向量图形,字体和图像组成。
要开始绘制图形,我们必须创建一个设备上下文(DC)对象。 在 wxWidgets 中,设备上下文称为wxDC
。 该文档将wxDC
定义为可以在其上绘制图形和文本的设备上下文。 它以通用方式表示设备数量。 同一段代码可以写入不同类型的设备。 无论是屏幕还是打印机。 wxDC
不能直接使用。 相反,程序员应选择派生类之一。 每个派生类都打算在特定条件下使用。
以下类是派生的wxDC
类:
wxBufferedDC
wxBufferedPaintDC
wxPostScriptDC
wxMemoryDC
wxPrinterDC
wxScreenDC
wxClientDC
wxPaintDC
wxWindowDC
wxScreenDC
用于在屏幕上的任何地方绘制。 如果要在整个窗口上绘制(仅 Windows),则使用wxWindowDC
。 这包括窗口装饰。 wxClientDC
用于绘制窗口的客户区域。 客户区域是没有装饰(标题和边框)的窗口区域。 wxPaintDC
也用于绘制客户区。 但是wxPaintDC
和wxClientDC
之间有一个区别。 wxPaintDC
应该仅从wxPaintEvent
使用。 不应从wxPaintEvent
中使用wxClientDC
。 wxMemoryDC
用于在位图上绘制图形。 wxPostScriptDC
用于在任何平台上写入 PostScript 文件。 wxPrinterDC
用于访问打印机(仅 Windows)。
简单的线条
我们从画一条线开始。
line.h
#include <wx/wx.h>
class Line : public wxFrame
{
public:
Line(const wxString& title);
void OnPaint(wxPaintEvent& event);
};
line.cpp
#include "line.h"
Line::Line(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Line::OnPaint));
this->Centre();
}
void Line::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxCoord x1 = 50, y1 = 60;
wxCoord x2 = 190, y2 = 60;
dc.DrawLine(x1, y1, x2, y2);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "line.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Line *line = new Line(wxT("Line"));
line->Show(true);
return true;
}
在我们的示例中,我们在窗口的客户区域上画一条简单的线。 如果我们调整窗口的大小,它将被重绘。 生成wxPaintEvent
。 然后再次画线。
void OnPaint(wxPaintEvent& event);
在这里,我们声明一个OnPaint()
事件处理函数。
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Line::OnPaint));
我们将绘图事件连接到OnPaint()
方法。 所有绘图都发生在OnPaint()
事件处理器内。
wxPaintDC dc(this);
我们定义一个wxPaintDC
设备上下文。 它是设备上下文,用于在wxPaintEvent
内部的窗口上绘制
wxCoord x1 = 50, y1 = 60;
wxCoord x2 = 190, y2 = 60;
我们定义四个坐标。
dc.DrawLine(x1, y1, x2, y2);
我们画一条简单的线调用DrawLine()
方法。
图:一条简单的线
绘制文字
在窗口上绘制一些文本很容易。
text.h
#include <wx/wx.h>
class Text : public wxFrame
{
public:
Text(const wxString & title);
void OnPaint(wxPaintEvent & event);
};
text.cpp
#include "text.h"
Text::Text(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
Connect(wxEVT_PAINT, wxPaintEventHandler(Text::OnPaint));
Centre();
}
void Text::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
dc.DrawText(wxT("Лев Николaевич Толстoй"), 40, 60);
dc.DrawText(wxT("Анна Каренина"), 70, 80);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "text.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Text *text = new Text(wxT("Text"));
text->Show(true);
return true;
}
在我们的示例中,我们在窗口上绘制了俄罗斯阿兹布卡语的列夫·尼古拉耶维奇·托尔斯泰,安娜·卡列尼娜的文字。
dc.DrawText(wxT("Лев Николaевич Толстoй"), 40, 60);
dc.DrawText(wxT("Анна Каренина"), 70, 80);
DrawText()
方法在窗口上绘制文本。 它使用当前文本字体以及当前文本前景色和背景色在指定点绘制文本字符串。 感谢wxT()
宏,我们可以直接在代码中使用西里尔字母。 wxT()
宏与_T()
宏相同。 它包装字符串字面值以使用或不使用 Unicode。 未启用 Unicode 时,wxT()
是一个空宏。 启用 Unicode 时,它将为字符串字面值添加必要的L
,以使其成为宽字符串常量。
图:绘制文本
点
最简单的几何对象是一个点。 它是窗口上的一个普通点。
DrawPoint(int x, int y)
此方法在 x,y 坐标处绘制点。
point.h
#include <wx/wx.h>
class Points : public wxFrame
{
public:
Points(const wxString & title);
void OnPaint(wxPaintEvent & event);
};
points.cpp
#include "points.h"
#include <stdlib.h>
#include <time.h>
Points::Points(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Points::OnPaint));
srand(time(NULL));
this->Centre();
}
void Points::OnPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);
wxCoord x = 0;
wxCoord y = 0;
wxSize size = this->GetSize();
for (int i = 0; i<1000; i++) {
x = rand() % size.x + 1;
y = rand() % size.y + 1;
dc.DrawPoint(x, y);
}
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "points.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Points *points = new Points(wxT("Points"));
points->Show(true);
return true;
}
单点可能很难看清。 因此,我们创建了 1000 点。 每次调整窗口大小时,我们都会在窗口的客户区域上绘制 1000 点。
wxSize size = this->GetSize();
在这里,我们得到窗口的大小。
x = rand() % size.x + 1;
在这里,我们得到一个介于 1 到size.x
范围内的随机数。
图:点
钢笔
笔是基本的图形对象。 它用于绘制矩形,椭圆形,多边形或其他形状的线,曲线和轮廓。
wxPen(const wxColour& colour, int width = 1, int style = wxSOLID)
wxPen
构造器具有三个参数:colour
,width
和style
。 以下是可能的笔样式的列表:
wxSOLID
wxDOT
wxLONG_DASH
wxSHORT_DASH
wxDOT_DASH
wxTRANSPARENT
pen.h
#include <wx/wx.h>
class Pen : public wxFrame
{
public:
Pen(const wxString& title);
void OnPaint(wxPaintEvent& event);
};
pen.cpp
#include "pen.h"
Pen::Pen(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(360, 180))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Pen::OnPaint));
this->Centre();
}
void Pen::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxColour col1, col2;
col1.Set(wxT("#0c0c0c"));
col2.Set(wxT("#000000"));
wxBrush brush(wxColour(255, 255, 255), wxTRANSPARENT);
dc.SetBrush(brush);
dc.SetPen(wxPen(col1, 1, wxSOLID));
dc.DrawRectangle(10, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxDOT));
dc.DrawRectangle(130, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxLONG_DASH));
dc.DrawRectangle(250, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxSHORT_DASH));
dc.DrawRectangle(10, 105, 90, 60);
dc.SetPen(wxPen(col1, 1, wxDOT_DASH));
dc.DrawRectangle(130, 105, 90, 60);
dc.SetPen(wxPen(col1, 1, wxTRANSPARENT));
dc.DrawRectangle(250, 105, 90, 60);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "pen.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Pen *pen = new Pen(wxT("Pen"));
pen->Show(true);
return true;
}
在我们的示例中,我们绘制了 6 个具有不同笔样式的矩形。 最后一个是透明的,不可见。
dc.SetPen(wxPen(col1, 1, wxSOLID));
dc.DrawRectangle(10, 15, 90, 60);
在这里,我们为第一个矩形定义了一支笔。 我们设置了一支颜色为col1
(#0c0c0c
),宽 1 像素的实心钢笔。 DrawRectangle()
方法绘制矩形。
图:笔
区域
可以组合区域以创建更复杂的形状。 我们可以使用四个设置操作:Union()
,Intersect()
,Substract()
和Xor()
。 以下示例显示了所有正在执行的四个操作。
Regions.h
#include <wx/wx.h>
class Regions : public wxFrame
{
public:
Regions(const wxString & title);
void OnPaint(wxPaintEvent & event);
};
Regions.cpp
#include "Regions.h"
Regions::Regions(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 220))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Regions::OnPaint));
this->Centre();
}
void Regions::OnPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);
wxColour gray, white, red, blue;
wxColour orange, green, brown;
gray.Set(wxT("#d4d4d4"));
white.Set(wxT("#ffffff"));
red.Set(wxT("#ff0000"));
orange.Set(wxT("#fa8e00"));
green.Set(wxT("#619e1b"));
brown.Set(wxT("#715b33"));
blue.Set(wxT("#0d0060"));
dc.SetPen(wxPen(gray));
dc.DrawRectangle(20, 20, 50, 50);
dc.DrawRectangle(30, 40, 50, 50);
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(100, 20, 50, 50);
dc.DrawRectangle(110, 40, 50, 50);
wxRegion region1(100, 20, 50, 50);
wxRegion region2(110, 40, 50, 50);
region1.Intersect(region2);
wxRect rect1 = region1.GetBox();
dc.SetClippingRegion(region1);
dc.SetBrush(wxBrush(red));
dc.DrawRectangle(rect1);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(180, 20, 50, 50);
dc.DrawRectangle(190, 40, 50, 50);
wxRegion region3(180, 20, 50, 50);
wxRegion region4(190, 40, 50, 50);
region3.Union(region4);
dc.SetClippingRegion(region3);
wxRect rect2 = region3.GetBox();
dc.SetBrush(wxBrush(orange));
dc.DrawRectangle(rect2);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(20, 120, 50, 50);
dc.DrawRectangle(30, 140, 50, 50);
wxRegion region5(20, 120, 50, 50);
wxRegion region6(30, 140, 50, 50);
region5.Xor(region6);
wxRect rect3 = region5.GetBox();
dc.SetClippingRegion(region5);
dc.SetBrush(wxBrush(green));
dc.DrawRectangle(rect3);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(100, 120, 50, 50);
dc.DrawRectangle(110, 140, 50, 50);
wxRegion region7(100, 120, 50, 50);
wxRegion region8(110, 140, 50, 50);
region7.Subtract(region8);
wxRect rect4 = region7.GetBox();
dc.SetClippingRegion(region7);
dc.SetBrush(wxBrush(brown));
dc.DrawRectangle(rect4);
dc.DestroyClippingRegion();
dc.SetBrush(white);
dc.DrawRectangle(180, 120, 50, 50);
dc.DrawRectangle(190, 140, 50, 50);
wxRegion region9(180, 120, 50, 50);
wxRegion region10(190, 140, 50, 50);
region10.Subtract(region9);
wxRect rect5 = region10.GetBox();
dc.SetClippingRegion(region10);
dc.SetBrush(wxBrush(blue));
dc.DrawRectangle(rect5);
dc.DestroyClippingRegion();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Regions.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Regions *regions = new Regions(wxT("Regions"));
regions->Show(true);
return true;
}
图:区域
渐变
在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。 (answers.com)
void GradientFillLinear(const wxRect& rect, const wxColour& initialColour,
const wxColour& destColour, wxDirection nDirection = wxEAST)
此方法从initialColour
开始以线性渐变填充由 rect 指定的区域,最终逐渐渐变为destColour
。 nDirection
参数指定颜色更改的方向,默认值为wxEAST
。
gradient.h
#include <wx/wx.h>
class Gradient : public wxFrame
{
public:
Gradient(const wxString& title);
void OnPaint(wxPaintEvent& event);
};
gradient.cpp
#include "gradient.h"
Gradient::Gradient(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(220, 260))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Gradient::OnPaint));
this->Centre();
}
void Gradient::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxColour col1, col2;
col1.Set(wxT("#e12223"));
col2.Set(wxT("#000000"));
dc.GradientFillLinear(wxRect(20, 20, 180, 40), col1, col2, wxNORTH);
dc.GradientFillLinear(wxRect(20, 80, 180, 40), col1, col2, wxSOUTH);
dc.GradientFillLinear(wxRect(20, 140, 180, 40), col1, col2, wxEAST);
dc.GradientFillLinear(wxRect(20, 200, 180, 40), col1, col2, wxWEST);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "gradient.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Gradient *grad = new Gradient(wxT("Gradient"));
grad->Show(true);
return true;
}
图:渐变
形状
形状是更复杂的几何对象。 在下面的示例中,我们将绘制各种几何形状。
shapes.h
#include <wx/wx.h>
class Shapes : public wxFrame
{
public:
Shapes(const wxString & title);
void OnPaint(wxPaintEvent & event);
};
shapes.cpp
#include "shapes.h"
Shapes::Shapes(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(350, 300))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Shapes::OnPaint));
this->Centre();
}
void Shapes::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxPoint lines[] = { wxPoint(20, 260), wxPoint(100, 260),
wxPoint(20, 210), wxPoint(100, 210) };
wxPoint polygon[] = { wxPoint(130, 140), wxPoint(180, 170),
wxPoint(180, 140), wxPoint(220, 110), wxPoint(140, 100) };
wxPoint splines[] = { wxPoint(240, 170), wxPoint(280, 170),
wxPoint(285, 110), wxPoint(325, 110) };
dc.DrawEllipse(20, 20, 90, 60);
dc.DrawRoundedRectangle(130, 20, 90, 60, 10);
dc.DrawArc(240, 40, 340, 40, 290, 20);
dc.DrawPolygon(4, polygon);
dc.DrawRectangle(20, 120, 80, 50);
dc.DrawSpline(4, splines);
dc.DrawLines(4, lines);
dc.DrawCircle(170, 230, 35);
dc.DrawRectangle(250, 200, 60, 60);
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "shapes.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Shapes *shapes = new Shapes(wxT("Shapes"));
shapes->Show(true);
return true;
}
图:形状
在本章中,我们介绍了 wxWidgets 中的 GDI。
wxWidgets 中的自定义小部件
工具箱通常仅提供最常见的窗口小部件,例如按钮,文本窗口小部件,滚动条,滑块等。没有工具箱可以提供所有可能的窗口小部件。 wxWidgets 有许多小部件; 客户程序员创建了更多专门的小部件。
使用工具箱提供的绘图工具创建自定义窗口小部件。 有两种可能:程序员可以修改或增强现有的小部件,或者可以从头开始创建自定义小部件。
刻录小部件
这是我们从头开始创建的小部件的示例。 可以在各种媒体刻录应用(例如 Nero 刻录 ROM)中找到此小部件。
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <wx/wx.h>
class Widget : public wxPanel
{
public:
Widget(wxPanel *parent, int id );
wxPanel *m_parent;
void OnSize(wxSizeEvent& event);
void OnPaint(wxPaintEvent& event);
};
#endif
widget.cpp
#include <wx/wx.h>
#include "widget.h"
#include "burning.h"
int num[] = { 75, 150, 225, 300, 375, 450, 525, 600, 675 };
int asize = sizeof(num)/sizeof(num[1]);
Widget::Widget(wxPanel *parent, int id)
: wxPanel(parent, id, wxDefaultPosition, wxSize(-1, 30), wxSUNKEN_BORDER)
{
m_parent = parent;
Connect(wxEVT_PAINT, wxPaintEventHandler(Widget::OnPaint));
Connect(wxEVT_SIZE, wxSizeEventHandler(Widget::OnSize));
}
void Widget::OnPaint(wxPaintEvent& event)
{
wxFont font(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_NORMAL, false, wxT("Courier 10 Pitch"));
wxPaintDC dc(this);
dc.SetFont(font);
wxSize size = GetSize();
int width = size.GetWidth();
Burning *burn = (Burning *) m_parent->GetParent();
int cur_width = burn->GetCurWidth();
int step = (int) round(width / 10.0);
int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);
if (cur_width >= 700) {
dc.SetPen(wxPen(wxColour(255, 255, 184)));
dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
dc.DrawRectangle(0, 0, full, 30);
dc.SetPen(wxPen(wxColour(255, 175, 175)));
dc.SetBrush(wxBrush(wxColour(255, 175, 175)));
dc.DrawRectangle(full, 0, till-full, 30);
} else {
dc.SetPen(wxPen(wxColour(255, 255, 184)));
dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
dc.DrawRectangle(0, 0, till, 30);
}
dc.SetPen(wxPen(wxColour(90, 80, 60)));
for ( int i=1; i <= asize; i++ ) {
dc.DrawLine(i*step, 0, i*step, 6);
wxSize size = dc.GetTextExtent(wxString::Format(wxT("%d"), num[i-1]));
dc.DrawText(wxString::Format(wxT("%d"), num[i-1]),
i*step-size.GetWidth()/2, 8);
}
}
void Widget::OnSize(wxSizeEvent& event)
{
Refresh();
}
burning.h
#ifndef BURNING_H
#define BURNING_H
#include <wx/wx.h>
#include "widget.h"
class Burning : public wxFrame
{
public:
Burning(const wxString& title);
void OnScroll(wxScrollEvent& event);
int GetCurWidth();
wxSlider *m_slider;
Widget *m_wid;
int cur_width;
};
#endif
burning.cpp
#include "burning.h"
#include "widget.h"
int ID_SLIDER = 1;
Burning::Burning(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(350, 200))
{
cur_width = 75;
wxPanel *panel = new wxPanel(this, wxID_ANY);
wxPanel *centerPanel = new wxPanel(panel, wxID_ANY);
m_slider = new wxSlider(centerPanel, ID_SLIDER, 75, 0, 750, wxPoint(-1, -1),
wxSize(150, -1), wxSL_LABELS);
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL);
m_wid = new Widget(panel, wxID_ANY);
hbox->Add(m_wid, 1, wxEXPAND);
hbox2->Add(centerPanel, 1, wxEXPAND);
hbox3->Add(m_slider, 0, wxTOP | wxLEFT, 35);
centerPanel->SetSizer(hbox3);
vbox->Add(hbox2, 1, wxEXPAND);
vbox->Add(hbox, 0, wxEXPAND);
panel->SetSizer(vbox);
m_slider->SetFocus();
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(Burning::OnScroll));
Centre();
}
void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
cur_width = m_slider->GetValue();
m_wid->Refresh();
}
int Burning::GetCurWidth()
{
return cur_width;
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "burning.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Burning *burning = new Burning(wxT("The Burning Widget"));
burning->Show(true);
return true;
}
我们在窗口底部放置一个wxPanel
并手动绘制整个窗口小部件。 所有重要的代码都位于小部件类的OnPaint()
方法中。 此小部件以图形方式显示了介质的总容量和可供我们使用的可用空间。 小部件由滑块控制。 自定义窗口小部件的最小值为 0,最大值为 750。如果值达到 700,则开始绘制红色。 这表明过度燃烧。
wxSize size = GetSize();
int width = size.GetWidth();
...
int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);
我们动态绘制小部件。 窗口越大,刻录小部件越大。 反之亦然。 这就是为什么我们必须计算在其上绘制自定义窗口小部件的wxPanel
的大小。 till
参数确定要绘制的总大小。 该值来自滑块小部件。 它占整个面积的一部分。 full
参数确定了我们开始绘制红色的点。 注意使用浮点算法。 这是为了达到更高的精度。
实际图纸包括三个步骤。 我们绘制黄色或红色和黄色矩形。 然后,我们绘制垂直线,这些垂直线将小部件分为几个部分。 最后,我们画出数字来表示介质的容量。
void Widget::OnSize(wxSizeEvent& event)
{
Refresh();
}
每次调整窗口大小时,我们都会刷新小部件。 这将导致小部件重新绘制自身。
void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
cur_width = m_slider->GetValue();
m_wid->Refresh();
}
如果滚动滑块的拇指,我们将获得实际值并将其保存到cur_width
变量中。 绘制刻录窗口小部件时使用此值。 然后,我们使小部件重新绘制。
图:刻录小部件
在 wxWidgets 教程的这一部分中,我们创建了一个自定义窗口小部件。
wxWidgets 中的俄罗斯方块游戏
俄罗斯方块游戏是有史以来最受欢迎的计算机游戏之一。 原始游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。此后,几乎所有版本的几乎所有计算机平台上都可以使用俄罗斯方块。
俄罗斯方块被称为下降块益智游戏。 在这个游戏中,我们有七个不同的形状,称为 tetrominoes。 S 形,Z 形,T 形,L 形,线形,镜像 L 形和正方形。 这些形状中的每一个都形成有四个正方形。 形状从板上掉下来。 俄罗斯方块游戏的目的是移动和旋转形状,以便它们尽可能地适合。 如果我们设法形成一行,则该行将被破坏并得分。 我们玩俄罗斯方块游戏,直到达到顶峰。
图:Tetrominoes
wxWidgets 是一个用于创建应用的工具包。 还有其他一些旨在创建计算机游戏的库。 不过,可以使用 wxWidgets 和其他应用工具包来创建简单的游戏。
开发
我们的俄罗斯方块游戏没有图像,我们使用 wxWidgets 编程工具包中提供的绘图 API 绘制四面体。 每个计算机游戏的背后都有一个数学模型。 俄罗斯方块也是如此。
游戏背后的一些想法。
- 我们使用
wxTimer
创建游戏周期。 - 绘制四方块。
- 形状以正方形为单位移动(而不是逐个像素移动)。
- 从数学上讲,棋盘是一个简单的数字列表。
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
enum Tetrominoes { NoShape, ZShape, SShape, LineShape,
TShape, SquareShape, LShape, MirroredLShape };
class Shape
{
public:
Shape() { SetShape(NoShape); }
void SetShape(Tetrominoes shape);
void SetRandomShape();
Tetrominoes GetShape() const { return pieceShape; }
int x(int index) const { return coords[index][0]; }
int y(int index) const { return coords[index][1]; }
int MinX() const;
int MaxX() const;
int MinY() const;
int MaxY() const;
Shape RotateLeft() const;
Shape RotateRight() const;
private:
void SetX(int index, int x) { coords[index][0] = x; }
void SetY(int index, int y) { coords[index][1] = y; }
Tetrominoes pieceShape;
int coords[4][2];
};
#endif
Shape.cpp
#include <stdlib.h>
#include <algorithm>
#include "Shape.h"
using namespace std;
void Shape::SetShape(Tetrominoes shape)
{
static const int coordsTable[8][4][2] = {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};
for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j)
coords[i][j] = coordsTable[shape][i][j];
}
pieceShape = shape;
}
void Shape::SetRandomShape()
{
int x = rand() % 7 + 1;
SetShape(Tetrominoes(x));
}
int Shape::MinX() const
{
int m = coords[0][0];
for (int i=0; i<4; i++) {
m = min(m, coords[i][0]);
}
return m;
}
int Shape::MaxX() const
{
int m = coords[0][0];
for (int i=0; i<4; i++) {
m = max(m, coords[i][0]);
}
return m;
}
int Shape::MinY() const
{
int m = coords[0][1];
for (int i=0; i<4; i++) {
m = min(m, coords[i][1]);
}
return m;
}
int Shape::MaxY() const
{
int m = coords[0][1];
for (int i=0; i<4; i++) {
m = max(m, coords[i][1]);
}
return m;
}
Shape Shape::RotateLeft() const
{
if (pieceShape == SquareShape)
return *this;
Shape result;
result.pieceShape = pieceShape;
for (int i = 0; i < 4; ++i) {
result.SetX(i, y(i));
result.SetY(i, -x(i));
}
return result;
}
Shape Shape::RotateRight() const
{
if (pieceShape == SquareShape)
return *this;
Shape result;
result.pieceShape = pieceShape;
for (int i = 0; i < 4; ++i) {
result.SetX(i, -y(i));
result.SetY(i, x(i));
}
return result;
}
Board.h
#ifndef BOARD_H
#define BOARD_H
#include "Shape.h"
#include <wx/wx.h>
class Board : public wxPanel
{
public:
Board(wxFrame *parent);
void Start();
void Pause();
void linesRemovedChanged(int numLines);
protected:
void OnPaint(wxPaintEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnTimer(wxCommandEvent& event);
private:
enum { BoardWidth = 10, BoardHeight = 22 };
Tetrominoes & ShapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }
int SquareWidth() { return GetClientSize().GetWidth() / BoardWidth; }
int SquareHeight() { return GetClientSize().GetHeight() / BoardHeight; }
void ClearBoard();
void DropDown();
void OneLineDown();
void PieceDropped();
void RemoveFullLines();
void NewPiece();
bool TryMove(const Shape& newPiece, int newX, int newY);
void DrawSquare(wxPaintDC &dc, int x, int y, Tetrominoes shape);
wxTimer *timer;
bool isStarted;
bool isPaused;
bool isFallingFinished;
Shape curPiece;
int curX;
int curY;
int numLinesRemoved;
Tetrominoes board[BoardWidth * BoardHeight];
wxStatusBar *m_stsbar;
};
#endif
Board.cpp
#include "Board.h"
Board::Board(wxFrame *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxBORDER_NONE)
{
timer = new wxTimer(this, 1);
m_stsbar = parent->GetStatusBar();
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
ClearBoard();
Connect(wxEVT_PAINT, wxPaintEventHandler(Board::OnPaint));
Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(Board::OnKeyDown));
Connect(wxEVT_TIMER, wxCommandEventHandler(Board::OnTimer));
}
void Board::Start()
{
if (isPaused)
return;
isStarted = true;
isFallingFinished = false;
numLinesRemoved = 0;
ClearBoard();
NewPiece();
timer->Start(300);
}
void Board::Pause()
{
if (!isStarted)
return;
isPaused = !isPaused;
if (isPaused) {
timer->Stop();
m_stsbar->SetStatusText(wxT("paused"));
} else {
timer->Start(300);
wxString str;
str.Printf(wxT("%d"), numLinesRemoved);
m_stsbar->SetStatusText(str);
}
Refresh();
}
void Board::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxSize size = GetClientSize();
int boardTop = size.GetHeight() - BoardHeight * SquareHeight();
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = ShapeAt(j, BoardHeight - i - 1);
if (shape != NoShape)
DrawSquare(dc, 0 + j * SquareWidth(),
boardTop + i * SquareHeight(), shape);
}
}
if (curPiece.GetShape() != NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
DrawSquare(dc, 0 + x * SquareWidth(),
boardTop + (BoardHeight - y - 1) * SquareHeight(),
curPiece.GetShape());
}
}
}
void Board::OnKeyDown(wxKeyEvent& event)
{
if (!isStarted || curPiece.GetShape() == NoShape) {
event.Skip();
return;
}
int keycode = event.GetKeyCode();
if (keycode == 'p' || keycode == 'P') {
Pause();
return;
}
if (isPaused)
return;
switch (keycode) {
case WXK_LEFT:
TryMove(curPiece, curX - 1, curY);
break;
case WXK_RIGHT:
TryMove(curPiece, curX + 1, curY);
break;
case WXK_DOWN:
TryMove(curPiece.RotateRight(), curX, curY);
break;
case WXK_UP:
TryMove(curPiece.RotateLeft(), curX, curY);
break;
case WXK_SPACE:
DropDown();
break;
case 'd':
OneLineDown();
break;
case 'D':
OneLineDown();
break;
default:
event.Skip();
}
}
void Board::OnTimer(wxCommandEvent& event)
{
if (isFallingFinished) {
isFallingFinished = false;
NewPiece();
} else {
OneLineDown();
}
}
void Board::ClearBoard()
{
for (int i = 0; i < BoardHeight * BoardWidth; ++i)
board[i] = NoShape;
}
void Board::DropDown()
{
int newY = curY;
while (newY > 0) {
if (!TryMove(curPiece, curX, newY - 1))
break;
--newY;
}
PieceDropped();
}
void Board::OneLineDown()
{
if (!TryMove(curPiece, curX, curY - 1))
PieceDropped();
}
void Board::PieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
ShapeAt(x, y) = curPiece.GetShape();
}
RemoveFullLines();
if (!isFallingFinished)
NewPiece();
}
void Board::RemoveFullLines()
{
int numFullLines = 0;
for (int i = BoardHeight - 1; i >= 0; --i) {
bool lineIsFull = true;
for (int j = 0; j < BoardWidth; ++j) {
if (ShapeAt(j, i) == NoShape) {
lineIsFull = false;
break;
}
}
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
ShapeAt(j, k) = ShapeAt(j, k + 1);
}
}
}
if (numFullLines > 0) {
numLinesRemoved += numFullLines;
wxString str;
str.Printf(wxT("%d"), numLinesRemoved);
m_stsbar->SetStatusText(str);
isFallingFinished = true;
curPiece.SetShape(NoShape);
Refresh();
}
}
void Board::NewPiece()
{
curPiece.SetRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.MinY();
if (!TryMove(curPiece, curX, curY)) {
curPiece.SetShape(NoShape);
timer->Stop();
isStarted = false;
m_stsbar->SetStatusText(wxT("game over"));
}
}
bool Board::TryMove(const Shape& newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (ShapeAt(x, y) != NoShape)
return false;
}
curPiece = newPiece;
curX = newX;
curY = newY;
Refresh();
return true;
}
void Board::DrawSquare(wxPaintDC& dc, int x, int y, Tetrominoes shape)
{
static wxColour colors[] = { wxColour(0, 0, 0), wxColour(204, 102, 102),
wxColour(102, 204, 102), wxColour(102, 102, 204),
wxColour(204, 204, 102), wxColour(204, 102, 204),
wxColour(102, 204, 204), wxColour(218, 170, 0) };
static wxColour light[] = { wxColour(0, 0, 0), wxColour(248, 159, 171),
wxColour(121, 252, 121), wxColour(121, 121, 252),
wxColour(252, 252, 121), wxColour(252, 121, 252),
wxColour(121, 252, 252), wxColour(252, 198, 0) };
static wxColour dark[] = { wxColour(0, 0, 0), wxColour(128, 59, 59),
wxColour(59, 128, 59), wxColour(59, 59, 128),
wxColour(128, 128, 59), wxColour(128, 59, 128),
wxColour(59, 128, 128), wxColour(128, 98, 0) };
wxPen pen(light[int(shape)]);
pen.SetCap(wxCAP_PROJECTING);
dc.SetPen(pen);
dc.DrawLine(x, y + SquareHeight() - 1, x, y);
dc.DrawLine(x, y, x + SquareWidth() - 1, y);
wxPen darkpen(dark[int(shape)]);
darkpen.SetCap(wxCAP_PROJECTING);
dc.SetPen(darkpen);
dc.DrawLine(x + 1, y + SquareHeight() - 1,
x + SquareWidth() - 1, y + SquareHeight() - 1);
dc.DrawLine(x + SquareWidth() - 1,
y + SquareHeight() - 1, x + SquareWidth() - 1, y + 1);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(colors[int(shape)]));
dc.DrawRectangle(x + 1, y + 1, SquareWidth() - 2,
SquareHeight() - 2);
}
Tetris.h
#include <wx/wx.h>
class Tetris : public wxFrame
{
public:
Tetris(const wxString& title);
};
Tetris.cpp
#include "Tetris.h"
#include "Board.h"
Tetris::Tetris(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(180, 380))
{
wxStatusBar *sb = CreateStatusBar();
sb->SetStatusText(wxT("0"));
Board *board = new Board(this);
board->SetFocus();
board->Start();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Tetris.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
srand(time(NULL));
Tetris *tetris = new Tetris(wxT("Tetris"));
tetris->Centre();
tetris->Show(true);
return true;
}
我对游戏做了一些简化,以便于理解。 游戏启动后立即开始。 我们可以通过按 p
键暂停游戏。 空格键
将把俄罗斯方块放在底部。 d
键会将棋子下降一行。 (它可以用来加快下降速度。)游戏以恒定速度运行,没有实现加速。 分数是我们已删除的行数。
...
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
在开始游戏之前,我们先初始化一些重要的变量。 isFallingFinished
变量确定俄罗斯方块形状是否已完成下降,然后我们需要创建一个新形状。 numLinesRemoved
计算行数,到目前为止我们已经删除了行数。 curX
和curY
变量确定下降的俄罗斯方块形状的实际位置。
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = ShapeAt(j, BoardHeight - i - 1);
if (shape != NoShape)
DrawSquare(dc, 0 + j * SquareWidth(),
boardTop + i * SquareHeight(), shape);
}
}
游戏的绘图分为两个步骤。 在第一步中,我们绘制所有形状或已放置到板底部的形状的其余部分。 所有正方形都记在board
数组中。 我们使用ShapeAt()
方法访问它。
if (curPiece.GetShape() != NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
DrawSquare(dc, 0 + x * SquareWidth(),
boardTop + (BoardHeight - y - 1) * SquareHeight(),
curPiece.GetShape());
}
}
下一步是绘制掉落的实际零件。
...
switch (keycode) {
case WXK_LEFT:
TryMove(curPiece, curX - 1, curY);
break;
...
在Board::OnKeyDown()
方法中,我们检查按键是否按下。 如果按向左箭头键,我们将尝试将棋子向左移动。 我们说尝试,因为这片可能无法移动。
void Board::OnTimer(wxCommandEvent& event)
{
if (isFallingFinished) {
isFallingFinished = false;
NewPiece();
} else {
OneLineDown();
}
}
在Board::OnTimer()
方法中,我们可以创建一个新的片段,将前一个片段放到底部,或者将下降的片段向下移动一行。
void Board::DropDown()
{
int newY = curY;
while (newY > 0) {
if (!TryMove(curPiece, curX, newY - 1))
break;
--newY;
}
PieceDropped();
}
Board::DropDown()
方法将下落的形状立即下降到板的底部。 当我们按下空格键时会发生这种情况。
void Board::PieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
ShapeAt(x, y) = curPiece.GetShape();
}
RemoveFullLines();
if (!isFallingFinished)
NewPiece();
}
在Board::PieceDropped()
方法中,我们将当前形状设置为其最终位置。 我们调用RemoveFullLines()
方法来检查是否至少有一个完整的行。 如果尚未在Board::PieceDropped()
方法中创建新的俄罗斯方块形状,则可以创建一个新的俄罗斯方块形状。
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
ShapeAt(j, k) = ShapeAt(j, k + 1);
}
}
此代码将删除所有行。 找到整条线后,我们增加计数器。 我们将整行上方的所有行向下移动一行。 这样我们就破坏了整个生产线。 注意,在俄罗斯方块游戏中,我们使用了朴素引力。 这意味着正方形可能会漂浮在空白间隙上方。
void Board::NewPiece()
{
curPiece.SetRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.MinY();
if (!TryMove(curPiece, curX, curY)) {
curPiece.SetShape(NoShape);
timer->Stop();
isStarted = false;
m_stsbar->SetStatusText(wxT("game over"));
}
}
Board::NewPiece()
方法随机创建一个新的俄罗斯方块。 如果棋子无法进入其初始位置,则游戏结束。
bool Board::TryMove(const Shape& newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (ShapeAt(x, y) != NoShape)
return false;
}
curPiece = newPiece;
curX = newX;
curY = newY;
Refresh();
return true;
}
在Board::TryMove()
方法中,我们尝试移动形状。 如果形状在棋盘的边缘或与其他形状相邻,则返回false
。 否则,我们将当前下降形状放置到新位置并返回true
。
Shape
类保存有关俄罗斯方块的信息。
for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j)
coords[i][j] = coordsTable[shape][i][j];
}
coords
数组保存俄罗斯方块的坐标。 例如,数字{0, -1}, {0, 0}, {1, 0}, {1, 1}
表示旋转的 S 形。 下图说明了形状。
图:坐标
当绘制当前下降片时,将其绘制在curX
和curY
位置。 然后,我们查看坐标表并绘制所有四个正方形。
图:俄罗斯方块
这是 wxWidgets 中的俄罗斯方块游戏。
wxPython 教程
这是 wxPython 教程。 在本教程中,您将学习 wxPython 中 GUI 编程的基础。 本教程适合初学者和中级程序员。
本教程介绍了 wxPython Phoenix 版本 4.0.1。 可在 wxPython-examples 仓库中获得示例的源代码。
目录
wxPython
wxPython 是用于创建桌面 GUI 应用的跨平台工具包。 使用 wxPython,开发者可以在 Windows,Mac OS 和各种 Unix 系统上创建应用。 wxPython 是 wxWidgets 的包装,wxWidgets 是成熟的跨平台 C++ 库。
电子书
独特的电子书,涵盖 wxPython 的高级功能:高级 wxPython 教程
相关教程
在 ZetCode 上有完整的 Python 教程。 其他 Python 绑定的教程包括 PyQt4 教程, PyQt5 教程, PySide 教程, Python Gtk 教程和 Tkinter 教程。
标签:wxT,教程,int,GUI,dc,public,new,include,ZetCode From: https://www.cnblogs.com/apachecn/p/18500134