前面说了Qt最基本的实例创建、控件以及工具集的介绍,相当于对于Qt有了一个初次的认识,这次我们开始认识Qt信号通信的重点之一——信号槽。
信号槽是 Qt 框架引以为豪的机制之一。熟练使用和理解信号槽,能够设计出解耦的非常漂亮的程序,有利于增强我们的技术设计能力。
所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式。)
1 #include "slottest.h" 2 #include <QtWidgets/QApplication> 3 #include <QPushButton> 4 int main(int argc, char *argv[]) 5 { 6 QApplication a(argc, argv); 7 8 QPushButton button("Quit"); 9 QObject::connect(&button,&QPushButton::clicked,&QApplication::quit); 10 button.show(); 11 //slottest w; 12 //w.show(); 13 return a.exec(); 14 }
我们开始一步一步的分析这短短的几行代码。第6行声明一个QApplication类的对象,用于程序的启动和调用;第8行声明了一个按钮控件QPushButton类的对象,用于产生一个按钮,并传递一个字符串“Quit”命名控件显示的名称;第9行用到了QObject中的connect函数,我们先来看这个函数的定义。
1 /*Creates a connection of the given type from the signal in the sender object to the method in the receiver object. Returns a handle to the connection that can be used to disconnect it later. 2 3 You must use the SIGNAL() and SLOT() macros when specifying the signal and the method*/ 4 QMetaObject::Connection connect(const QObject *, const char *, 5 const QObject *, const char *, 6 Qt::ConnectionType); 7 8 QMetaObject::Connection connect(const QObject *, const QMetaMethod &, 9 const QObject *, const QMetaMethod &, 10 Qt::ConnectionType); 11 12 QMetaObject::Connection connect(const QObject *, const char *, 13 const char *, 14 Qt::ConnectionType) const; 15 16 QMetaObject::Connection connect(const QObject *, PointerToMemberFunction, 17 const QObject *, PointerToMemberFunction, 18 Qt::ConnectionType) 19 20 QMetaObject::Connection connect(const QObject *, PointerToMemberFunction, 21 Functor);
从上面可以看到,connect函数一共有五个重载的版本,其中各个参数比较之后sender和receiver没有什么区别,都是QObject指针,主要是signal和slot形式的区别。再回到我们的调用回来,我们采用的是第五个重载,当我们的Button发出了clicked信号时,会调用QApplication的quit函数,从而使程序退出。
对于信号和槽的要求是参数保持一致,这里所说的一致是参数类型的一致。如果不一致,可以是槽函数的参数可以比信号的少,而且参数的顺序必须保持一致。
将上述代码稍加修改,使用lambda表达式后我们再来看一下效果。
1 QObject::connect(&button, &QPushButton::clicked,/*&QApplication::quit*/[](){qDebug()<< "quit"; });
QWidget类是所有用户界面对象的基类。 窗口部件是用户界面的一个基本单元:它从窗口系统接收鼠标、键盘和其它事件,并且在屏幕上绘制自己。每一个窗口部件都是矩形的,并且它们按Z轴顺序排列。一个窗口部件可以被它的父窗口部件或者它前面的窗口部件盖住一部分。
QMainWindow 类提供一个有菜单条、锚接窗口(例如工具条)和一个状态条的主应用程序窗口。主窗口通常用在提供一个大的中央窗口部件(例如文本编辑或者绘制画布)以及周围 菜单、工具条和一个状态条。QMainWindow常常被继承,因为这使得封装中央部件、菜单和工具条以及窗口状态条变得更容易,当用户点击菜单项或者工具条按钮时,槽会被调用。
QDialog类是对话框窗口的基类。对话框窗口是主要用于短期任务以及和用户进行简要通讯的顶级窗口。QDialog可以是模态对话框也可以是非模态对话框。QDialog支持扩展性并且可以提供返回值。它们可以有默认按钮。QDialog也可以有一个QSizeGrip在它的右下角,使用setSizeGripEnabled()。
QDialog 是最普通的顶级窗口。一个不会被嵌入到父窗口部件的窗口部件叫做顶级窗口部件。通常情况下,顶级窗口部件是有框架和标题栏的窗口(尽管使用了一定的窗口部件标记,创建顶级窗口部件时也可能没有这些装饰。)在Qt中,QMainWindow和不同的QDialog的子类是最普通的顶级窗口。
如果是顶级对话框,那就基于QDialog创建,如果是主窗体,那就基于QMainWindow,如果不确定,或者有可能作为顶级窗体,或有可能嵌入到其他窗体中,则基于QWidget创建。
当然了,实际中,你还可以基于任何其他部件类来派生。看实际需求了,比如QFrame、QStackedWidget等等。
通过系统提供的connect函数可以让我们连接系统信号和槽,但是你应该会想到我们桐乡可以自己定制自己的信号槽,这也是Qt架构的设计思路,用于我们设计解耦的程序。信号槽不是GUI模块提供的,而是Qt核心特性之一,所以我们同样可以在普通的控制台程序中使用信号槽。
注意:我之所以将头文件和源文件分开来写,是因为moc在我的编译器中必须要将其分开,不然就会报错。
1 #include<QObject> 2 3 /////////////////newspaper.h 4 class Newsspaper :public QObject{ 5 6 Q_OBJECT 7 public: 8 Newsspaper(const QString& name) :m_name(name) 9 { 10 11 } 12 void send(); 13 signals: 14 void newPaper(const QString& name); 15 private: 16 QString m_name; 17 };
1 #include "newspaper.h" 2 3 void Newsspaper::send() 4 { 5 emit newPaper(m_name); 6 }
1 #include<QObject> 2 #include<QDebug> 3 4 class Reader :public QObject 5 { 6 Q_OBJECT 7 public: 8 Reader(); 9 void receiveNewspaper(const QString& name); 10 };
1 #include "reader.h" 2 3 Reader::Reader(){} 4 5 void Reader::receiveNewspaper(const QString& name) 6 { 7 qDebug() << "Receive Newspaper:" << name; 8 }
1 #include "slottest.h" 2 #include <QtWidgets/QApplication> 3 #include <QPushButton> 4 #include <QObject> 5 #include <QCoreapplication> 6 #include "newspaper.h" 7 #include "reader.h" 8 9 int main(int argc, char *argv[]) 10 { 11 QApplication a(argc, argv); 12 Newsspaper newspaper("NewsPaper A"); 13 Reader reader; 14 QObject::connect(&newspaper,&Newsspaper::newPaper,&reader,&Reader::receiveNewspaper); 15 newspaper.send(); 16 17 return a.exec(); 18 19 }
运行结果:
Receive Newspaper: "NewsPaper A"
根据代码我们对几个重点进行解释:1、Q_OBJECT是Qt的一个宏,能够使程序具有信号槽机制,由moc进行处理,会生成以moc为开头的文件,moc是一种预处理器;2、emit是Qt的另一个宏,用于表示发射信号,使槽函数能够接受信号;3、使用connect函数进行信号槽的连接,槽函数在获取到消息体和信号参数后做出相应,这里就是响应打印消息。4、信号槽的机制类似于广播,可以参照广播模式来理解代码。5、使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;6、槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响。