1.8元对象系统
元对象系统最主要的一个功能就是实现信号和槽,窗体和控件对象之间的沟通一般都使用信号和槽。Qt 元对象系统实现了对象之间通信机制信号和槽,并提供了运行时类型信息和动态属性系统。元对象系统是 Qt 类库独有的功能,是 Qt 对标准 C++ 的扩展。
Qt中的元对象系统和标准C++系统的区别:
1.支持对象间使用信号和槽的机制进行通信
2.动态对象转化
3.可查询可设计的对象属性
4.层次结构可查询的对象树
5.安全的指针管理
6.支持国际化的文本转化
7.支持多任务定时器
8.事件和事件过滤器
1.8.1 使用元对象系统
1.Qt的元对象系统基于如下三件事情:
(1) QObject宏为所有需要利用元对象系统的对象提供了一个基类。Qt 的窗体和控件最顶层的基类都是 QObject。
(2) Q_OBJECT,通常可以声明在类的私有段中,让该类可以使用元对象的特性,比如动态属性,信号和槽。
所有QObject的派生类在官方文档中都推荐在头文件中放置宏Q_OBJECT,建立工程的时候自动生成的类声明里已经自动加入了Q_OBJECT的声明。
(3) 元对象编译器(moc)为每个QObject子对象自动生成必要的代码来实现元对象特性。
moc工具会读入C++的源文件,如果它发现了一个或者多个声明了Q_OBJECT宏的类,它就创建另一个C++源文件,为每个类生成包含元对象实现的代码。这些编译生成的源文件通常都已经被包含到类的源文件中或者和类的实现同时被编译和链接。
Mainwindow.h中的Q_OBJECT声明:
图1-8-1Q_OBJECT的声明
2.元对象系统除了提供信号和槽机制用于对象之间的通信,还提供了一些其他的特性
(1)返回当前类对象关联的元对象(meta-object)。
QObject::metaObject()
(2)返回当前对象的类名称字符串,而不需要 C++ 编译器原生的运行时类型信息(run-time type information,RTTI)支持。
QMetaObject::className()
(3)函数判断当前对象是否从某个基类派生,判断某个基类是否位于从 QObject 到对象当前类的继承树上。
QObject::inherits()
(4)函数负责翻译国际化字符串,因为 Qt5 规定源文件字符编码是 UTF-8,所以这两个函数现在功能是一样的。
QObject::tr()
QObject::trUtf8()
(5)动态设置和获取属性,都是通过属性名称字符串来操作。
QObject::setProperty() 和 QObject::property()
(6)构建一个当前类的新实例对象。
QMetaObject::newInstance()
1.8.2 信号与槽机制
信号槽机制与Windows下消息机制类似,消息机制是基于回调函数,Qt中用信号与槽来代替函数指针,使程序更安全简洁。
信号和槽用于对象间的通讯,信号/槽机制是Qt的一个核心特征。在图形用户界面编程中,我们经常通过信号槽将一个窗口部件的一个变化通知给另一个窗口部件。
1.信号
当对象改变其状态时,信号就由该对象发射 (emit) 出去,而且对象只负责发送信号,它不知道另一端是谁在接收这个信号。这样就做到了真正的信息封装,能确保对象被当作一个真正的软件组件来使用。
2.槽
用于接收信号,而且槽只是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且对象并不了解具体的通信机制。
3.信号与槽的连接
所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。
示例:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
其中 sender 与 receiver 是指向对象的指针,SIGNAL() 与 SLOT() 是转换信号与槽的宏。
4.特点
一个信号可以连接多个槽,当信号发射时,会以不确定的顺序一个接一个的调用各个槽。多个信号可以连接同一个槽,即无论是哪一个信号被发射,都会调用这个槽。信号直接可以相互连接,发射第一个信号时,也可以发射第二个信号。
5.注意的问题
信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。信号和槽的参数个数与类型必须一致,并且信号与槽函数都没有返回值。
1.8.3 信号与槽手动关联
新建一个工程,在UI界面拖一个按钮控件;实现点击按钮发送一个信号,按钮的槽函数中弹出一个对话框。
先打开 QtCreator,新建一个 Qt Widgets Application 项目,基类选择 MainWindow,其他默认即可。(对应的代码编号CH1-2)
1.项目创建成功后双击mainwindow.ui文件打开UI设计界面,向编辑窗口中拖入一个pushButton按钮。
图1-8-2 设计UI界面
2.打开mainwindow.h文件,在类里加上槽函数的声明代码。
public slots: //槽函数声明标志
void pushButton_clicked(); //声明槽函数
代码截图如下:
图1-8-3 槽函数的声明
3.打开mainwindow.cpp文件,在文件最下面编写槽函数代码。在槽函数里用到了弹出消息框的代码,还需要添加一个#include <QMessageBox>头文件。
代码示例:
4.添加槽函数与信号关联代码。
//添加信号槽关联代码,必须放在 setupUi 函数之后。
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(pushButton_clicked()));
参数解析:
(1)ui->pushButton:发送信号的对象指针名称(就是上一步在UI设计界面添加的按钮控件的对象名称,鼠标选中控件右键可以修改按钮的对象名称)。
(2)SIGNAL(clicked()) :clicked()函数是按钮支持的点击信号。SIGNAL()是转换信号的宏。
(3)this :表示接收信号的对象指针。
(4)SLOT(pushButton_clicked()):pushButton_clicked()函数是槽函数的名称。SLOT()是转换槽函数的宏。
代码截图:
图1-8-4 添加关联代码
5.点击QT Creator左下角的三角形图标或者按下Ctrl+R 运行程序,点击按钮测试效果。
图1-8-5 运行效果
1.8.4 信号与槽函数自动关联
这里的自动关联是指不需要手动编写 connect 函数,通过自动命名槽函数的方式来编写代码。自动关联的要求是槽函数根据源头的对象名(指针)和其信号名称来命名,元对象系统可以实现自动 connect 功能。这对窗体设计非常方便,如果我们窗体里拖了 10以上的按钮,手动编写 connect 函数的话,比较麻烦。通过自动关联方式,这些 connect 函数代码全可以省了。我们只需要关注如何实现槽函数的功能即可。
打开 QtCreator,新建一个 Qt Widgets Application 项目,基类选择 MainWindow,其他的选项默认。(对应的代码编号CH1-3)
1.项目创建成功后双击mainwindow.ui文件打开UI设计界面,向编辑窗口中拖入一个pushButton按钮控件。
图1-8-6 UI设计界面
2.自动生成信号和槽函数代码。
鼠标选中控件点击右键,选择“转到槽”,然后选择需要关联的信号,再点击确定,即可自动生成代码。
图1-8-7 选择需要关联的信号
3.选择关联的信号后,QT Creator会自动生成槽函数的声明和槽函数代码,并且自动关联信号与槽函数。
mainwindow.cpp文件自动生成是槽函数代码(红色线标记的部分表示新添加的代码,处于还未保存状态): 图1-8-8 槽函数代码
mainwindow.h文件代码中自动生成函数声明:
图1-8-9 槽函数的声明
4.在按钮槽函数里添加弹出对话框代码。需要添加一个#include <QMessageBox>头文件。
void MainWindow::on_pushButton_clicked()
{
QMessageBox::information(NULL,"信号与槽函数学习","信号接收成功!!");
}
5.点击QT Creator左下角的三角形图标或者按下Ctrl+R 运行程序,点击按钮测试效果,与手动关联的效果一样。
图1-8-10 运行效果
1.8.5 信号与槽自动关联的过程
没有手调用connect 函数,信号和槽是怎么关联起来的?
关键地方主要有两点:
1.槽函数命名非常严格,必须按照以下规则命名:
void on_<object name>_<signal name>(<signal parameters>);
函数必须以 on_ 开头,接下来是对象名,再接一个下划线,最后是信号名和信号可能的参数。
比如我们的按钮对象名为:pushButton,信号为:clicked()。槽函数自动命名就是:on_pushButton_clicked();
2.由 uic 和 moc 等工具自动生成代码。
以名称为send_Signal项目为例,项目存放在D:\QtExample路径下,编译的模式为Debug模式。那么在
D:\QtExample\send_Signal\build-SIGNAL-Desktop_Qt_5_7_0_MinGW_32bit-Debug路径下可以看到ui_mainwindow.h文件。
ui_mainwindow.h文件里有一个void setupUi(QMainWindow *MainWindow) 函数。在setupUi 函数末尾有一个QMetaObject::connectSlotsByName(MainWindow)函数,该函数就是完成最后的自动关联,这是元对象系统包含的功能,根据对象名、信号名与 on_<object name>_<signal name>(<signal parameters>) 格式的槽函数进行自动匹配关联。
图1-8-11 connectSlotsByName函数
1.8.6 connect语法格式
我们在1.8.5节使用了Qt Creator软件自动关联信号和槽方法,使用起来非常方便。但自动关联不是万能的,尤其是涉及到多个窗体的时候,比如 A 窗体私有按钮控件与 B 窗体私有消息框函数互相关联信号和槽函数,这个因为权限限制,不能自动关联。自动关联一般用于一个窗体之内的控件关联,其他很多情况都是需要手动编写 connect 函数的,所以还需要深入学习connect 函数的语法,了解怎么传递参数。
在QT5版本中,connect 函数有两种写法,一种是与QT4兼容的写法,一种是QT5版本新出现的写法,下面将详细介绍这两种写法使用方法。
1.与QT4兼容的写法格式。
QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
参数:
(1)const QObject *sender :信号源的对象指针(必须是实际存在的对象)。
(2)const char *signal :发送的信号。需要使用SIGNAL宏进行包装。
(3)const QObject *receiver :接收方的对象指针(必须是实际存在的对象)。
(4)const char *method :接收方的槽函数。需要使用SLOT宏进行包装。
(5)Qt::ConnectionType type :该参数在多线程编程的时候才会有区别。
对于单线程,关联一般用直连类型(Qt::DirectConnection),信号一触发,对应槽函数立即就被调用执行;对于多线程程序,跨线程的关联一般用入队关联(Qt::QueuedConnection),信号触发后,跨线程的槽函数被加入事件 处理队列里面执行,避免干扰接收线程里的执行流程。Qt::AutoConnection 会自动根据源头对象和接收对象所属的线程来处理,默认都用这种类型的关联,对于多线程程序这种关联也是安全的。
这种句式可读性很好,信号和槽的标识也很清晰。但是connect 函数参数里的 signal 和 method(槽函数)都是 char * 字符串类型,这种语法格式的connect 函数是根据信号和槽函数的字符串名称来关联的,不具备编译时类型检查,信号与槽函数都是字符串,参数类型在编译时都不知道。关联出错只有在运行时才会体现出来。
关联用法示例:
QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
QObject::connect(scrollBar, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));
2.QT5新出现的格式写法
QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
新写法用的是 PointerToMemberFunction ,这个类型名称是不存在的,只是在文档里面显示,实际使用的是模板函数。需要注意的是信号与槽的两个形参类型必须一致!
关联用法示例:
QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged,label, &QLabel::setText);
1.8.7 解除信号槽关联
如果在某些时候不需要用到信号与槽函数的关联关系,可以使用 disconnect 函数解除之前的关联关系。disconnect 函数与 connect 函数功能相反,两个函数的参数差不多。
1.兼容QT4的语法格式
bool QObject::disconnect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)
2.QT5新语法格式
bool QObject::disconnect(const QObject * sender, PointerToMemberFunction signal, const QObject * receiver, PointerToMemberFunction method)
通过disconnect 函数的返回值可以判断解除关联是否执行成功。成功返回true,失败返回false。
3.两种解除关联方式示例
QObject::disconnect(ui->lineEdit, SIGNAL(textEdited(QString)), ui->label, SLOT(setText(QString)));
QObject::disconnect(lineEdit, &QLineEdit::textChanged,label, &QLabel::setText);
1.8.8 自定义信号与槽
1.使用自定义的信号和槽,需要注意以下几点:
(1)类的声明中必须包含Q_OBJECT宏。
(2)信号只需要声明不需要实现函数实体。
(3)发射信号使用emit关键字。
(4)自定义槽函数的实现与普通成员函数的实现方式一样。
2.槽函数声明方式示例
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void pushButton_clicked(); //槽函数声明
signals:
void valuestring(); //信号声明
void valueChanged(int newValue); //信号声明
}
3.发送信号示例
emit valueChanged; //不带参数
emit valuestring("12345"); //带参数