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


    用 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 对象,是个栈集合,这个应该都懂,后进先出。两个公共方法,AddOne 用来向 Stack 对象压入元素,TakeOne 方法从 Stack 对象中弹出一个元素。不过,弹出的元素不是经 TakeOne 方法返回,而是发出 GetItem 信号,用这个信号将弹出的元素发送给接收者(槽在 TestRecver 类中)。第二个类是 TestRecver,对,上面 DemoObject 类发出的 GetItem 信号可以在 TestRecver 类中接收,槽函数是 setItem。

    本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

    1. #include
    2. #include
    3. #include
    4. class DemoObject : public QObject
    5. {
    6. // 这个是宏
    7. Q_OBJECT
    8. private:
    9. QStack<int> _inner;
    10. public:
    11. void AddOne(int val)
    12. {
    13. _inner.push(val);
    14. }
    15. void TakeOne()
    16. {
    17. if(_inner.empty()){
    18. return;
    19. }
    20. int x = _inner.pop();
    21. // 发出信号
    22. emit GetItem(x);
    23. }
    24. // 信号
    25. signals:
    26. void GetItem(int n);
    27. };
    28. class TestRecver : public QObject
    29. {
    30. // 记得用这个宏
    31. Q_OBJECT
    32. // 槽
    33. public slots:
    34. void setItem(int n)
    35. {
    36. std::cout << "取出项:" << n << std::endl;
    37. }
    38. };

    在 main 函数中,先创建 DemoObject 实例,用 AddOne 方法压入三个元素。然后创建 TestRecver 实例,用 connect 方法建立信号和槽的连接。

    1. int main(int argc, char **argv)
    2. {
    3. DemoObject a;
    4. a.AddOne(50);
    5. a.AddOne(74);
    6. a.AddOne(80);
    7. TestRecver r;
    8. // 信号与槽连接
    9. QObject::connect(&a, &DemoObject::GetItem, &r, &TestRecver::setItem);
    10. // 下面这三行会发送GetItem信号
    11. a.TakeOne();
    12. a.TakeOne();
    13. a.TakeOne();
    14. return 0;
    15. }

    下面是 CMakeLists.txt 文件:

    1. cmake_minimum_required(VERSION 3.0.0)
    2. project(myapp LANGUAGES CXX)
    3. find_package(Qt6 REQUIRED COMPONENTS Core)
    4. set(CMAKE_CXX_STANDARD 17)
    5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
    6. set(CMAKE_AUTOMOC ON)
    7. add_executable(myapp main.cpp)
    8. target_link_libraries(myapp PRIVATE Qt6::Core)

    注意,这里一定要把 CMAKE_AUTOMOC 选项设置为 ON,1,或者 YES。因为我们用到了 Q_OBJECT 宏,它需要 MOC 生成一些特定C++代码和元数据。这个示例只用到 QtCore 模块的类,所以 find_package 和 target_link_libraries 中只要引入这个就行。

    当你兴奋异常地编译和运行本程序时,会发生错误:

     

    这个错误是因为 MOC 生成的代码最终要用回到我们的程序中的,但代码文件没有包含这些代码。所以你看上面已经提示你了,解决方法是包含 main.moc。这个文件名和你定义 DemoObject 类的代码文件名相同。我刚刚的代码文件是 main.cpp,所以它生成的代码文件就是 main.moc。

    不过,#include 指令一定要写在 DemoObject 和 TestRecver 类的定义之后,这样才能正确放入生成的代码。# include 放在文件头部仍然会报错的,此时,DemoObject 和 TestRecver 类还没有定义,无法将 main.moc 中的源代码插入到 main.cpp 中(会找不到类)。

    1. #include
    2. #include
    3. #include
    4. class DemoObject : public QObject
    5. {
    6. // 这个是宏
    7. Q_OBJECT
    8. ……
    9. };
    10. class TestRecver : public QObject
    11. {
    12. // 记得用这个宏
    13. Q_OBJECT
    14. ……
    15. };
    16. #include "main.moc"
    17. int main(int argc, char **argv)
    18. {
    19. ……
    20. return 0;
    21. }

    要是你觉得这样麻烦,最省事的做法是把类的定义写在头文件中,实现代码写在cpp文件中。MOC 默认会处理头文件,所以不会报错。

    之后再编译运行,就不会报错了。

    如果用的是 Windows 系统,cmd 默认编码是 GBK,不是 UTF-8,VS Code 的代码默认是 UTF8 的,控制台可能会打印出来乱码。这里老周不建议改代码文件的编码,因为说不定你还要把这代码放到 Linux 系统中编译的。在 cmd 中用 CHCP 命令改一下控制台的编码,再运行程序就行了。

    chcp 65001

    其实,信号和槽的函数签名可以不一致。下面我们再来做一例。这个例子咱们用到 QWidget 类的 windowTitleChanged 信号。当窗口标题栏中的文本发生改变时会发出这个信号。它的签名如下:

      void windowTitleChanged(const QString &title);

    这个信号有一个 title 参数,表示修改的窗口标题文本(指新的标题)。而咱们这个例子中用于和它连接的槽函数是无参数的。

    1. private slots: // 这个是槽
    2. void onTitleChanged();

    尽管签名不一致,但可以用。

    在这个例子中,只要鼠标点一下窗口区域,就会修改窗口标题——显示鼠标指针在窗口中的坐标。窗口标题被修改,就会发出 windowTitleChanged 信号,然后,onTitleChanged 也会被调用。

    接下来是实现步骤:

    1、准备 CMakeLists.txt 文件。

    1. cmake_minimum_required(VERSION 3.0.0)
    2. project(demo VERSION 0.1.0)
    3. find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
    4. set(CMAKE_CXX_STANDARD 17)
    5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
    6. set(CMAKE_AUTOMOC ON)
    7. file(GLOB SRC_LIST ./*.h ./*.cpp)
    8. add_executable(demo WIN32 ${SRC_LIST})
    9. target_link_libraries(demo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)

    这里老周就偷懒一下。add_executable(demo ....) 是添加头文件和源码文件的。老周嫌麻烦,加一个文件又要改一次,于是就用 file 命令搜索项目根目录下的所有头文件和 C++ 代码文件。然后把这些搜到的文件添加到变量 SRC_LIST 中。在 add_executable 命令中引用 SRC_LIST 变量,就可以自动添加文件了。

    2、定义一个自定义窗口类,从 QWidget 类派生。

    1. /* 头文件 */
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. class MyWindow : public QWidget
    8. {
    9. Q_OBJECT
    10. public:
    11. MyWindow(QWidget* parent = nullptr);
    12. private slots: // 这个是槽
    13. void onTitleChanged();
    14. protected:
    15. void mousePressEvent(QMouseEvent *event) override;
    16. };
    1. /* 实现代码 */
    2. #include "MyWindow.h"
    3. /****************************************************************/
    4. MyWindow::MyWindow(QWidget *parent)
    5. : QWidget::QWidget(parent)
    6. {
    7. // 窗口大小
    8. resize(300, 275);
    9. connect(this, &MyWindow::windowTitleChanged, this, &MyWindow::onTitleChanged);
    10. }
    11. void MyWindow::onTitleChanged()
    12. {
    13. QMessageBox::information(this, "Test", "看,窗口标题变了。", QMessageBox::Ok);
    14. }
    15. void MyWindow::mousePressEvent(QMouseEvent *event)
    16. {
    17. auto pt = event->pos();
    18. QString s = QString("鼠标指针位置:%1, %2")
    19. .arg(pt.x())
    20. .arg(pt.y());
    21. setWindowTitle(s);
    22. QWidget::mousePressEvent(event);
    23. }
    24. /*****************************************************************/

    重写了 mousePressEvent 方法,当鼠标按钮按下时触发,先通过事件参数的 pos 函数得到鼠标坐标,再用 setWindowTitle 方法修改窗口标题。随即 windowTitleChanged 信号发出,在槽函数 onTitleChanged 中只是用 QMessgeBox 类弹出了一个提示框。运行结果如下图所示。

     

    一个信号可以连接多个槽,一个槽可以与多个信号建立连接。这外交能力是真的强,来者不拒。下面咱们做一个 SaySomething 信号连接三个槽的实验。

    1. #include
    2. class SomeObj : public QObject
    3. {
    4. Q_OBJECT
    5. public:
    6. SomeObj(QObject *parent = nullptr);
    7. void SpeakOut(); // 用这个方法发信号
    8. signals:
    9. void SaySomething();
    10. };
    11. class SlotsObj : public QObject
    12. {
    13. Q_OBJECT
    14. public slots:
    15. // 来几个cao
    16. void slot1();
    17. void slot2();
    18. void slot3();
    19. };

    以上是头文件。SomeObj 类负责发出信号,SlotsObj 类负责接收信号,它有三个 cao:slot1、slot2、slot3。

    下面是 SomObj 类的实现代码。

    1. SomeObj::SomeObj(QObject *parent)
    2. : QObject::QObject(parent)
    3. {
    4. // 无事干
    5. }
    6. void SomeObj::SpeakOut()
    7. {
    8. emit SaySomething();
    9. }

    emit 关键字(Qt 特有)发出 SaySomething 信号。

    下面是 SlotsObj 类的实现代码。

    1. #include "app.h"
    2. #include <iostream>
    3. using namespace std;
    4. void SlotsObj::slot1()
    5. {
    6. cout << "第一个cao触发了" << endl;
    7. }
    8. void SlotsObj::slot2()
    9. {
    10. cout << "第二个cao触发了" << endl;
    11. }
    12. void SlotsObj::slot3()
    13. {
    14. cout << "第三个cao触发了" << endl;
    15. }

    来,咱们试一试,分别实例化 SomeObj 和 SlotsObj 类,然后让 SaySomething 信号依次与 slot1、slot2、slot3 建立连接。这是典型的“一号战三槽”。

    1. int main(int argc, char** argv)
    2. {
    3. // 分别实例化
    4. SomeObj sender;
    5. SlotsObj recver;
    6. // 建立连接
    7. QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot1);
    8. QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot2);
    9. QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot3);
    10. // 发信号
    11. sender.SpeakOut();
    12. return 0;
    13. }

    结果表明:信号一旦发出,三个 slot 都调用了。如下图:

    本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

  • 相关阅读:
    【服务器数据恢复】断电导致存储raid6阵列瘫痪的数据恢复案例
    Android 实现GIF播放的几种方式
    SPDK/NVMe存储技术分析之初识UIO(二)
    算法刷题小结---几数之和
    nodejs+vue杰和牧场管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计
    Windbg线上问题分析:生产环境应用高CPU问题分析
    Python网页应用开发神器fac 0.2.10版本新功能介绍
    DirectX11 With Windows SDK--06 使用ImGui
    《中国工业经济》企业数字化转型与供应链配置—集中化还是多元化
    JavaWeb之前后端分离的三步骤
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/127926067