一、代码
在上一篇,我已经把 HelloWorld 的代码写了出来,这里为了方便介绍,我重新贴一遍:
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
class MyFrame : public wxFrame
{
public:
explicit MyFrame(const wxString &title)
: wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
// 创建一个面板
auto *panel = new wxPanel(this, wxID_ANY);
// 创建一个按钮
auto *button = new wxButton(panel, wxID_ANY, wxT("Click me!"),
wxPoint(60, 40), wxSize(100, 30));
// 绑定按钮点击事件
button->Bind(wxEVT_BUTTON, &MyFrame::OnButtonClicked, this);
}
private:
void OnButtonClicked(wxCommandEvent &event)
{
wxUnusedVar(event);
wxMessageBox(wxT("Hello, wxWidgets!"), wxT("Hello"), wxOK | wxICON_INFORMATION);
}
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_BUTTON(wxID_ANY, MyFrame::OnButtonClicked)
wxEND_EVENT_TABLE()
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
auto *frame = new MyFrame(wxT("Hello wxWidgets!"));
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(MyApp); // NOLINT
二、详细介绍
1. 头文件包含部分
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
其实不想折腾,可以直接写成这样:
#include <wx/wx.h>
它们的主要区别在于“预编译”。启用预编译头可以提高编译速度,但在某些情况下可能导致额外的配置和兼容性问题(虽然这种情况目前我没遇到)。如果你不确定是否需要预编译头,那么直接包含 wx/wx.h
是一个更简单的选择。
2. 程序入口
我们直接跳转到代码段的最底部,看到这句:
wxIMPLEMENT_APP(MyApp); // NOLINT
这是 wxWidgets 中用于定义应用程序入口点的宏。这个宏会自动生成一个 main
函数(在 Windows 上为 WinMain
函数),该宏定义会创建应用程序类的实例并启动程序的事件循环。
MyApp
是自定义的应用程序类,它必须从 wxApp
类派生(代码中有体现,稍后会介绍)。wxIMPLEMENT_APP()
宏将 MyApp
与库中的实际入口点关联起来。这样,当程序启动时,wxWidgets 便知道如何创建应用程序类的实例并调用 OnInit()
方法来初始化应用程序。
那么我们可能会有个疑问,wxWidgets 为什么要隐藏 main
函数呢?我认为无非就两个原因:
- 跨平台兼容性:wxWidgets 是一个跨平台的库,可以在多种操作系统(如 Windows、macOS 和 Linux)上运行。不同的平台可能有不同的应用程序入口点要求。隐藏
main
函数,让 wxWidgets 来处理平台相关的差异,使得我们的应用程序代码保持跨平台兼容。 - 简化程序开发:隐藏
main
函数,wxWidgets 将会帮助我们处理平台底层的细节,这样我们开发时就可以专注于实现自定义的应用程序类和其他功能,而不需要关心平台相关的细节。
当然,有些小伙伴还是想自己操纵 main
函数(例如我),这完全可以,而且 wxWidgets 也没有难为我们,下面是自定义 main
函数的例子:
- Windows
wxIMPLEMENT_APP_NO_MAIN(MyApp); // NOLINT
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wxCmdLineArgType lpCmdLine, int nCmdShow)
{
return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
- Linux
wxIMPLEMENT_APP_NO_MAIN(MyApp); // NOLINT
int main(int argc, char **argv)
{
return wxEntry(argc, argv);
}
-
macOS
没钱买电脑……我猜跟 Linux 的写法是一样的。
上述代码中,我们使用 wxIMPLEMENT_APP_NO_MAIN
替换了原来的 wxIMPLEMENT_APP
。这种写法既保留了 wxWidgets 为我们做的一些预处理工作,又可以自定义 main
函数,是我的理想化答案。
3. MyAPP 应用程序类
顾名思义,这是应用程序的入口类。它本身并没有创建窗口,它的指责只是创建一个应用程序,并且让它运行起来。一个应用程序,它可能有一个或多个窗口,但它只有一个程序入口(如果用“每个程序一个窗口”的方式来实现多窗口,我想你可能要用到进程间通信)。这个示例中的 MyAPP
定义如下:
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
auto *frame = new MyFrame(wxT("Hello wxWidgets!"));
frame->Show(true);
return true;
}
};
应该很好理解,做最简单的程序,我们只需要对 wxApp
类的 OnInit
虚函数进行重定义(当然你想把函数声明部分写成 “bool OnInit() override
” 我也不拦着你,看个人习惯)。OnInit
函数是 wxApp
类的一个重要成员,它在应用程序启动时被调用,用于执行应用程序的初始化操作。
函数体第一句代码:创建了一个名为 frame
的指针,指向 MyFrame
类的实例。MyFrame
类是一个从 wxFrame
派生的自定义类,表示应用程序的主窗口。这里将窗口标题设置为 “Hello wxWidgets!”。
函数体第二句代码:显示主窗口。其实 Show
的参数写不写都无所谓,它默认参数就是 true
。
函数体第三句代码:返回 true
,表示应用程序初始化成功,如果返回 false
,将被认为初始化失败,程序将退出,main
函数体中调用的 wxEntry
函数将返回非0值(我这里返回了255)。
4. MyFrame 主窗口
// 就是剩下的那部分代码。代码有点长,不重新贴出来了,以免觉得我在水文……
MyFrame
继承于 wxFrame
类,它是一个 wxWidgets 的顶级窗口类,通常用于创建主窗口。继承wxFrame
来创建主窗口类的原因是因为 wxFrame
提供了许多方便的方法来管理窗口的生命周期,处理窗口事件,以及与操作系统交互。通过继承 wxFrame
,我们可以轻松地实现自定义的主窗口类,并利用 wxFrame
提供的方法来处理窗口的生命周期和事件。
以上是比较官方的话(当然这不是官方的话),实际情况是——我不继承 wxFrame
,我似乎没法创建主窗口,直接继承 wxWindow
?好像它默认就没法直接显示到系统UI上,因为它需要有父对象,而父对象……wxFrame
就很不错,它父对象可以设置为空。
wxFrame
有一个特点,就是如果它只有一个直属的控件,那么这个控件就会布满整个 wxFrame
的客户区域(客户区域指的就是用户主要操作的区域,排除那些默认边框、默认顶部栏等部分)。在这个程序中,只有一个类型为 wxPanel
的面板 panel
的父对象设置成 this, panel
自然而然就占据了整个客户区域:
auto *panel = new wxPanel(this, wxID_ANY);
接下来,我们又在面板 panel
中添加了一个名为 button
的按钮:
auto *button = new wxButton(panel, wxID_ANY, wxT("Click me!"),
wxPoint(60, 40), wxSize(100, 30));
按钮标题为“Click me!”,且位于 panel
的 (60, 40) 位置,大小为 (100, 30)。
创建完按钮,我们又为按钮绑定了一个点击事件,事件ID为 wxEVT_BUTTON
:
button->Bind(wxEVT_BUTTON, &MyFrame::OnButtonClicked, this);
是不是看不出来它是个点击事件ID?没错,我也看不出来[狗头]。当然还有其他的较为精细化的事件,例如:wxEVT_LEFT_DOWN、wxEVT_LEFT_UP、wxEVT_MIDDLE_DOWN、wxEVT_MIDDLE_UP、wxEVT_RIGHT_DOWN、wxEVT_RIGHT_UP、wxEVT_MOTION 等,这些事件并不是按钮特有的,我们可以继承 wxWindow
并通过实现这些事件来自定义一个按钮,当然这是后话。
接着,我们定义了一个按钮事件触发函数:
void OnButtonClicked(wxCommandEvent &event)
{
wxUnusedVar(event);
wxMessageBox(wxT("Hello, wxWidgets!"), wxT("Hello"), wxOK | wxICON_INFORMATION);
}
这个函数的参数是由事件决定的,wxEVT_BUTTON
事件对应的事件对象类型就是 wxCommandEvent
。在函数中,我们创建了一个消息对话框,对话框的标题为“Hello”,内容为“Hello, wxWidgets!”。
往下看,类声明中还有一句代码:
wxDECLARE_EVENT_TABLE();
熟悉MFC的小伙伴应该知道它的作用,它用于创建事件表,而事件表的定义就在类声明的下方:
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_BUTTON(wxID_ANY, MyFrame::OnButtonClicked)
wxEND_EVENT_TABLE()
其实这个事件表有点多此一举,因为在创建按钮时,已经使用了 Bind
函数进行了绑定。这里单纯是为了把事件表的使用方式给展示出来,大家在实际使用时可二选一。具体 wxWidgets
的事件介绍我放在了后面的章节。
三、其他细节问题
-
在程序中,我们可以看到凡是输入文本的地方,我们都用了
wxT
宏。这个宏的主要作用就是把文本类型转换为wxWidgets
能识别和显示的文本类型。就目前 3.2 而言,在Windows下,这个宏加不加都不会有什么影响,而 Linux 下,去掉这个宏,中文就没法显示了,当然你可以使用“宽字符串”来wxWidgets
正确显示中文,它在wxUSE_UNICODE
宏未定义的情况下等同于wxT
,例如:L"你好,wxWidgets!"
。 -
wxID_ANY
表示由wxWidgets
自动分配ID,这些自动分配的ID都是负数,也就是说,如果我们要自定义ID,就不要把ID设置成负数,否则很容易与 wxWidgets 自动分配的ID冲突。
【wxWidgets 教程】HelloWorld 程序详细介绍(二) 至此完毕,欢迎大家指正!还请大家点点赞,给我点动力~~
上一篇:【wxWidgets 教程】安装、配置、HelloWorld篇(一)
下一篇:【wxWidgets 教程】事件篇Ⅰ(三)