1. 元对象系统
元对象系统是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统。
什么是元对象
在计算机科学中,元对象是这样一个东西:它可以操纵、创建、描述、或执行其他对象。元对象描述的对象称为基对象。元对象可能存在这样的信息:基础对象的类型、接口、类、方法、属性、变量、控制结构等。
元对象系统组成
QObject
QObject是 QT 对象模型的核心,绝大部分的 Qt类都是从这个类继承而来。
Q_OBJECT
Q_OBJECT宏必须出现在类定义的私有部分,用来开启信号和槽、动态属性系统,或Qt元对象系统提供的其他服务。
#define Q_OBJECT \
public: \
QT_WARNING_PUSH \
Q_OBJECT_NO_OVERRIDE_WARNING \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_TR_FUNCTIONS \
private: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
struct QPrivateSignal {}; \
QT_ANNOTATE_CLASS(qt_qobject, "")
MOC
Qt 的信号和槽机制是采用标准 C++ 来实现的。该实现使用 C++ 预处理器和 Qt 所包括 的 moc(元对象编译器)。元对象编译器读取应用程序的头文件,并生成必要的代码,以支 持信号和槽机制。
元对象编译器 moc(meta object compiler)对 C++文件中的类声明进行分析并产生用 于初始化元对象的 C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针。
moc 读 C++源文件,如果发现有 Q_OBJECT 宏声明的类,它就会生成另外一个 C++源文件,这个新生成的文件中包含有该类的元对象代码。例如,假设我们有一个头文件Widget.h,在这个文件中包含有信号或槽的声明,然后在同名源文件 Widget.cpp中包含这个头文件,并实现类,那么在编译之前 moc 工具就会根据该文件自动生成一个名为moc_Widget.cpp 的 C++源文件并将其提交给编译器。
2. 信号与槽机制
信号与槽是 Qt 框架引以为傲的机制之一。所谓信号与槽,实际就是观察者模式(发布-订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系。
信号与槽的本质
信号是由对象发送出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由Qt的
Qt Meta Object System
实现的。槽实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以作为槽函数。
当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数。
信号与槽的绑定
在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起,好比牛郎和织女想要相会必须要有喜鹊为他们搭桥一样。
信号与槽绑定使用
QObject::connent()
函数实现,其基本格式如下:
[static] QMetaObject::Connection connect(
const QObject *sender,
const QMetaMethod &signal,
const QObject *receiver,
const QMetaMethod &method,
, Qt::ConnectionType type = Qt::AutoConnection)
[static] QMetaObject::Connection connect(
const QObject *sender,
PointerToMemberFunction signal,
Functor functor)
参数解析:
-
sender:信号发送者,需要传递一个QObject族的对象指针
-
signal:发出的具体信号,需要传递一个函数指针
-
receiver:信号接收者,需要传递一个QObject族的对象指针
-
method:接收到信号之后,信号接收者处理动作,需要传递一个函数指针(槽函数)
-
type:第一个connect函数独有参数,表示信号和槽的连接类型;有默认值,一般不需修改。
信号与槽的断开
信号与槽连接之后,还可以断开连接,断开之后,信号出发之后,槽函数就不会调用了。注意,信号与槽的断开时必须与连接时的参数一致。当然也可以直接使用connect函数的返回值断开连接。
bool disconnect(const QObject *sender,
const QMetaMethod &signal,
const QObject *receiver,
const QMetaMethod &method)
bool disconnect(const QMetaObject::Connection &connection)
3. 标准信号与槽的使用
使用信号与槽机制必须在该类的定义私有部分包含Q_OBJECT宏。
在Qt提供的很多类中都可以对用户触发的某些特定事件进行检测, 当事件被触发后就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号。
同样的,在Qt的很多类内部为我了提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数。
#include <QApplication>
#include <QWidget>
//去掉控制台
#pragma comment(linker,"/subsystem:windows /entry:mainCRTStartup")
class Widget : public QWidget
{
Q_OBJECT // 使用信号与槽机制必须包含此宏
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowTitle("窗口标题"); // 设置窗口标题
resize(640, 480); // 设置窗口大小
}
~Widget()
{
}
private:
};
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
如上代码将会得到一个窗口:
当在Widget构造函数中添加以下代码会出现:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowTitle("窗口标题"); // 设置窗口标题
resize(640, 480); // 设置窗口大小
//按钮的使用
QPushButton* btn = new QPushButton;
btn->setText("Button");
btn->show();
}
按钮为什么没有显示在窗口上面而是独立显示,那是因为没有给按钮设置父对象,给按钮设置父对象之后就不需要调用show()了,按钮会跟窗口一起显示。
//按钮的使用
QPushButton* btn = new QPushButton;
btn->setText("Button");
btn->setParent(this);
然后尝试使用信号和槽实现点击按钮关闭窗口的效果:
class Widget : public QWidget
{
Q_OBJECT // 使用信号与槽机制必须包含此宏
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowTitle("窗口标题"); // 设置窗口标题
resize(640, 480); // 设置窗口大小
m_btn = new QPushButton("Button", this);
//关联信号与槽
auto con = connect(m_btn, &QPushButton::clicked, this, &QWidget::close);
}
~Widget()
{
}
private:
QPushButton* m_btn = nullptr;
};
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
使用disconnect函数解除信号与槽的绑定
//关联信号与槽
auto con = connect(m_btn, &QPushButton::clicked, this, &QWidget::close);
//解绑信号与槽
disconnect(m_btn, &QPushButton::clicked, this, &QWidget::close);
//disconnect(con); // 与上一行代码一样的效果
使用帮助文档查看标准的信号与槽
直接使用帮助文档查看控件,如果该控件没有信号或者槽就继续查找它的父类是否有。