用 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_OBJECT private:
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 文件:
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 中只要引入这个就行。
当你兴奋异常地编译和运行本程序时,会发生错误:
这个错误是因为 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;
}
要是你觉得这样麻烦,最省事的做法是把类的定义写在头文件中,实现代码写在cpp文件中。MOC 默认会处理头文件,所以不会报错。
之后再编译运行,就不会报错了。
如果用的是 Windows 系统,cmd 默认编码是 GBK,不是 UTF-8,VS Code 的代码默认是 UTF8 的,控制台可能会打印出来乱码。这里老周不建议改代码文件的编码,因为说不定你还要把这代码放到 Linux 系统中编译的。在 cmd 中用 CHCP 命令改一下控制台的编码,再运行程序就行了。
chcp 65001
其实,信号和槽的函数签名可以不一致。下面我们再来做一例。这个例子咱们用到 QWidget 类的 windowTitleChanged 信号。当窗口标题栏中的文本发生改变时会发出这个信号。它的签名如下:
void windowTitleChanged(const QString &title);
这个信号有一个 title 参数,表示修改的窗口标题文本(指新的标题)。而咱们这个例子中用于和它连接的槽函数是无参数的。
private slots: // 这个是槽
void onTitleChanged();
尽管签名不一致,但可以用。
在这个例子中,只要鼠标点一下窗口区域,就会修改窗口标题——显示鼠标指针在窗口中的坐标。窗口标题被修改,就会发出 windowTitleChanged 信号,然后,onTitleChanged 也会被调用。
接下来是实现步骤:
1、准备 CMakeLists.txt 文件。
cmake_minimum_required(VERSION 3.0.0)
project(demo VERSION 0.1.0)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON) file(GLOB SRC_LIST ./*.h ./*.cpp)
add_executable(demo WIN32 ${SRC_LIST})
target_link_libraries(demo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
这里老周就偷懒一下。add_executable(demo ....) 是添加头文件和源码文件的。老周嫌麻烦,加一个文件又要改一次,于是就用 file 命令搜索项目根目录下的所有头文件和 C++ 代码文件。然后把这些搜到的文件添加到变量 SRC_LIST 中。在 add_executable 命令中引用 SRC_LIST 变量,就可以自动添加文件了。
2、定义一个自定义窗口类,从 QWidget 类派生。
/* 头文件 */
#include <QWidget>
#include <QMessageBox>
#include <QMouseEvent>
#include <QString>
#include <QApplication> class MyWindow : public QWidget
{
Q_OBJECT public:
MyWindow(QWidget* parent = nullptr); private slots: // 这个是槽
void onTitleChanged(); protected:
void mousePressEvent(QMouseEvent *event) override;
};
/* 实现代码 */
#include "MyWindow.h" /****************************************************************/
MyWindow::MyWindow(QWidget *parent)
: QWidget::QWidget(parent)
{
// 窗口大小
resize(300, 275);
connect(this, &MyWindow::windowTitleChanged, this, &MyWindow::onTitleChanged);
} void MyWindow::onTitleChanged()
{
QMessageBox::information(this, "Test", "看,窗口标题变了。", QMessageBox::Ok);
} void MyWindow::mousePressEvent(QMouseEvent *event)
{
auto pt = event->pos();
QString s = QString("鼠标指针位置:%1, %2")
.arg(pt.x())
.arg(pt.y());
setWindowTitle(s);
QWidget::mousePressEvent(event);
}
/*****************************************************************/
重写了 mousePressEvent 方法,当鼠标按钮按下时触发,先通过事件参数的 pos 函数得到鼠标坐标,再用 setWindowTitle 方法修改窗口标题。随即 windowTitleChanged 信号发出,在槽函数 onTitleChanged 中只是用 QMessgeBox 类弹出了一个提示框。运行结果如下图所示。
一个信号可以连接多个槽,一个槽可以与多个信号建立连接。这外交能力是真的强,来者不拒。下面咱们做一个 SaySomething 信号连接三个槽的实验。
#include <QObject> class SomeObj : public QObject
{
Q_OBJECT public:
SomeObj(QObject *parent = nullptr);
void SpeakOut(); // 用这个方法发信号 signals:
void SaySomething();
}; class SlotsObj : public QObject
{
Q_OBJECT public slots:
// 来几个cao
void slot1();
void slot2();
void slot3();
};
以上是头文件。SomeObj 类负责发出信号,SlotsObj 类负责接收信号,它有三个 cao:slot1、slot2、slot3。
下面是 SomObj 类的实现代码。
SomeObj::SomeObj(QObject *parent)
: QObject::QObject(parent)
{
// 无事干
} void SomeObj::SpeakOut()
{
emit SaySomething();
}
emit 关键字(Qt 特有)发出 SaySomething 信号。
下面是 SlotsObj 类的实现代码。
#include "app.h"
#include <iostream>
using namespace std; void SlotsObj::slot1()
{
cout << "第一个cao触发了" << endl;
}
void SlotsObj::slot2()
{
cout << "第二个cao触发了" << endl;
}
void SlotsObj::slot3()
{
cout << "第三个cao触发了" << endl;
}
来,咱们试一试,分别实例化 SomeObj 和 SlotsObj 类,然后让 SaySomething 信号依次与 slot1、slot2、slot3 建立连接。这是典型的“一号战三槽”。
int main(int argc, char** argv)
{
// 分别实例化
SomeObj sender;
SlotsObj recver;
// 建立连接
QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot1);
QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot2);
QObject::connect(&sender, &SomeObj::SaySomething, &recver, &SlotsObj::slot3); // 发信号
sender.SpeakOut();
return 0;
}
结果表明:信号一旦发出,三个 slot 都调用了。如下图:
好了,今天的故事就讲到这儿了,欲知后事如何,且待下回分解。
用 VS Code 搞 Qt6:信号、槽,以及QObject的更多相关文章
- 用 VS Code 搞 Qt6:让信号和槽自动建立连接
Qt 具备让某个对象的信号与符合要求的槽函数自动建立连接.弄起来也很简单,只要调用这个静态方法即可: QMetaObject::connectSlotsByName(...); connectSlot ...
- 用VS Code搞Qt6:编译源代码与基本配置
先说明一下,本水文老周仅讨论新版的 Qt 6,旧版的 Qt 不讨论. 尽管 Qt 有自己的开发环境,但老周必须说句不装逼的话:真的不好用.说起写代码,当然了,用记事本也能写.但是,有个高逼格的工具,写 ...
- 用VS Code搞Qt6:至简窗口部件——QWidget
在正题开始之前,老周照例扯点别的.嗯,咱们扯一下在 VS 2022 下结合 CMake 开发 Qt6 时的环境变量设置问题.在VS Code 中,通够通过 CMake Tools 扩展的配置来设置环境 ...
- 用 VS Code 搞Qt6:使用 PySide 6
一般来说,用C++写 Qt 应用才是正宗的,不过,为了让小学生也能体验 Qt 的开发过程,或者官方为了增加开发者人数,推出了可用 Python 来编程的 Qt 版本.此版本命名比较奇葩,叫 PySid ...
- 用VS Code搞Qt6:编译附加模块
上一次水文中,老周所介绍的是编译 Qt 的基础模块-- qtbase.一次性编译所有代码可以一劳永逸,但体积相当大,编译时间较长,CPU负载大发热大,风扇转得猛,电费交得多.因此老周更喜欢分开来编译. ...
- QT信号槽详解
1 QT信号槽详解 1.1 信号和槽的定义 信号是触发信号,例如按钮的点击触发一个clicked信号,槽是用来接收信号,并处理信号,相当于信号响应函数.一个信号可以关联多个槽函数,信 ...
- Boost信号/槽signals2
信号槽是Qt框架中一个重要的部分,主要用来解耦一组互相协作的类,使用起来非常方便.项目中有同事引入了第三方的信号槽机制,其实Boost本身就有信号/槽,而且Boost的模块相对来说更稳定. signa ...
- 用ISO C++实现自己的信号槽(Qt另类学习)
qtc++objectsignalclassstring 目录(?)[-] Qt信号与槽 引入元对象系统 建立信号槽链接 信号的激活 槽的调用 全家福 零零散散写在后面 Q_OBJECT Conn ...
- Qt 学习之路:深入 Qt5 信号槽新语法
在前面的章节(信号槽和自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号槽新语法.由于这次改动很大,许多以前看起来不是问题的问题接踵而来,因此,我们用单独的一章重新介绍一些 Qt 5 的信号槽新语 ...
随机推荐
- 100行代码实现一个RISC-V架构下的多线程管理框架
1. 摘要 本文将基于RISC-V架构和qemu仿真器实现一个简单的多线程调度和管理框架, 旨在通过简单的代码阐明如何实现线程的上下文保存和切换, 线程的调度并非本文的重点, 故线程调度模块只是简单地 ...
- 来开源吧!发布开源组件到 MavenCentral 仓库超详细攻略
请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...
- [BZOJ3625][CF438E]小朋友和二叉树 (多项式开根,求逆)
题面 题解 设多项式的第a项为权值和为a的二叉树个数,多项式的第a项表示是否为真,即 则,所以F是三个多项式的卷积,其中包括自己: ,1是F的常数项,即. 我们发现这是一个一元二次方程,可以求出,因为 ...
- windows系统-不能打印问题:PDF打印软件正常打开PDF文件,点击打印后软件卡死并提示未响应(No response)
电脑突然出现PDF软件卡死问题,导致无法打印:初步思路记录: 导致问题出现的原因可能为文件问题(文件过大,打印机容量小).打印机问题(打印机未连接.故障等).电脑驱动问题(打印机驱动损坏).电脑补丁问 ...
- 第十三章 k8s的GUI资源管理插件--dashboard
1.部署Kubernetes-dashboard 1.1 准备dashboard镜像 在10.4.7.200上操作 [root@hdss7-200 ~]# docker pull k8scn/kube ...
- python自动化测试系列教程
随着互联网产品更新迭代加快,Web 开发和测试的需求也越来越大.很难想象,如果阿里的双 11.京东的 618,这些庞大繁杂的系统,由工程师们一个个手动测试,将会是一个怎样费时费力.成本巨大的工程. 也 ...
- 从代码到发包,一个程序全搞定!Gitea 推出软件包自托管功能 Package Registry
2022 年 7 月的最后一天,随着 Gitea 1.17.0 版本的正式发布,Gitea 开源社区推出了一项名为 Package Registry 的包管理功能,与 Gitea 代码仓库无缝集成,类 ...
- 跟羽夏学 Ghidra ——初识
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- ProxySQL监控后端节点
ProxySQL通过Monitor模块监控后端MySQL Server的read_only值来自动调整节点所属的组.所以,在配置读.写组之前,必须先配置好监控. 首先看下Monitor库中的表: ad ...
- Ingress-nginx灰度发布功能详解