OPC Client第3讲(wxwidgets):wxFormBuilder;基础框架;事件处理
wxwidgets开源桌面软件框架使用 - 哔哩哔哩
wxwidgets跨平台GUI框架使用入门详解_哔哩哔哩_bilibili
一、wxwidgets配置【见上一讲五、】
二、安装wxFormBuilder
1、wxFormBuilder介绍、安装
wxFormBuilder是一个开源的GUI设计工具,支持C++、Python等语言,提供所见即所得的界面设计。
虽然事件处理需要手动实现,但自动生成的代码能作为基础模板。该工具适用于快速构建如登录页面等简单的GUI应用。
第四章文本输入控件TextCtrl_哔哩哔哩_bilibili
wxFormBuilder使用介绍-CSDN博客
2、 wxFormBuilder启动
在上图的终端里输入下述命令,启动wxFormBuilder:
cd wxFormBuilder
_install/wxFormBuilder
3、自动生成的代码,复原工程代码
1>可以自动生成c++、python等代码
2>复原工程代码
法1》 保存为.fbp文件
.fbp 文件是 wxFormBuilder 项目的文件格式,用于保存使用 wxFormBuilder 设计的界面布局和相关设置。
法2》file->Import XRC...
.xrc 文件是 wxWidgets 库使用的一种资源文件格式,用于以 XML 形式描述用户界面元素。
把上图自动生成的XRC文件复制粘贴,另存为一个新的.xrc文件,如下图
然后再通过下图的file->Import XRC...导入上图的.xrc文件
4、预览窗口
View->XRC window
5、导出代码
.fbp 文件是 wxFormBuilder 项目的文件格式,用于保存使用 wxFormBuilder 设计的界面布局和相关设置。
1>导出代码方法:file->Generate Code
会将生成的源代码(.c/.cpp)保存到工程文件(.fbp)的目录下,文件名为第一步的配置。
三、wxwidgets基础框架
前提:理解多态中的虚函数
53 类和对象-多态-多态的基本语法_哔哩哔哩_bilibili
共分为六个部分,见下述代码一、到六、【也是写代码的逻辑顺序】
注意:宏定义可以写在前面
IMPLEMENT_APP(MainApp);可以写在MainApp::OnInit实现函数之前,只要写在MainApp::OnInit()声明之后就行!
1、不把头文件和实现文件分开
#include<wx/wx.h>// 导入系统路径wx文件夹下的wx.h头文件// 包含 wxWidgets 的核心头文件,提供 GUI 应用程序的基本功能。// 一、每一个wxWidgets程序都需要定义一个wxApp类的子类,并且需要创建并且只能创建一个这个类的实例,这个实例控制着整个程序的执行
class MainApp : public wxApp
{
public:// 你的这个继承自wxApp的子类至少需要定义一个OnInit函数,当wxWidgets准备好运行你写的代码的时候,它将会调用这个函数// 为什么OnInit是虚函数?:目的是允许子类重写这个函数,从而实现特定的初始化逻辑virtual bool OnInit();
};// 三、自定义主窗口类InitFrame,内容为:
// 1、有一个构造函数。目的为当自动调用父函数的构造函数时,传入自定义的窗口的标题
// 2、两个用来把菜单命令和C++代码相连的事件处理函数
// 3、还有一个宏来声明事件表
class InitFrame : public wxFrame
{
public:InitFrame(const wxString& title);void OnQuit(wxCommandEvent& event);void OnAbout(wxCommandEvent& event);
private:DECLARE_EVENT_TABLE()//声明事件表//在类的实现文件中使用 BEGIN_EVENT_TABLE 和 END_EVENT_TABLE 宏,并列出具体的事件绑定【略】
};//四、创建MainApp的实例,提供应用程序的入口点(里面有 main() 函数,调用运行MainApp类里的OnInit函数)
// 只要声明了MyApp,就可以用IMPLEMENT_APP(MainApp);创建实例了
//实现应用程序的入口点,并告诉框架需要创建哪个 wxApp 子类的实例
IMPLEMENT_APP(MainApp)//五、使用MainApp的实例【有了这一行就可以使用MainApp& wxGetApp了()】
//当wxWidgets创建这个MainApp类的实例的时候,会将创建的结果赋值给一个全局变量wxTheApp,类型为 wxApp*。它是所有 wxApp 实例的指针引用。
//【wxTheApp作用:通过 wxTheApp,你可以访问应用程序的全局状态或调用 wxApp 提供的方法】
// 法1、DECLARE_APP(MainApp);
// 法2、强制类型转换后使用子类方法
// 提示:如果直接使用 wxTheApp,因为不是虚函数,所以编译器只会调用基类wxApp的方法,而不是MainApp里的方法!
DECLARE_APP(MainApp)// 二、根据虚函数的定义,子类要重写虚函数
// 在这个OnInit函数中,通常应该作的事情包括:创建至少一个窗口实例,对传入的命令行参数进行解析,为应用程序进行数据设置和其它的一些初始化的操作.
// 如果这个函数返回真,wxWidgets将开始事件循环用来接收用户输入并且在必要的情况下处理这些输入。
// 如果OnInit函数返回假,wxWidgets将会释放它内部已经分配的资源,然后结束整个程序的运行。
bool MainApp::OnInit()
{//1、创建窗口实例:需要用到自定义主窗口类InitFrameInitFrame *frame = new InitFrame(wxT("wxWidgets App"));//传入自定义的窗口的标题"wxWidgets App"//wxT这个宏:作用是让代码兼容Unicode模式frame->Show(true);//显示窗口(即让窗口可见)return true;
}//六、接下来就是处理 自定义主窗口类InitFrame【略,具体见开发文档】
// 1、InitFrame构造函数的实现
// 2、里面的OnQuit和OnAbout函数的实现
// 3、在类的实现文件中使用 BEGIN_EVENT_TABLE 和 END_EVENT_TABLE 宏,并列出具体的事件绑定。
//类的事件表InitFrame//实现六、3、
BEGIN_EVENT_TABLE(InitFrame, wxFrame)EVT_MENU(wxID_ABOUT, InitFrame::OnAbout)EVT_MENU(wxID_EXIT, InitFrame::OnQuit)
END_EVENT_TABLE()//实现六、2、
void InitFrame::OnAbout(wxCommandEvent& event)
{wxString msg;msg.Printf(wxT("Hello and welcome to %s"),wxVERSION_STRING);wxMessageBox(msg, wxT("About Minimal"),wxOK | wxICON_INFORMATION, this);
}//实现六、2、
void InitFrame::OnQuit(wxCommandEvent& event)
{//释放主窗口Close();
}//实现六、1、
InitFrame::InitFrame(const wxString& title): wxFrame(NULL, wxID_ANY, title)
{//创建菜单条wxMenu *fileMenu = new wxMenu;//添加“关于”菜单项wxMenu *helpMenu = new wxMenu;helpMenu->Append(wxID_ABOUT, wxT("&About...\tF1"),wxT("Show about dialog"));fileMenu->Append(wxID_EXIT, wxT("E&xit\tAlt−X"),wxT("Quit this program"));//将菜单项添加到菜单条中wxMenuBar* menuBar = new wxMenuBar();menuBar->Append(fileMenu, wxT("&File"));menuBar->Append(helpMenu, wxT("&Help"));//然后将菜单条放置在主窗口上..SetMenuBar(menuBar);// 创建一个状态条来让一切更有趣些。CreateStatusBar(2);SetStatusText(wxT("Welcome to wxWidgets!"));
}
2、分开,有InitFrame.h/.cpp
3、区别父类、父窗口
1>父类:就是C++面向对象编程(OOP)中的继承关系
2>父窗口:GUI 编程中的容器关系
4、窗口的基础知识
内容太多,直接看文档。如果想要搜索某个控件,直接在文档里ctrl+f
四、在项目中的用法
1、项目代码运行过程
1>点击代码程序运行
2>main.cpp内部使用wxWidgets 库,构建显示InitFrame定义的配置窗口,窗口如下图
InitFrame.h/.cpp 作用:读取配置文件,填写或者修改配置信息,执行自己的逻辑,修改配置文件,然后将配置信息作为参数传递给下一个窗口,并关闭当前窗口。
3>切换窗口显示MainFrame窗口,窗口如下图
MainFrame.h/.cpp 作用:将配置信息分别对内部的数据库、连接的机台IP、缓冲区设置等进行初始化,初始化完成之后显示主窗口,在定时器中执行批量连接并订阅节点,完成之后开启定义的线程用于轮询连接的机台,至此主要的框架就开始运行。
4>监听订阅数据的变化,以及机台的连接状态和socket的状态,都有可以自动处理的逻辑。
2、写代码,对应二、中框架
运行代码,出现下述窗口
3、补充完整 InitFrame.h/.cpp:见下一章
即实现下述窗口
五、wxWidgets事件处理——如何添加新功能?
1、静态事件处理方法(最常用,但5、动态绑定更方便)
1>原理:大多数与用户交互的核心组件(如窗口、控件等)都继承自 wxEvtHandler,因为它们需要支持事件处理机制
2>举例,原则如下
下图中的MyFrame就是二、中的InitFrame
1》所有的事件处理函数拥有相同的形式【如下图2里的红色标记】
所有的事件处理函数返回值都是void,都不是虚函数,都只有一个事件对象作为参数。
- 事件对象参数的类型是随这个处理函数要处理的事件的变化而变化的。
- 例如简单控件(比如按钮)的事件命令处理函数和菜单命令事件的处理函数的参数都是wxCommandEvent类型;
- 而size事件(这个事件通常是由用户改变窗口的客户区尺寸而引起的)处理函数的参数则是wxSizeEvent的类型。
2》举例
3>运行过程【例子见3、4>】
当点击按钮后,会通过wxID查找其绑定的事件。
如果绑定的事件是Command事件,在自己的父类里面找不到这个事件的话,可以被递归地去父窗口里面查找这个事件;需要在后面加上wxEvent::Skip函数。
注意其它事件(例如wxSizeEvent,wxPanel等)是不会去父窗口里面查找的,只会在自己的父类里面查找;需要在后面加上wxEvent::Skip函数。
- 这些事件之所以不会传递给其父窗口,是因为这些事件仅对产生这个事件的窗口才有意义。
- 举例来说,一个子窗口的重绘事件发送给它的父亲,其实是没有任何意义的。
- 如果需要传递给父窗口,可以通过 wxPostEvent (GetParent() ,wxCommandEvent(event.GetEventTvpe());传递
3、过滤某个事件【继承自wxTextCtrl类,EVT_KEY_DOWN宏,wxEvent::Skip函数】
1>原理:可以 过滤某些按键事件 以便 本地原生的编辑框控件 不处理这些按键。
wxEvent::Skip函数:让事件继续传递
2>使用步骤
1》需要实现一个继承自wxTextCtrl的新的类,
2》然后在其事件表中使用EVT_KEY_DOWN事件映射宏。
3》过滤所有的你不想要的按键事件,wxEvent::Skip函数来提示事件处理过程对于其中的某些按键事件应该继续寻找其父类的事件表.
3>举例
4>Command 事件的传播顺序:控件 -> 父类 -> 父窗口(利用 wxEvent::Skip)
Command事件:1、3>里的
从下述链接14:27开始看
第三章事件处理_哔哩哔哩_bilibili
1》class FirstButton : public wxButton
上图对应的.cpp文件如下,加上event.Skip;
定义了按钮的响应事件:wxID为8000,绑定的事件为OnButton
2》class SecondButton : public FirstButton
上图对应的.cpp文件如下,加上event.Skip;
定义了按钮的响应事件:wxID为8000,绑定的事件为OnButton
3》调用2》中的SecondButton
MyFrame.h主框架里面定义了SecondButton
上图对应的.cpp文件如下。
《1》定义了按钮的响应事件:wxID为8000,绑定的事件为OnButton
里面也加上了event.Skip
《2》在MyFrame.cpp的构造函数中创建了SecondButton的实例
如果用户点击主界面的"Button"按钮,通过wxID='8000'查找其绑定的事件SecondButton,就会先进入SecondButton里查找wxID为8000的事件;
同时,mButton->Create(this, 8000,"BUTTON")的第一个参数指的是父窗口(即MyFrame)
4》运行代码,查看结果
代码先进入MyFrame.h主框架里面,运行MyFrame的构造函数,显示出下述的窗口界面
由3》《2》可知,其中的"Button"按钮是绑定在SecondButton上的,并且设定了其wxID='8000'
《1》点击上图中的按钮BUTTON后,就会先进入SecondButton里查找其wxID=‘8000’的事件处理器,如下图
《2》由于class SecondButton : public FirstButton,且里面还有event.Skip:所以 Command事件 继续传递到父类FirstButton。
在父类FirstButton里查找其wxID=‘8000’的事件处理器。
点击上图的OK,出现下图
《3》由于class FirstButton : public wxButton,且里面还有event.Skip:所以 Command事件 会继续传递到父类wxButton。父类wxButton没有输出,至此父类阶段结束。
《5》当所有父类处理完成后,事件进入父窗口传播阶段
在父类阶段结束后,由于 SecondButton 在 MyFrame.h主框架的构造函数中,说明了其父窗口是 MyFrame(见3》),所以开始父窗口的传递
在父窗口MyFrame里查找其wxID=‘8000’的事件处理器。
点击上图的OK,出现下图
5》由此,证明了 1、3>中的 Command 事件的传播顺序:控件 -> 父类 -> 父窗口(利用 wxEvent::Skip函数)
如果不想往上传,不加wxEvent::Skip函数就行了
4、挂载事件表【略】
wxWindow::PopEventHandler和wxWindow::PopEventHandler
5、动态事件处理方法(即动态绑定:Bind/Unbind;Connect/Disconnect)
前面我们讨论的事件处理方法,都是静态的事件表,这也是我们处理事件最常用的方式。
动态绑定:在运行期改变事件表的映射关系
1>区别动态绑定和静态绑定
1》什么叫动态绑定在“运行时”?
核心:动态绑定在“运行时”指可以对同一个事件绑定新的事件处理器(通过 Unbind() 和 Bind() 动态绑定到另一个新的事件处理器);但是静态绑定的一个事件只能绑定一个事件处理器,一旦编译完成就不能更改。
2>动态绑定两种方法:Bind/Unbind;Connect/Disconnect
MyFrame.h主框架如下图
上图对应的.cpp文件如下。
1》动态绑定法1:Bind/Unbind【更推荐】
法《1》
法《2》直接用Lambada表达式
2》动态绑定法2:Connect/Disconnect
《1》不需要手动调用wxEvtHandler::Disconnect函数,这个函数将在窗口类被释放的时候自动被调用。
《2》如果事件处理函数的 参数类型是wxXYZEvent,那么其处理函数的类型就应该用wxXYZEventHandler宏进行强制转换
在 wxWidgets 中,事件处理函数的类型需要与事件的实际参数类型匹配。为了确保类型安全和正确性,通常会使用 wxXYZEventHandler 宏将函数指针强制转换为对应的事件处理器类型。
- Bind/Unbind 不需要显式地使用 wxXYZEventHandler 宏。
- 这是因为 Bind/Unbind 使用了 C++ 的模板机制,能够自动推导出事件处理器的类型,从而避免了手动强制类型转换的需求。
- Connect/Disconnect 是较老的方法,没有模板支持,必须显式指定事件处理器的类型。
- 例如 wxMouseEventHandler 或 wxCommandEventHandler
《3》举例