C++ GUI Programming with Qt 4 中文版
翻译:刘封天
e-mail:fengtian.WE@gmail.com
Blog:http://www.linuxall.net
Q Q :38407031
<注意>
朋友们,下载翻译这本书经过一次由Open Office造成的数据丢失。以下的翻译由于时间仓促翻译不够精细,并且也没有整理,很粗糙,但是许多朋友强烈要求,决定先贡献出来头两章,供大家入门学习,这本书非常的不错,通过一些例子来讲解QT的种种机制,老少皆宜。
希望大家多多包涵,如果发现了存在的一些问题,也请发送邮件告知在下。我的电子邮件:fengtian.WE@hotmail.com 之后我会做出一个专门的在线勘误
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
,为打架提供一个“翻译错误”的提交和查看平台。
并且说明以下,以后我会根据重要性来单独的翻译本书的一些章节。会在http://www.linuxall.net上有所公告。
<注意>
译者注:
在继续之前,说一下,千万不要用OPEN OFFICE,因为当我已经把这本书翻译到一般的时候,由于OPEN OFFICE造成文件损坏,我只能现在从新开始写.OO 顶你的肺,我用MS OFFICE写.
前言和序章都是罗嗦的废话,我不翻译了,一些内容对LINUX,WINDOWS,MAC OS等做个各自简短的说明,很少的一点,我这里只翻译对LINUX的说明.(难道windows那些还用说吗?)
在学习之前你要清楚QT是做什么的.
如果你要在计算机上做软件,比如计算器,那就去用VB,那是不二的选择.
如果你要做一个网站,就去用PHP,ASP或者.NET.
但是如果你没有Windows,也没有前大硬件设备,但是你还想开发图形化话的程序(GUI),这时候,你就需要QT了.
目录
第一部分:QT基础
第一章.开始学习
第二章.创建对话框
第三章.创建主窗体
第四章.实现程序功能
第五章.创建自定义空间
第二部分:中级QT
第六章.布局管理器
第七章.事件处理
第八章.2D和3D绘图
第九章.拖拽和扔放操作
第十章. “项浏览”类
第十一章.容器类
第十二章.输入输出
第十三章.数据库
第十四章.网络
第十五章.XML
第十六章.在线帮助
第三部分:高级QT
第十七章:国际化
第十八章:多线程
第十九章:制作插件
第二十章:基于平台的特性
第二十一章:嵌入式程序
第一部分:QT基础
第一章.开始学习
第二章.创建对话框
第三章.创建主窗体
第四章.实现程序功能
第五章.创建自定义空间
第1章. 开始学习
Hello Qt
//第一个QT程序
Making Connections
//建立连接
Laying Out Widgets
//布置控件
Using the Reference Documentation
//使用参考文档
这一章指导我们结合C++基于QT做一些小的GUI程序,并且也介绍了QT的两个关键思想:”信号---信号槽”和布局,在第二章我们会更深入的介绍他们,在第三章哦们会编写更实际的程序.
如果你使用过C#或者JAVA,但是对C++还不是很了解,你可以阅读附录B的C++介绍. (编者注:附录B是一个C++的速成教程,对于有经验的程序员来说,是很好的学习教程.)
第一个QT程序
让我们通过一个非常简单的QT程序来开始学习.我们会一行一行的的解读他们,然后说明该如何编译运行这个程序.
1 #include
2 #include
3 int main(int argc, char *argv[])
4 {
5 QApplication app(argc, argv);
6 QLabel *label = new QLabel("Hello Qt!");
7 label->show();
8 return app.exec();
9 }
头两行我们包含了两个头文件, Qapplication类和Qlabel类.对于QT来说,与类同名的头文件中包含了该类的定义.
第5行创建了一个Qapplication对象用来管理整个程序的资源.QT支持命令行参数,可以通过Qapplication的构造函数来传递argc 和 argv参数给程序使用.
第6行创建了一个用来显示"Hello Qt!"的控件.在Unix方面的术语中,”挂件(widget)”是用户界面的是个可视元素,这个词来自于"window gadget",相当于我们在windows编程中常说的“控件”或“容器”,按钮,菜单,滚动条和框架都是控件.控件是可以在包含控件的,例如:一个程序窗体同城会再包含一个QmenuBar(菜单条),一些QtoolBars(工具条),一个QstatusBar(状态栏),和一些其他的控件.大多数的程序会使用QmainWindow或者Qdialog作为整个程序的主窗体.但是QT是非常灵活的,所以甚至可以使用任何控件作为一个窗体.在这个例子中, Qlabel就被作为这个程序的窗体.
(编者注:随软关于widget叫法很多,不过在下文中我会同意称称之为”控件”,并且后面的翻译在遇到某个陌生控件的头几次我会给出中文的名字,不过为了让大家都习惯叫控件的英文名,所以再往后就都使用英文名了.)
第7行将这个label(标签)设置成可见的.控件生成后一般是隐藏的,所以我们要在顶只好后使其可见,这样可以避免控件的不正常显示.
第8行将程序的控制权交给QT,注意这时程序会进图事件循环(循环等待事件发生).这是一种等待模式,使程序等待用户的动作,比如点击鼠标或是按下键盘.用户的动作通常会产生一个事件(也叫"消息"),然后程序可以对期做出响应,一般是执行一个或多个函数,例如当用户点击控件,"mouse press" 和 a "mouse release"事件就会产生.要注意GUI程序完全不同于常规的命令行程序那样让你输入,然后处理结果,最后在非认为的情况下自动结束.
简单来说,我们不需要顾虑是否要在main()函数的最后删除Qlabel对象,因为对于这种小程序不用考虑内存遗漏问题,在程序结束后操作系统会自动回收内存.
图1.1. Hello on Linux
我们现在可以尝试在你的机器上运行这个程序了.首先你需要安装QT 4.1.1或者更新的版本,安装过程可以参考附录A.现在我们假设你已经正确的安装了QT4并且将它的bin目录加入了环境变量PATH中.你可以将程序代码拷贝到你的文件中起名叫hello.cpp,并且放在目录hello中,或者自己写进去也行.在本书的附带光盘中也有: /examples/chap01/hello/hello.cpp.
(译者注:相信看这个的朋友都没有那个光盘吧,如果你有的话,希望能打包发给我一份,谢谢*@_@* 呵呵 ,所以以后关于这些废话我就不翻译了,而且后面说的编译部分也只翻译一次,所有的例子的编译方式都一样,并且这里建议大家为自己的每一个例子都建立一个文件夹来放置。)
进入命令行,进入hello目录,键入:
qmake –project
这样就创建了一个与平台无关项目文件(hello.pro),然后键入:
qmake hello.pro
会通过项目文件创建一个基于你当前平台的makefile 文件.
./hello
运行程序。
图1.2. A label with basic HTML formatting
在进行下一个例子之前,我们来做一个有趣的实验,替换下面的代码:
将:QLabel *label = new QLabel("Hello Qt!");
替换为:QLabel *label = new QLabel("Hello ""Qt!
");
重新编译运行这个程序看看,你会发现,使用简单的HTML代码我们可以轻松美化QT程序的界面。
建立连接
第二个例子演示如何让程序响应用户的动作,这个程序由一个按钮组成,当用户点击它时会退出程序。程序代码和Hello相似,只不过这里会用QpushButton代替Qlabel作为主控件,并且我们将会八用户的动作和一些程序联系起来。
1 #include
2 #include
3 int main(int argc, char *argv[])
4 {
5 QApplication app(argc, argv);
6 QPushButton *button = new QPushButton("Quit");
7 QObject::connect(button, SIGNAL(clicked()),
8 &app, SLOT(quit()));
9 button->show();
10 return app.exec();
11 }
QT控件会通过发射一个信号(signal)来表明用户做个一个操作或控件的状态发生了改变.例如:当用户点击按钮时, QpushButton就会发射clicked()信号.信号可以连接到一个函数上(一般叫做槽(slot)(译者注:国内一般称之为信号槽)),当信号发射后,与之相两的信号槽就会被执行.例如:我们将按钮的clicked()信号和Qapplication对象的quit()信号槽连接起来,我们会在下一章中更深入的讲解SIGNAL()和SLOT()机制.
图1.3. The Quit application
编译并运行程序,当点击Quit后,会退出程序.
布置控件
在这一部分,我们通过一个小例子来演示如何布置控件在窗体中的几何位置,还有如何使用信号和信号槽让两个控件同步工作.程序会询问用户年龄,用户可以通过翻动框或滑动条来操作.
这个程序包含三个控件: QspinBox()(翻动框), Qslider(滑动条), Qwidget.Qwidget作为程序的主窗体, QspinBox和Qslider都是放置在Qwidget内部的的.他们是Qwidget的子对象,也可以说Qwidget是他们的父对象, Qwidget没有父对象,因为它被用做于顶级窗体. Qwidget和它的子类都可以用带有QWidget *参数的构造函数来制定期父对象.
图1.4. The Age application
程序代码:
1 #include
2 #include
3 #include
4 #include
5 int main(int argc, char *argv[])
6 {
7 QApplication app(argc, argv);
8 QWidget *window = new QWidget;
9 window->setWindowTitle("Enter Your Age");
10 QSpinBox *spinBox = new QSpinBox;
11 QSlider *slider = new QSlider(Qt::Horizontal);
12 spinBox->setRange(0, 130);
13 slider->setRange(0, 130);
14 QObject::connect(spinBox, SIGNAL(valueChanged(int)),
15 slider, SLOT(setValue(int)));
16 QObject::connect(slider, SIGNAL(valueChanged(int)),
17 spinBox, SLOT(setValue(int)));
18 spinBox->setValue(35);
19 QHBoxLayout *layout = new QHBoxLayout;
20 layout->addWidget(spinBox);
21 layout->addWidget(slider);
22 window->setLayout(layout);
23 window->show();
24 return app.exec();
25 }
第8,9行将Qwidget设置为程序的主窗体,调用setWindowTitle()方法设置窗体的标题.
第10,11行创建QspinBox对象和Qslider对象, 我们假设用户的年龄最多不超过130岁,并在12,13行为这两个控件设置有效范围.我们可以通过QspinBox和Qslider构造函数来指定他们的父对象,但是没有必要这么做,因为我们之后就会发现布局系统会自动设定他们的父子关系.
下面的14到17行调用了两个QObject::connect()方法会使得QspinBox和Qslider的状态能够同步起来,显示同样的数值.当他们其中一个的值发生改变时,它就会发射valueChanged(int)信号,然后另一个控件的setValue(int)信号槽就会被调用,并且用新的数值作为参数.
18行将QspinBox的值设置为35,这样的话,QspinBox就会发射一个valueChanged(int)信号,并且参数int的值会是35.这个参数会被传递到Qslider的setValue(int)信号槽,将Qslider的值设置为35,因为Qslider的值发生了改变所以Qslider也会发射一个valueChanged(int)信号,发射到QspinBox的setValue(int)信号槽,但是要注意这里的setValue(int)不会再发射任何信号,因为QspinBox的值已经是35了.这样是为了预防无限循环.
(译者注:以前在做事件处理的时候,我都是自己写判断来放置无限循环的,现在好像QT已经在信号里面做了判断处理,省事多了,不用再自己写了,HOHO 值得表扬.)图1.5说明了这种情况.
图1.5. Changing one widget's value changes both
19行到22行我们用“布局管理器”来布置两个控件.布局管理器是通过控件的功能来设置控件的尺寸和位置的对象.一共有三种布局管理器.
QHBoxLayout 在水平方向上从左到右或者从左到右布置控件.
QVBoxLayout 在竖直方向从上到下布置控件.
QGridLayout 以网格形式布局控件.
22行调用setLayout()方法来为窗体设置布局管理器.在设置布局管理器的同时,控件QSpinBox 和 QSlider 会在后台重新设置各自的父对象,所以说我们没有必要在创建控件是通过构造函数指定它的父对象,完全可以交给布局管理器来完成.
图The Age application's widgets
虽然我们并没有对控件的尺寸和位置做任何的详细设定,登时他们仍然被适当的紧凑的并排放置在一起,这是因为布局QHBox-Layout根据控件的功能和需要自动的分配位置和尺寸.布局管理器使得我们不再手动去为控件的位置和尺寸去进行硬编码,也使得控件能够自动的根据窗体尺寸的改变而跟着适应.
QT是非常易学易用高灵活性的GUI编程工具.通常QT程序在创建控件后都有必要设置他们的属性,将控件加入”布局”,自动的为控件非配尺寸和位置,用型号和信号槽机制处理用户对已经做了连接的控件所做的操作.
使用参考文档
QT参考文复函了所有的QT类和函数的说明信息,对于任何QT开发开说,QT参考文档都是优秀的工具.文档中虽然包含了许都的类和函数,但并未包括所有的,也并不是对所提及到的所有的类和函数全部做出深入的讲解.如果想更好的使用QT,你应该使自己能够尽可能熟练的使用QT参考文档.
(译者注:相信”写程序,查文档”对于老程序员来说,不会陌生,要高速有效的写出好的程序,考的不是你的记忆,而是对帮助文档的灵活使用.)
HTML格式的文档在QT目录下的doc/html目录下,可以使用任意浏览器打开.除了HTML文档之外也可以使用Qt Assistant(QT 帮助浏览器),Qt Assistant是一个比浏览器更加方便的文档查看工具,他的搜索,索引等功能可以更加快随便捷的查看你要查阅的文档.在LINUX下使用命令assistant来打开Qt Assistant(编者注:我安装完QT就在开始里有Qt Assistant 呵呵).
在”API区”首页的连接用来提供各种方式来查阅QT类。页面"All Classes"列出所有的QT类,"Main Classes"会列出最常用的QT类,你可以查看本章用到的类来作为练习。
图1.7Qt's documentation in Qt Assistant on Mac OS X
注意,通过继承得到的方法会被写在父类的文档中。例如:QpushButton的参考文档没有方法show()的说明,但是可以在他的父类Qwidget的文档中找到这个方法的说明。图1.8会说明类之间的继承关系。
图1.8. Inheritance tree for the Qt classes seen so far
在线文档在:http://doc.trolltech.com/
上面的网站还有《QT季刊》的一些经典文章。
这一章介绍了信号―信号槽和布局的关键思想。也展现出用面向对象概念创建控件的特点。如果你浏览了QT文档的话,可能会发现,可以去直接学习一个新控件加以使用。而且QT对方法,参数,枚举等等的命名也都让人感觉很自然。
第二章:创建对话框
Subclassing Qdialog
//Qdialog类
Signals and Slots in Depth
//进入了解信号和信号槽
Rapid Dialog Design
//快速
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
窗体
Shape-Changing Dialogs
//变形对话框
Dynamic Dialogs
//动态对话框
Built-in Widget and Dialog Classes
//对话框类和内置控件
这一章指导你使用QT创建一个对话框.对话框的功能是为用户提供可选择的选项,并且允许用户设定一些选项.这个叫做对话框或者简单会话,它为用户和程序提供了交流的功能.
大多数GUI程序会由一个菜单和工具条,还有一些对话框窗体来组成主窗体.程序可能会创建一个对话框来接受用户的动作,比如计算器程序.
我们用纯手工编码的方式来演示如何编写第一个对话框.然后再看看如何使用Qt Designer的建立对话框.Qt Designer是一种可视化的QT设计器,可以比手工编写代码更加快速便捷的编写,修改和测试你设计的界面.
Qdialog类
这个例子是完全用C++编写的“查找对话框”。我们在一个类中来实现,从而使他能够独立使用,并且使用自己的信号和信号槽。
图2.1. The Find dialog
程序代码分别写在finddialog.h和finddialog.cpp中,我们从finddialog.h来看:
1 #ifndef FINDDIALOG_H
2 #define FINDDIALOG_H
3 #include
4 class QCheckBox;
5 class QLabel;
6 class QLineEdit;
7 class QPushButton;
第1,2,27行保证头文件不被多次包含。
第三行包含了Qdialog类,他会作为我们对话框类的父类,Qdialog继承自Qwidget类。
4到7行在实现对话框之前提前声明一些类,提前声明会通知C++编译器虽然没有给出这些类的详细定义,但是这些类是存在的(通常会在头文件里有详细的定义)。关于这一点我们会在后面讲解更多。
接下来我们定义FindDialog类,并且继承于Qdialog:
8 class FindDialog : public QDialog
9 {
10 Q_OBJECT
11 public:
12 FindDialog(QWidget *parent = 0);
如果要定义信号或信号槽的话,在定义类的开始一定要加入宏指令Q_OBJECT。
FindDialog有典型的QT控件类的构造函数,参数parent指定此控件的父对象,默认是空指针,也就是没有父对象。
13 signals:
14 void findNext(const QString &str, Qt::CaseSensitivity cs);
15 void findPrevious(const QString &str, Qt::CaseSensitivity cs);
信号代码段声明了两个当用户点击查找按钮时会发射的信号。如果向后搜索选项被开启,对话框就会发射findPrevious()和findNext()信号。
关键字signals是一个宏指令,C++预处理器会在编译器得到它之前对期进行处理。Qt::CaseSensitivity是枚举类型,值分别可以为Qt::CaseSensitive或Qt::CaseInsensitive。
16 private slots:
17 void findClicked();
18 void enableFindButton(const QString &text);
19 private:
20 QLabel *label;
21 QLineEdit *lineEdit;
22 QCheckBox *caseCheckBox;
23 QCheckBox *backwardCheckBox;
24 QPushButton *findButton;
25 QPushButton *closeButton;
26 };
27 #endif
在私有代码段,我们声明两个信号槽,我们通过指针指向这些控件,可以在实现信号槽的时候更好的访问他们,关键字slots也是一个宏指令,会在处理后才交给C++编译器。
我们已经提前定义了这些私有变量的类。因为我们我能所有的指针都不会在头文件中做访问,所以编译器不需要类的详细定义。我们可以包含相关的头文件如QcheckBox,Qlabel等。但是提前定义可能会使编译更快一些。
下面看看finddialog.cpp,它包含的FindDialog的实现部分。
1 #include
2 #include "finddialog.h"
首先我们包含了QtGui,这个头文件包括了QT GUI类的定义。QT包括几个单独的模块,存放在他们各自的目录里。比较重要的模块有QtCore,QtGui, QtNetwork, QtOpenGL, QtSql, QtSvg, 和 QtXml。头文件QtGui中包含着QtCore和QtGui模块的所有类的定义。包含这个头文件后我们就不用再逐个包含所有类了。
在头文件filedialog.h中,没有包含Qdialog头文件,并且提前声明了QCheckBox, QLabel,
QLineEdit,和QpushButton,但是包含这样一个大的头文件文件不是一个好主意,尤其是在比较大的程序中。
3 FindDialog::FindDialog(QWidget *parent)
4 : QDialog(parent)
5 {
6 label = new QLabel(tr("Find &what:"));
7 lineEdit = new QLineEdit;
8 label->setBuddy(lineEdit);
9 caseCheckBox = new QCheckBox(tr("Match &case"));
10 backwardCheckBox = new QCheckBox(tr("Search &backward"));
11 findButton = new QPushButton(tr("&Find"));
12 findButton->setDefault(true);
13 findButton->setEnabled(false);
14 closeButton = new QPushButton(tr("Close"));
第4行将参数parent传递给父类的构造函数。然后开始创建子对象,通过调用方法tr()可以使得字符创自动转换成其他的语言,它定义在Qobject类中,并且QObject所有的子类都包含宏指令Q_OBJECT。建议为所有用户可以看到的字符串都使用TR(),即使不打算将你的程序翻译成其他语言,也可以将此作为一个好习惯。QT程序的多语言化将在17章讲解。
我们可以在字符串中使用连接符“&”来指定快捷键。例如11行创建的查找按可以使用Alt+F来激活。连接符也可以用来控制焦点。在第6行我们创建了一个快捷键为Alt+W的标签,并且在第8行我们设定了这个标签的友好控件为文本框。所谓友好控件,就是说当标签的快捷键被按下时,该标签的友好控件就会得到焦点。所以当用户按下标签的快捷键Alt+W时,焦点就会交给标签的友好控件,也就是这个文本框。
在12行我们将查找按钮设置为窗体的默认按键,默认按键是指当回车按下后被激活的控件。13将禁用查找按钮,被禁用的按钮将显示诶灰色并且不对用户的操作做任何响应处理。
当文本框中的内容发生改变时私有信号槽enableFindButton(const QString &)会被调用。当用户点击查找按钮时私有信号槽findClicked()会被调用,当用户点击Close按钮后对话框窗体会关闭。信号槽close()继承自Qwidget,默认的功能是将控件隐藏(而不是删除),我们稍后再看enableFindButton()和findClicked()信号槽。
因为查找对话框继承自Qobject类,所以我们可以在调用connect()方式时忽略QObject::前缀。
21 QHBoxLayout *topLeftLayout = new QHBoxLayout;
22 topLeftLayout->addWidget(label);
23 topLeftLayout->addWidget(lineEdit);
24 QVBoxLayout *leftLayout = new QVBoxLayout;
25 leftLayout->addLayout(topLeftLayout);
26 leftLayout->addWidget(caseCheckBox);
27 leftLayout->addWidget(backwardCheckBox);
28 QVBoxLayout *rightLayout = new QVBoxLayout;
29 rightLayout->addWidget(findButton);
30 rightLayout->addWidget(closeButton);
31 rightLayout->addStretch();
32 QHBoxLayout *mainLayout = new QHBoxLayout;
33 mainLayout->addLayout(leftLayout);
34 mainLayout->addLayout(rightLayout);
35 setLayout(mainLayout);
接下来用布局管理器来布置这些子对象,布局不仅可以包含控件,还可以包含其他布局。结合QHBoxLayouts, QVBoxLayouts,和QgridLayouts来嵌套使用,可以布置出非常复杂的程序界面。
在我们的查找对话框中,我们使用了两个QHBoxLayouts和两个QVBoxLayouts两个布局,如图2.2,在最外面的是主布局,通过第35行非配给FindDialog,用来负责整个窗体的布局,其他三个作为子布局。在图2.2右下角的像小弹簧一样的东西是一个spacer item(伸缩器),用来使右边两个按钮紧紧相连,更加紧凑,确保按钮靠都向上面对齐。
图2.2. The Find dialog's layouts
严格的说,布局管理器类不是控件,它继承自Qobject类的子类:Qlayout。在例图上控件使用实线绘制,布局使用虚线绘制,但在程序运行时,布局是隐藏的,不会被显示出来。当在25,33,34行将自布局加载到父布局中,自布局会自动的改变自己的父布局。然后到主布局被设置给对话框后,它会变成对话框的子对象,并且所有的控件也都会变成对话框的子对象。图2.3说明了最后的父子关系。
图2.3. The Find dialog's parentchild relationships
36 setWindowTitle(tr("Find"));
37 setFixedHeight(sizeHint().height());
38 }
最后我们为对话框的窗体设置标题和准确的高度,使得所有控件都不会占据额外控件,这样会更加紧凑。QWidget::sizeHint()方法会返回一个控件的理想尺寸。
这样以来我们就完成了FindDialog类的构造函数。因为我么会使用new关键字来创建控件和布局,你可能会觉得应该编写一个叫做delete的方法来销毁他们。但其实没有必要这么做,因为当父对象销毁时QT会自动的删除子对象,并且子对象和布局都是FindDialog的子集。
现在我们来看对话框的信号槽:
39 void FindDialog::findClicked()
40 {
41 QString text = lineEdit->text();
42 Qt::CaseSensitivity cs =
43 caseCheckBox->isChecked() ? Qt::CaseSensitive
44 : Qt::CaseInsensitive;
45 if (backwardCheckBox->isChecked()) {
46 emit findPrevious(text, cs);
47 } else {
48 emit findNext(text, cs);
49 }
50 }
51 void FindDialog::enableFindButton(const QString &text)
52 {
53 findButton->setEnabled(!text.isEmpty());
54 }
当用户点击查找按钮时,信号槽findClicked()就会被调用,它会根据是否选中向前查找选项发射findPrevious()或者findNext()信号。QT的关键字emit会被C++预处理更换成便准的C++表达式。
当用户改变文本框中的内容时信号槽enableFindButton()会被调用。如果文本框中有内容的话就它就会将查找按钮变为可用的,如果没有任何内容的话就会禁用查找按钮。
现在对话框的两个信号槽已经完成了,我们来创建main.cpp文件测试对话框控件:
1 #include
2 #include "finddialog.h"
3 int main(int argc, char *argv[])
4 {
5 QApplication app(argc, argv);
6 FindDialog *dialog = new FindDialog;
7 dialog->show();
8 return app.exec();
9 }
使用qmake编译程序,因为FindDialog类的定义中含有宏指令Q_OBJECT,所以qmake会通过特定规则运行moc工具来产生一个makefile。(moc:QT的元对象编译器。元对象系统会在下面进行讲解。)
为了让moc正常工作,我们必须在头文件中对类进行定义,将它和执行文件分隔开来。Moc会结合C++来处理头文件。
使用了宏指令Q_OBJECT的类都需要使用moc来处理。不用担心,qmake会自动的添加所需要的指令来生成makefile。但如果你忘使用qmake和moc来生成makefile,连接器会对一些报出一些问题,并且不实现。不过这个信息比较模糊,往往不能说明问题。GCC会产生一些这样的警告:
finddialog.o: In function 'FindDialog::tr(char const*, char const*)':
/usr/lib/qt/src/corelib/global/qglobal.h:1430: undefined reference to
'FindDialog::staticMetaObject'
如果经常出现这种情况,可以运行qmake更新makefile,然后重新编译程序。
现在运行程序,如果你的平台支持快捷键,测试一下Alt+W, Alt+C, Alt+B, 和 Alt+F是否能出发正确的动作。按下TAB键选择控件,默认情况下tab键得到控件焦点的顺序与控件被创建的顺序一致。可以使用QWidget::setTabOrder()方法来改变顺序。
合理的TAB顺序可以使得用户在不能或者不像使用鼠标的情况下,很好的完全使用键盘进行操作。完全的合理的键盘控制也可以提高软件操作人员的工作效率。
在第三章我们会结合查找对话框来做一个真实的程序。并且我们会连接findPrevious() 和 findNext()信号到一些信号槽。
深入了解信号和信号槽
“信号-信号槽”机制是QT程序的基本原理,它使得程序员可以绑定一些根本不相干的控件。我们已经将我们自己声明实现的信号和信号槽连接在一起了。现在我们来更深入的研究这一机制。
信号槽相当于C++成员函数,可以是虚函数,也可以被重载,可以是私有(private)的,共有(public)的或是受保护(protected)的,可以像普通的方法那样被调用,并且可有以任何类型的数据作为参数。信号槽也可以连接到信号,也就是每次都自动的调用信号使其发射。
connect()表达式如下:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
这里的sender和receiver是指向Qobjects类的指针,这里的信号和信号槽是没有参数名的函数。
迄今为止我们看到的例子中,我们总是将不同的信号连接不同的信号槽,但也要考虑其他的情况:
一个信号连接到多个信号槽:
connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(updateStatusBarIndicator(int)));
当信号发射时,信号槽会被一个一个调用(没有固定的顺序),
多个信号连接到同一个信号槽:
connect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
this, SLOT(handleMathError()));
当任意一个信号发射时,这个信号槽都会被调用。
一个信号连接到另一个信号:
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SIGNAL(updateRecord(const QString &)));
当第一个信号发射后第二个信号也会随之发射,另外,信号和信号的连接无异于信号和信号槽的连接.
删除连接:
disconnect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
这种做法比较少见。因为当对象被删除时,其中的连接也会被QT自动的删除.
要使信号能够连接到信号槽或则其他信号,他们必须拥有同样类型和同样顺序的参数:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));
另外,如果信号的参数比与之相连的信号槽的参数多,可以完全忽视多出的参数.
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(checkErrorCode(int)));
当以debug模式编译程序后,在运行时如果发现参数的类型不匹配或者信号,信号槽不存在的话QT会发出警告.
至今为止,我们我们只是使用了控件的信号和信号槽.但是这一机制还可以使用于所有的Qobject子类:
class Employee : public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
void Employee::setSalary(int newSalary)
{
if (newSalary != mySalary) {
mySalary = newSalary;
emit salaryChanged(mySalary);
}
}
注意信号槽setSalary()是如何实现的.如果newSalary != mySalary那么就发射salary-Changed()信号,以确保不会发生无限循环.
(译者注:关于信号的无限循环,前面提到过,看来在自定义的槽中,需要自己判断,而QT自身的槽已经帮我们写好的判断来防止循环.)
Qt's Meta-Object System(QT元对象系统)
Qt的一个重要产物是它通过C++建立了独立的软件组件,不受其他的组件约束和影响。
这种机制叫做meta-object system(元对象系统),它提供两个关键的服务:"信号-信号槽"和"自我检查".如果你要实现信号或信号槽,那么一定会用到"自我检查"功能.它还使得程序员在运行时可以得到Qobject子类的一些信息,比如类名或对象的信号和信号槽的列表.这一机制也支持一些工具(如Qt Designer),和一些文字的翻译(如国际化),并且为QT的程序脚本奠定了基础.
因为C++不为meta-object system提供所需的元信息,所以QT会通过一个独立工具moc来解决这一问题,它可以通过C++的函数来分析定义了Q_OBJECT的类,然后生成一些有用的信息.因为moc是完全通过C++来实现的,所以meta-object system可以基于任何C++编译器来使用.
关于这一机制所做的工作:
宏命令Q_OBJECT声明了一些自我检查函数如metaObject(), TR(), qt_metacall()等等,所以在所有的Qobject子类中用到这些函数的都要包含宏命令Q_OBJECT.
Moc工具为所有的声明了Q_OBJECT的函数和所有的信号做出处理.
对象的成员函数(比如connect() 或 disconnect())也会通过见我检查函数来实现.
qmake, moc,和 Qobject会自动的来操作这些,所以你不需要太多的考虑这些.但如果你对此感兴趣,你可以通过文档来查看QmetaObject类,或者产看moc产生的源代码来了解它是如何工作的.
快速设计对话框
可以使用手工编写代码来设计QT程序,并且许多程序员都完全使用C++来开发完成的程序,但是还有很多程序员更喜欢使用可视化手段来设计窗体,因为它比手动编写代码更快捷更轻松,并且能够更快捷方便的测试或修改窗体的设计。
Qt Designer使得程序员可以进行可视化的设计。Qt Designer可以进行整个程序的开发,也可以用来制作一个程序中单独的一些窗体。Qt Designer会为所设计出的窗体生成C++代码,所以Qt Designer系列工具可以在任何的C++编译器环境中使用。
在这一部分,我们使用Qt Designer和代码来制作一个Go-to-Cell对话框(如图2.4),不管是使用Qt Designer还是手写代码,都会以一下步骤制作这个程序:
创建并初始化子对象;
将控件放如布局(用布局管理器布置控件);
设置TAB键序;
建立信号-信号槽连接;
实现对话框的自定义信号槽;
图2.4. The Go-to-Cell dialog
启动Qt Designer,(译者注:我安装万在开始里就有。对于其他系统不做详细说明。)启动后会出现模版列表,选择“Widget”模版,然后点OK。(也许Dialog with Buttons Bottom模版看上去更好,但是在我们这个例子中需要需要一个OK按钮和和一个Cancel按钮来完成。)现在应该有了一个叫做Untitled的窗口。默认情况下Qt Designer的界面是由几个单独的顶级窗体组成的,但如果你更喜欢MDI风格的界面,也就是一个顶级窗体中包含几个子窗体,可以点击Edit|User Interface Mode|Docked Window来更改界面。
第一步是将子窗体放置在窗体上。创建一个label(标签),一个line editor(单行文本框),一个horizontal spacer(水平伸缩器),和两个push buttons(按钮)。你可以从Qt Designer的控件箱中拖出控件并将其放在窗体上,spacer控件不会在最终的窗体中显示,只会在Qt Designer中显示出一个蓝色的弹簧。
图2.5. Qt Designer in docked window mode on Windows
现在从窗体下面拖拽将其更短点。结果应该和图2.6相似。不需要话太多的时间在布置窗体上的控件,稍后,Qt的布局管理器将会使他们的位置更合适。
图2.6. The form with some widgets
通过QT的property editor(属性编辑器)来设置所有控件的属性:
1. 点击选中text label,将属性objectName(对象名)设置为“label”并且设置其属性text为“&Cell Location:”。
2. 点击选中line editor(单行文本框),objectName设为“lineEdit”。
3. 点击选中第一个按钮。ObjectName设为“okButton”,enabled设为“false”,text设为“OK”,default设为“true”。
4. 点击选中第二个按钮,objectName设为“cancelButton”,text设置为“Cancel”。
5. 通过点击窗体的背景选中窗体,设置objectName为“GoToCellDialog”,windowTitle为“Go to Cell”。
除了text label显示为“&Cell Location”外,其他所有控件都看上去不错了。点击Edit|Edit
Buddies进入特殊的友好控件编辑模式。点在label上并且将红色箭头拖拽至line editor再放开。这样label就应该显示为“Cell Location”了,并且line editor也成为了它的友好控件。现在点击Edit|Edit Widgets退出友好控件编辑模式。
图2.7. The form with properties set
下一步来布置窗体上的控件:
1. 选中Cell Location label,按下Shift点选line editor来将他们两个一起选中,选择Form|Lay Out Horizontally。
2. 选中spacer,按Shift追加按钮OK和Cancel。选择Form|Lay Out Horizontally。
3. 点击窗体背景,并且也取消的对其他控件的选择,选择 Form|Lay Out Vertically。
4. 选择Form|Adjust Size来调节窗体尺寸。
红线会在布局创建后出现,但是在运行时是不会显示出来的。
图2.8. The form with the layouts
选择Edit|Edit Tab Order,所有可以得到焦点的控件边上会出现一个蓝底数字。点击控件将设置他们的得到焦点的顺序,再点击Edit|Edit Widgets退出TAB排序模式。
图2.9. Setting the form's tab order
选择Form|Preview menu来预览对话框,可以反复按下TAB键测试焦点顺序。点击标题栏上的关闭按钮来关闭窗口。
将对话框保存为gotocelldialog.ui放在目录gotocell,并在此目录下创建main.cpp文件以文本方式编辑:
#include
#include
#include "ui_gotocelldialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Ui::GoToCellDialog ui;
QDialog *dialog = new QDialog;
ui.setupUi(dialog);
dialog->show();
return app.exec();
}
用qmake创建a .pro和makefile(qmake -project; qmake goto-cell.pro),
Now run qmake to create a .pro file and a makefile (qmake -project; qmake goto-cell.pro). qmake可以巧妙的检测用户界面文件goto-celldialog.ui,然后产生可以调用uic的makefile规则,uic工具可以将gotocelldialog.ui转成称C++程序保存在ui_gotocelldialog.h中。
产生的ui_gotocelldialog.h文件包含了Ui::GoToCellDialog类的定义,相当于gotocelldialog.ui文件,类定义了成员变量来保存窗体的控件和布局,setupUi()方法用来产生窗体,所产生的类如下:
class Ui::GoToCellDialog
{
public:
QLabel *label;
QLineEdit *lineEdit;
QSpacerItem *spacerItem;
QPushButton *okButton;
QPushBu