【wxWidgets 教程】HelloWorld 程序详细介绍(二)

一、代码

在上一篇,我已经把 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 的父对象设置成 thispanel 自然而然就占据了整个客户区域:

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_DOWNwxEVT_LEFT_UPwxEVT_MIDDLE_DOWNwxEVT_MIDDLE_UPwxEVT_RIGHT_DOWNwxEVT_RIGHT_UPwxEVT_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 的事件介绍我放在了后面的章节。

三、其他细节问题

  1. 在程序中,我们可以看到凡是输入文本的地方,我们都用了 wxT 宏。这个宏的主要作用就是把文本类型转换为 wxWidgets 能识别和显示的文本类型。就目前 3.2 而言,在Windows下,这个宏加不加都不会有什么影响,而 Linux 下,去掉这个宏,中文就没法显示了,当然你可以使用“宽字符串”来 wxWidgets 正确显示中文,它在 wxUSE_UNICODE 宏未定义的情况下等同于 wxT,例如:L"你好,wxWidgets!"

  2. wxID_ANY 表示由 wxWidgets 自动分配ID,这些自动分配的ID都是负数,也就是说,如果我们要自定义ID,就不要把ID设置成负数,否则很容易与 wxWidgets 自动分配的ID冲突


【wxWidgets 教程】HelloWorld 程序详细介绍(二) 至此完毕,欢迎大家指正!还请大家点点赞,给我点动力~~

上一篇:【wxWidgets 教程】安装、配置、HelloWorld篇(一)
下一篇:【wxWidgets 教程】事件篇Ⅰ(三)

wxWidgets教程是一系列文章或资源,旨在帮助读者了解和学习如何使用wxWidgets构建跨平台的GUI应用程序。这些教程可以包括基本知识、准备工作、创建第一个wxWidgets程序、控件的使用以及其他相关主题。通过阅读这些教程,读者可以逐步了解和掌握wxWidgets的功能和用法。一些常见的wxWidgets教程资源包括官方文档、书籍和在线教程。在官方文档中,您可以找到关于wxWidgets详细说明和使用指南。而书籍则提供了更深入的介绍和实例,帮助读者更好地理解和应用wxWidgets。此外,还有一些在线教程和博客文章,提供了一些实用的技巧和经验分享。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [wxWidgets完整教程专栏完整目录 cpp](https://blog.csdn.net/update7/article/details/130023533)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [wxWidgets教程(中文)](https://download.csdn.net/download/sweetbo/4871081)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao_Ley

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值