用 VS Code 搞 Qt6:信号、槽,以及QObject

Qt 里面的信号(Signal)和槽(Slot)虽然看着像事件 , 但它实际上是用来在两个对象之间进行通信的 。既然是通信,就会有发送者和接收者 。
1、信号是发送者,触发时通过特有的关键字“emit”来发出信号 。
2、槽是信号的接收者,它实则是一个方法(函数 )成员,当收到信号后会被调用 。
为了让C++类能够使用信号和槽机制,必须从 QObject 类派生 。QObject 类是 Qt 对象的公共基类 。它的第一个作用是让 Qt 对象之形成一株“对象树” 。当某个 Qt 对象发生析构时 , 它的子级对象都会发生析构 。比如 , 窗口中包含两个按钮,当窗口类析构时,里面的两个按钮也会跟着发生析构 。所以 , 在 Qt 的窗口应用程序里面,一般不用手动去 delete 指针类型的对象 。位于对象树上的各个对象会自动清理 。
QObject 类的另一个关键作用是实现信号和槽的功能 。
1、从 QObject 类派生的类,在类内部要使用 Q_OBJECT 宏 。
2、跟在 signals 关键字后面的函数被视为信号 。这个关键字实际上是 Q_SIGNALS 宏,是 Qt 项目专用的,并不是 C++ 的标准关键字 。
3、跟在 slots 或 public slots 后面的成员函数(方法)被认为是槽,当接收到信号时会自动调用 。
信号和槽之间相互不认识,需要找个“媒婆”让它们走到一起 。因此,在发出信号前要调用 QObject :: connect 方法在信号与槽之间建立连接 。
老周不喜欢说得太复杂,上面的介绍应该算比较简洁了,接下来咱们来个示例,就好理解了 。
这里老周定义了两个类:DemoObject 类里面包含了一个 QStack<int> 对象,是个栈集合 , 这个应该都懂,后进先出 。两个公共方法,AddOne 用来向 Stack 对象压入元素,TakeOne 方法从 Stack 对象中弹出一个元素 。不过 , 弹出的元素不是经 TakeOne 方法返回,而是发出 GetItem 信号,用这个信号将弹出的元素发送给接收者(槽在 TestRecver 类中) 。第二个类是 TestRecver,对,上面 DemoObject 类发出的 GetItem 信号可以在 TestRecver 类中接收,槽函数是 setItem 。
#include <iostream>#include <qobject.h>#include <qstack.h>class DemoObject : public QObject{// 这个是宏Q_OBJECTprivate:QStack<int> _inner;public:void AddOne(int val){_inner.push(val);}void TakeOne(){if(_inner.empty()){return;}int x = _inner.pop();// 发出信号emit GetItem(x);}// 信号signals:void GetItem(int n);};class TestRecver : public QObject{// 记得用这个宏Q_OBJECT// 槽public slots:void setItem(int n){std::cout << "取出项:" << n << std::endl;}};在 main 函数中,先创建 DemoObject 实例,用 AddOne 方法压入三个元素 。然后创建 TestRecver 实例,用 connect 方法建立信号和槽的连接 。
int main(int argc, char **argv){DemoObject a;a.AddOne(50);a.AddOne(74);a.AddOne(80);TestRecver r;// 信号与槽连接QObject::connect(&a, &DemoObject::GetItem, &r, &TestRecver::setItem);// 下面这三行会发送GetItem信号a.TakeOne();a.TakeOne();a.TakeOne();return 0;}下面是 CMakeLists.txt 文件:
【用 VS Code 搞 Qt6:信号、槽,以及QObject】cmake_minimum_required(VERSION 3.0.0)project(myapp LANGUAGES CXX)find_package(Qt6 REQUIRED COMPONENTS Core)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_AUTOMOC ON)add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE Qt6::Core)注意 , 这里一定要把 CMAKE_AUTOMOC 选项设置为 ON , 1 , 或者 YES 。因为我们用到了 Q_OBJECT 宏,它需要 MOC 生成一些特定C++代码和元数据 。这个示例只用到 QtCore 模块的类,所以 find_package 和 target_link_libraries 中只要引入这个就行 。
当你兴奋异常地编译和运行本程序时,会发生错误:

用 VS Code 搞 Qt6:信号、槽,以及QObject

文章插图
这个错误是因为 MOC 生成的代码最终要用回到我们的程序中的,但代码文件没有包含这些代码 。所以你看上面已经提示你了,解决方法是包含 main.moc 。这个文件名和你定义 DemoObject 类的代码文件名相同 。我刚刚的代码文件是 main.cpp,所以它生成的代码文件就是 main.moc 。
不过 , #include 指令一定要写在 DemoObject 和 TestRecver 类的定义之后,这样才能正确放入生成的代码 。# include 放在文件头部仍然会报错的 , 此时,DemoObject 和 TestRecver 类还没有定义,无法将 main.moc 中的源代码插入到 main.cpp 中(会找不到类) 。
#include <iostream>#include <qobject.h>#include <qstack.h>class DemoObject : public QObject{// 这个是宏Q_OBJECT……};class TestRecver : public QObject{// 记得用这个宏Q_OBJECT……};#include "main.moc"int main(int argc, char **argv){……return 0;}

推荐阅读