L2.3 Qt的开发步骤及实例
L1 信号和槽机制(Signal&Slot)
Qt提供了信号和槽机制来完成界面操作的响应,是完成任意两个Qt对象之间的通信机制。其中,信号会在某个特定情况或动作下被触发,槽是等同于接收并处理信号的函数。比如,要将一个窗口部件的变化情况通知给另一个窗口部件,则一个窗口部件发送信号,另一个窗口部件的槽接收此信号并进行相应的操作,即可实现两个窗口部件之间的通信。每个Qt对象都包含若干个预定义的信号和若干个预定义的槽,当某一个特定事件发生时,一个信号被发射,与信号相关联的槽则会响应信号完成相应的处理。当一个类被继承时,该类的信号和槽也同时被继承,也可以根据需要自定义信号和槽。
1.信号与槽机制的连接方式
(1) 一个信号可以与另一个信号相连
connect(Object1,SIGNAL(signal1),Object2,SIGNAL(signal1));
表示Object1的信号1发射可以触发Object2的信号1发射。
(2) 同一个信号可以与多个槽相连
connect(Object1,SIGNAL(signal2),Object2,SIGNAL(slot2)); connect(Object1,SIGNAL(signal2),Object3,SIGNAL(slot1));
(3) 同一个槽可以响应多个信号
connect(Object1,SIGNAL(signal2),Object2,SIGNAL(slot2)); connect(Object3,SIGNAL(signal2),Object2,SIGNAL(slot2));
但是常用的连接方式为:
connect(Object1,SIGNAL(signal),Object2,SLOT(slot));
signal为对象Object1的信号,slot为对象Object2的槽。
上面小实例的方法1(即通过设计器完成功能的方法)中,在Qt应用程序的用户界面加入显示圆面积的“显示对应圆的面积”按钮后,应用程序并没有响应显示操作。这是因为程序还没有将相应的信号和槽关联起来。因此,为了响应用户的显示面积值的操作,需要将“显示对应圆的面积”按钮发送的单击信号QPushButton::clicked()和对话框QDialog的Dialog::on_pushButton_clicked()槽关联起来,可以根据需要在槽函数中做相应的操作。类似地,改变文本编辑框内容信号QLineEdit::textChanged(const QString & text)产生后与对话框QDialog的Dialog::on_lineEdit_textChanged(QString )槽关联起来。
方法 2(即通过编写代码完成功能的方法)中,将“显示对应圆的面积”按钮发送的单击信号QPushButton::clicked()和对话框QDialog的Dialog::showArea()槽关联起来。类似地,改变文本编辑框内容信号QLineEdit::textChanged(const QString & text) 产生后与对话框QDialog的Dialog::showArea()槽关联起来。
SIGNAL()和SLOT()是Qt定义的两个宏,它们返回其参数的C风格字符串(const char*)。因此,下面关联信号和槽的两个语句是等同的:
connect(pushButton,SIGNAL(clicked()),this,SLOT(doPushButton())); connect(pushButton, "clicked()",this, "doPushButton()");
2.信号与槽机制的优点
(1) 类型安全:需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或几个参数。如果信号和槽的签名不符,编译器就会报错。
(2) 松散耦合:信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的哪个槽需要接收它发出的信号,它只需做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道是哪个对象的哪个槽接收到了信号;同样的,对象的槽也不知道是哪些信号关联到了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除,应用程序也不会出现崩溃。
一个类要想支持信号和槽,必须从QObject或QObject的子类继承。注意,Qt信号和槽机制不支持对模板的使用。
3.信号/槽机制的效率
信号和槽机制增强了对象间通信的灵活性,然而这也损失了一些性能。同回调函数相比较,信号和槽机制有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用非虚函数慢10倍。原因主要有:
(1) 需要定位接收信号的对象。
(2) 安全地遍历所有的关联(比如,一个信号关联到多个槽的情况)。
(3) 编组(marshal)/解组(unmarshal)传递的参数。
(4) 多线程的时候,信号可能需要排队等待。
然而,与创建堆对象的new操作以及删除堆对象的delete操作相比较,信号和槽的代价只是它们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的;同信号和槽提供的灵活性和简便性相比,这点性能的损失也是值得的。
L2 功能模块
Qt定义了多个模块,每个模块包含相对应独立的库文件并实现各自相应的功能。下面解释一下常用的几种主要模块:
● QtCore:Qt的基本模块,定义了其他模块使用的Qt核心的非GUI类,所有其他的模块都依赖于该模块。
● QtGui:定义了图形用户界面类。
● QtNetwork:定义了Qt的网络编程类。
● QtOpenGL:定义了OpenGL的支持类。
● QtSql:定义了访问数据库的类。
● QtSvg:定义了显示和生成SVG(Scalable Vector Graphics)类。
● QtXml:定义了处理XML(eXtensible Markup Language)语言的类。
● Qt3Support:定义了以前版本Qt3兼容的类,以使得Qt3的程序能够更容易移植到新版本中。
● QtTest:定义了对Qt应用程序和库进行单元测试(unit testing)的类。
● QtDBus:提供了使用D-Bus进行进程间通信(Inter-Process Communication,IPC)的Qt类。
● QtScript:提供了对脚本的支持。
L3 Qt元对象系统
Qt元对象系统提供了对象间的通信机制(信号和槽)、运行时类型信息和动态属性系统的支持,是标准C++的一个扩展,它使Qt能更好地实现GUI图形用户界面编程。Qt的元对象系统不支持C++模板,尽管模板扩展了标准C++的功能,但是元对象系统提供了模板无法提供的一些特性。Qt的元对象系统基于三个事实:
(1) 基类QObject:任何想使用元对象系统功能的类必须继承自QObject。
(2) Q_OBJECT宏:Q_OBJECT宏必须出现在类的私有声明区,以启动元对象的特性。
(3) 元对象编译器(Meta-Object Compiler,moc):为QObject子类实现元对象特性提供必要的代码实现。
L4 布局管理器
在上面的例子中,第一个使用设计器(Qt Designer)没有使用布局管理器,直接将加载的几个简单的窗口部件显示出来,是因为这个例子很简单。然而,在较复杂的GUI用户界面上,仅仅通过指定窗口部件的父子关系以期达到加载和排列窗口部件的方法是行不通的,最好的办法是使用Qt提供的布局管理器。
QGridLayout *mainLayout=new QGridLayout(this); mainLayout->addWidget(label1,0,0); mainLayout->addWidget(lineEdit,0,1); mainLayout->addWidget(label2,1,0); mainLayout->addWidget(button,1,1); //setLayout(mainLayout);
其中:
● QGridLayout *mainLayout=new QGridLayout(this):创建一个网格布局管理器对象mainLayout,并用this指出父窗口。
● mainLayout->addWidget(…):将控件对象Label1、lineEdit、label2和button放置在该管理器中。还可以在创建布局管理器对象时不必指明父窗口。
● QWidget::setLayout(…):将布局管理器添加到对应的窗口部件对象中。本实例的主窗口就是父窗口,所以直接调用setLayout(mainLayout)即可。
布局管理器的具体使用方法请参照第4章布局管理器部分。