QT入门学习(四):QT信号槽

前言

凡是桌面应用程序,都会有消息处理,QT也不例外。

QT的信号是广播传递的,当一个按钮检测到被点击时,就发送一个信号(signal)。如果有对象对这个信号感兴趣,那么它必须使用连接(connect)函数,来把这个信号与自己的函数绑定在一起。当信号发生时,绑定的函数会自动被调用。为了方便,我们把绑定的函数称为为槽函数。

moc (Meta Object Compiler)

为了更好的理解QT中的信号和槽,我们先看下QT的 moc (Meta Object Compiler)

moc构成了QT强大的元对象系统

  1. 元对象系统依赖于QObject,QObject是QT中所有类的基类
  2. Q_OBJECT 宏 可以启用元对象的特性,比如:动态属性、信号、槽
  3. 元对象编译器(moc)会在QObject子类中添加必要的代码来实现元对象特性

moc工具读取C++源文件,如果它找到一个或多个包含Q_OBJECT宏的类声明,那么它会生成另一个C++源文件。moc就相当于预处理一样,java 的 jsp 也是相同的原理。

元对象系统主要是为了提供对象间通信的信号槽机制,但是还提供了以下机制:

  1. QObject::metaObject() 返回类的关联元对象
  2. QMetaObject::className() 在运行时以字符串形式返回类名,不需要通过C++编译器支持RTTI
  3. QObject::inherits() 返回对象是否为继承QObject继承树中指定类的示例
  4. QObject::tr() 和QObject::trUtf8() 为国际化翻译字符串
  5. QObject::setProperty() 和 QObject::property() 按名称动态设置和获取属性
  6. QMetaObject::newInstance()构造类的新实例

QT提供了一个方法用于动态转换:qobject_cast(obj)

这里官方建议继承于QObject都要使用Q_OBJECT宏

QMetaObject 相当于java 中的Class 类

我们写一个小例子进一步说明 ( 控制台程序 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef A_H
#define A_H

#include <QObject>

class A:public QObject
{
Q_OBJECT
public:
A();
void toString();
};

#endif // A_H
1
2
3
4
5
6
7
8
9
10
#include "a.h"
#include <QDebug>
A::A()
{

}
void A::toString()
{
qDebug() << "A class";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <QCoreApplication>
#include "a.h"
#include <QString>
#include <QDebug>
#include <qobject.h>

int main(int argc, char *argv[])
{
// 获取元对象,通过元对象获取对象的类名
A a;
const QMetaObject* aMeta = a.metaObject();
QString AclassName = aMeta->className();


qDebug()<< AclassName ;

// 根据元对象创建类的实例
QObject* aNew = aMeta->newInstance();
A* aA = qobject_cast<A*>(aNew);
aA->toString();
delete aA;

//属性系统
//需要使用Q_PROPERTY() 宏
//Q_PROPERYU宏具体定义
/*
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL]
[REQUIRED]
)
READ 用来读取属性
WRITE 用来写属性
MEMBER 使给定的成员变量可读可写
RESET 设置属性的默认值
NOTIFY 在属性更改时会发出该信号
REVISION 定义了API的版本号,相当于一个注释(默认0)
DESIGNABLE 是否在GUI设计工具的属性编辑器可见,可以设置布尔成员函数(默认true)
SCRIPTABLE 是否可有脚本引擎访问(默认true)
STORED 指示该属性是否单独存在(默认为true)
USER 指示属性是否为类的面向用户或用户可编辑的属性(默认为false)
CONSTANT 属性值是恒定的
FINAL 属性不会被派生类覆盖
REQUIRED 表明该属性应该由类的用户设置,主要用于QML类
*/
//setProperty可以动态给对象添加属性,即使类没有声明Q_PROPERTY
//但是返回值会返回false
a.setProperty("name","张三");
//用property查询属性值
QVariant name = a.property("name");
qDebug()<< name;
//根据官方的文档来看,一般用法,Q_PROPERTY声不声明都没有关系
return 0;
}

元对象系统比较复杂,先简单介绍(主要我没学到)

信号

信号本身就一个函数,返回类型为void的函数

我们可以在C++头文件写上一个函数的声明,moc 会自动帮我们处理,把这个函数当做信号。

信号可以连接任意数量的槽,也可以连接到信号

槽是一个普通的C++成员函数,也可以是虚函数。

信号与槽的关联

信号与槽关联是用QObject::connect函数实现的,其基本格式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/***
@param sender 信号发送者
@param signal 信号
@param receiver 信号接收者
@param method 槽函数
@param type 连接类型
Qt::AutoConnection:如果信号接收者与信号发送者处于同一个线程,那么会使用Qt::DirectConnection
否则使用Qt::QueuedConnection
Qt::DirectConnection:发出信号之后,立刻调用槽函数,该信号只在信号发送者线程调用槽函数
Qt::QueuedConnection:发出信号之后,信号会排队执行(异步),该信号只在信号接收者线程调用槽函数
Qt::BlockingQueuedConnection:和Qt::QueuedConnection一样,不过等待槽函数返回
Qt::UniqueConnection:与Qt::AutoConnection,如果相同的信号已经连接到同一对对象的相同插槽,
则不会建立连接并且 connect() 返回false
**/
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

调用示例:

1
2
3
myButton = new QPushButton(this);
connect(myButton, SIGNAL(clicked()),
this, SIGNAL(buttonClicked()));

在指定信号方法时必须使用SIGNAL()SLOT()

1
2
3
4
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)

Q_CORE_EXPORT const char *qFlagLocation(const char *method);

可以看出这两个宏都是根据字符串来找到对应的方法。

QT 5.2 新增了一个静态方法:

1
QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

我们可以这样调用:

1
connect(this,&Widget::mySignal,this,&Widget::mySlot);

连接如果不用的话,记得断开连接。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT
public slots:
//槽函数
void setValue(int value);
signals:
//信号
void valueChanged(int newValue);
public:
//发送信号的函数
void sendSignal(int value);
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(this,&MainWindow::valueChanged,this,&MainWindow::setValue);
}

MainWindow::~MainWindow()
{
delete ui;
disconnect(this,&MainWindow::valueChanged,this,&MainWindow::setValue);
}
void MainWindow::setValue(int value)
{
emit valueChanged(value);
}
void MainWindow::sendSignal(int value)
{
qDebug()<< value;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.sendSignal(10);
w.sendSignal(20);
return a.exec();
}

评论