嵌入式爱好者

查看: 18648|回复: 0

[QT] QT学习总结

[复制链接]

9

主题

9

帖子

33

积分

扫一扫,手机访问本帖
发表于 2016-12-17 14:15:02 | 显示全部楼层 |阅读模式
一、Qt 概述
1、关于 Qt
Qt 是 Trolltech 公司的一个产品。Qt 是一个多平台的 C++图形用户界面应用程序框架。
它提供给应用程序开发者建立图形用户界面应用程序所需的所有功能。Qt 是完全面向对象
的,它很容易扩展,并且允许真正的组件编程。自从 1996 年早些时候,Qt 进入商业领域,
它已经成为全世界范围内数千种成功的应用程序的基础。 Qt 也是流行的 Linux 桌面环境 KDE
的基础。(KDE 是所有主要的 Linux 发行版的一个标准组件)
Qt 支持下述平台:
MS/Windows - 95、98、NT 4.0、ME、和 2000
Unix/X11 - Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX 和
其它很多 X11 平台
Macintosh - Mac OS X
Embedded -  有帧缓冲(frame buffer)支持的 Linux 平台。
2、Qt 版本信息
Qt 被按不同的版本发行:
Qt 企业版和 Qt 专业版:提供给商业软件开发。它们提供传统商业软件发行版并且提供
免费升级和技术支持服务。企业版比专业版多一些扩展模块。  
Qt 自由版:是 Qt 仅仅为了开发自由和开放源码软件  提供的 Unix/X11 版本。在 Q 公共
许可证和 GNU 通用公共许可证下,它是免费的。
Qt/嵌入式自由版:是 Qt 为了开发自由软件提供的嵌入式版本。在 GNU 通用公共许可
证下,它是免费的。
下表是关于 Qt 在 Windows 环境下各个版本的区别。 (Qt 为 Windows 只提供了专业版和
企业版,不过自由版本的 Qt 仍然可以在 Windows 环境下使用)
组成模块  自由版  专业版  企业版
Qt 的基本模块(工具、核心、窗口部件、对话框)
与平台无关的 Qt 图形用户界面工具包和应用类
X  X  X
Qt 设计器
可视化的 Qt 图形用户界面的生成器
X  X  X
图标视图模块
几套图形用户交互操作的可视化效果。
X  X
工作区模块
多文档界面(MDI)支持
X  X
OpenGL  三维图形模块
在 Qt 中集成了 OpenGL
X
网络模块
一些套接字, TCP,FTP 和异步 DNS 查询并与平台无关的类
X
画布模块
为可视化效果,图表和其它而优化的二维图形领域
X
表格模块
灵活的,可编辑的表格/电子表格
X
XML 模块      X
通过 SAX 接口和 DOM Level 1 的 XML 解析器
SQL 模块
SQL 数据库访问类
X
3、Qt 的组成
Qt 提供了一组范围相当广泛的 C++类库,并包含了几种命令行和图形界面的工具,有
效地使用这些工具可以加速开发过程。
Qt Designer:Qt 设计器。用来可视化地设计应用程序界面。
Qt Linguist:Qt 语言学家。用来翻译应用程序。以此提供对多种语言的支持。
Qmake:使用此工具可以由简单的、 与平台无关的工程文件来生成编译所需的 Makefile。
Qt Assistant:关于 Qt 的帮助文件。类似于 MSDN。可以快速地发现你所需要的帮助。
moc:元对象编译器。
uic:用户界面编译器。在程序编译时被自动调用,通过 ui_*.h 文件生成应用程序界面。
qembed:转换数据,比如,将图片转换为 C++代码。
4、Qt 的安装
安装的过程对于不同的 Qt 平台是不同的。在 Windows 环境下安装 Qt,需要先安装
MinGW。
MinGW,即  Minimalist GNU For Windows。它是一些头文件和端口库的集合,该集合
允许人们在没有第三方动态链接库的情况下使用  GCC (GNU Compiler C) 产生  Windows32
程序。 在基本层,MinGW  是一组包含文件和端口库,其功能是允许控制台模式的程序使
用微软的标准 C 运行时间库(MSVCRT.DLL),该库在所有的  NT OS  上有效,在所有的
Windows 95  发行版以上的  Windows OS  有效,使用基本运行时间,你可以使用  GCC  写控
制台模式的符合美国标准化组织(ANSI)程序,可以使用微软提供的  C  运行时间扩展。该
功能是  Windows32 API  不具备的。下一个组成部分是  w32api  包,它是一组可以使用
Windows32 API  的包含文件和端口库。与基本运行时间相结合,就可以有充分的权利既使
用  CRT (C Runtime) 又使用  Windows32 API  功能。 实际上  MinGW  并不是一个  C/C++  编
译器,而是一套  GNU  工具集合。除开  GCC (GNU  编译器集合)  以外,MinGW  还包含有
一些其他的  GNU  程序开发工具  (比如  gawk bison  等等)。
在安装 MinGW 之后,再安装 Qt,然后更改一下 Windows 系统的环境变量,就可以在
Windows 环境下使用 Qt 了。如果想在 VC 环境下使用 Qt,那么还需要进一步编译和设置,
或者下载专门用于 VC 的 QT 版本。有关此方面的信息请参考附录。
二、  开始学习 Qt
1、Hello, Qt!
我们以一个非常简单的 Qt 程序开始 Qt 的学习。我们首先一行行的分析代码,然后我
们将会看到怎样编译和运行这个程序。
1 #include <QApplication>
2 #include <QLabel>
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 }
第 1 行和第 2 行包含了两个类的定义:QApplication 和 QLabel。对于每一个 Qt 的类,
都会有一个同名的头文件,头文件里包含了这个类的定义。因此,你如果在程序中使用了一
个类的对象,那么在程序中就必须包括这个头文件。
第 3 行是程序的入口。几乎在使用 Qt 的所有情况下,main()函数只需要在把控制权转
交给 Qt 库之前执行一些初始化,然后 Qt 库通过事件来向程序告知用户的行为。argc 是命
令行变量的数量,argv 是命令行变量的数组。这是一个 C/C++特征。它不是 Qt 专有的,无
论如何 Qt 需要处理这些变量
第 5 行定义了一个 QApplication 对象 App。QApplication 管理了各种各样的应用程序的
广泛资源,比如默认的字体和光标。 App 的创建需要 argc 和 argv 是因为 Qt 支持一些自己的
命令行参数。在每一个使用 Qt 的应用程序中都必须使用一个 QApplication 对象,并且在任
何 Qt 的窗口系统部件被使用之前创建此对象是必须的。App 在这里被创建并且处理后面的
命令行变量(比如在 X 窗口下的-display)。请注意,所有被 Qt 识别的命令行参数都会从 argv
中被移除(并且 argc 也因此而减少)。
第 6 行创建了一个 QLabel 窗口部件(widget) ,用来显示“Hello,Qt!”。在 Qt 和 Unix
的术语中,一个窗口部件就是用户界面中一个可见的元素,它相当于 Windows 术语中的“容
器”加上“控制器”。按钮(Button)、菜单(menu)、滚动条(scroll bars)和框架(frame)
都是窗口部件的例子。窗口部件可以包含其它的窗口部件。例如,一个应用程序界面通常就
是一个包含了 QMenuBar,一些 Q**,一个 QStatusBar 和其它的一些部件的窗口。绝大
多数应用程序使用一个 QMainWindow 或者一个 QDialog 作为程序界面,但是 Qt 允许任何
窗口部件成为窗口。在这个例子中,QLabel 窗口部件就是作为应用程序主窗口的。
第 7 行使我们创建的 QLabel 可见。当窗口部件被创建的时候,它总是隐藏的,必须调
用 show()来使它可见。通过这个特点我们可以在显示这些窗口部件之前定制它们,这样就不
会出现闪烁的情况。
第 8 行就是 main()将控制权交给 Qt。在这里,程序进入了事件循环。事件循环是一种
stand-by 的模式,程序会等待用户的动作(比如按下鼠标或者是键盘)。用户的动作将会产
生程序可以做出反应的事件(也被称为“消息”) 。程序对这些事件的反应通常是执行一个或
几个函数。
为了简单起见,我们没有在 main()函数的结尾处调用 delete 来删除 QLabel 对象。这种
内存泄露是无害的,因为像这样的小程序,在结束时操作系统将会释放程序占用的内存堆。
下面我们来编译这个程序。建立一个名为 hello 的目录,在目录下建立一个名为 hello.cpp
的 c++源文件,将上面的代码写入文件中。
运行“开始&#61664;程序&#61664;Qt by Trolltech&#61664;Qt Command Prompt”。
在命令行模式下,切换目录到 hello 下,然后输入命令:qmake    –project。这个命令将
产生一个依赖于工作平台的工程文件(hello.pro)。
再输入命令:qmake    hello.pro。这个命令通过工程文件产生一个可以在特定工作平台
上使用的 makefile。
最后输入命令:make 来产生应用程序。运行这个程序,可以得到如下的程序界面。
Qt 也支持 XML。我们可以把程序的第 6 行替换成下面的语句:
QLabel *label = new QLabel ("<h2><i>Hello</i> " "<font color=red>Qt! </font></h2>");
重新编译程序,我们发现界面拥有了简单的 HTML 风格。如下图:
2、调用退出
第二个例子展示了如何使应用程序对用户的动作进行响应。这个应用程序包括了一个按
钮,用户可以点击这个按钮来退出程序。程序代码与上一个程序非常相似,不同之处在于我
们使用了一个 QPushButton 来代替 QLabel 作为我们的主窗口,并且我们将一个用户动作(点
击一个按钮)和一些程序代码连接起来。
1 #include <QApplication>
2 #include <QPushButton>
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 程序的窗口部件发射信号(signals)来指出一个用户的动作或者是状态的变化。在这
个例子中,当用户点击这个按钮的时候,QPushButton 就会发射一个信号——clicked()。一
个信号可以和一个函数(在这种情况下我们把这个函数叫做“槽(slot)”)相连,当信号
被发射的时候,和信号相连的槽就会自动执行。在这个例子中,我们把按钮的信号“clicked()”
和一个 QApplication 对象的槽“quit()”相连。当按钮被按下的时候,这个程序就退出了。
3、窗口布局
在本小节,我们将用一个样例来展现如何在窗口中规划各个部件的布局,并学习使用信
号和槽来使两个窗口部件同步。这个应用程序要求输入用户的年龄,使用者可以通过一个旋
转窗口或者一个滑块窗口来输入。
这个应用程序包括三个窗口部件:一个 QSpinBox,一个 QSlider 和一个 QWidget。窗口
部件 QWidget 是程序的主窗口。 QSpinBox 和 QSlider 被放置在 QWidget 中;他们是 QWidget
的子窗口。当然,我们也可以说 QWidget 是 QSpinBox 和 QSlider 的父窗口。QWidget 本身
没有父窗口,因为它被当作一个顶级的窗口。 QWidget 以及所有它的子类的构造函数都拥有
一个参数:QWidget *,这说明了它的父窗口。
下面是程序的代码:
1 #include <QApplication>
2 #include <QHBoxLayout>
3 #include <QSlider>
4 #include <QSpinBox>
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 (50);
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,第 12 行和第 13 行设置了它们
的取值范围(我们假设用户最大也只有 130 岁)。我们可以将之前创建的 QWidget 对象 window
传递给 QSpinBox 和 QSlider 的构造函数,用来说明这两个对象的父窗口,但是这么做并不
是必须的。原因是窗口布局系统将会自己指出这一点,自动将 window 设置为父窗口。我们
一会儿就可以看到这个特性。
在第 14 行和第 17 行,两个对于 QObject::connect()函数的调用确保了旋转窗口和滑块窗
口的同步,这样这两个窗口总是显示同样的数值。不管一个窗口对象的数值何时发生变化,
它的信号 valueChanged(int)就将被发射,而另一个窗口对象的槽 setValue(int)会接受到这个信
号,使得自身的数值与其相等。
第 18 行将旋转窗口的数值设置为 50。当这个事件发生的时候,QSpinBox 发射信号
valueChanged(int) ,这个信号包括一个值为 50 的整型参数。这个参数被 QSlider 的槽
setValue(int)接受,就会将滑块的值也设置为 50。由于 QSlider 的值被改变,所以 QSlider 也
会发出一个 valueChanged(int)信号并触发 QSpinBox 的 setValue(int)槽。但是在这个时候,
QSpinBox 不会再发出任何信号,因为旋转窗口的值已经被设置为 50 了。这将有效地防止信
号的无限循环。
从第 19 行到第 22 行,我们通过使用一个 layout 管理器对旋转窗口和滑块窗口进行了布
局设置。一个布局管理者就是一个根据窗口作用设置其大小和位置的对象。Qt 有三个主要
的布局管理类:
QHBoxLayout:将窗口部件水平自左至右设置(有些情况下是自右向左)。
QVBoxLayout:将窗口部件垂直自上向下设置。
QGridLayout:  以网格形式设置窗口部件。
第 22 行我们调用 QWidget::setLayout()函数在对象 window 上安装布局管理器。通过这
个调用,QSpinBox 和 QSlider 自动成为布局管理器所在窗口的子窗口。现在我们明白为什
么在设置子窗口时不用显式地说明父窗口了。
可以看到,虽然没有明显地给出任何窗口的大小和位置,但 QSpinBox 和 QSlider 是很
完美地被水平依次放置的。这是因为 QHBox-Layout 根据各个窗口的作用自动的为其设置了
合理的大小和位置。这个功能使我们从烦琐的界面调整中解放出来,更加专注于功能的实现。
Qt 构建用户界面的方法很容易理解,并且有很高的灵活性。Qt 程序员最常用的设计模
式是:说明所需要的窗口部件,然后设置这些部件必须的特性。程序员把窗口部件添加到布
局管理器中,布局管理器就将自动地设置这些部件的大小和位置。而用户界面的行为是通过
连接各个部件(运用信号/槽机制)来实现的。
4、派生 QDialog
我们现在开始尝试着在 Qt 里只用 C++语言而不是借助界面设计器来完成一个对话框:
FIND。我们将这个对话框作为一个类来完成,这么做的好处是我们使这个对话框成为了一
个独立的,拥有自己的信号和槽的,设备齐全的组件。
程序的源代码由两部分组成:finddialog.h 和 finddialog.cpp。我们从头文件开始。
1 #ifndef    FINDDIALOG_H
2 #define    FINDDIALOG_H
3 #include <QDialog>
4 class QCheckBox;
5 class QLabel;
6 class QLineEdit;
7 class QPushButton;
第 1 行,第 2 行(和第 27 行)的作用是防止头文件被重复包含。
第 3 行包含了 QDialog 的定义。QDialog 从 QWidget 继承而来,是 Qt 的对话框基类。
第 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);
标记为  signals 的这一段声明了两个信号。当用户点击对话框的“Find”按钮的时候,
信号将被发射。如果选项“Search backward”被选中,对话框将发射消息 findPrevious();相
反的,对话框将发射消息 findNext()。
关键字“signals”实际上也是一个宏。 C++预处理器将在编译器看到它之前就已经将它
转换为了标准的 C++。Qt::CaseSecsitivity 是一个枚举类型。它可以代表值 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
在类的 private 字段中,我们声明了两个槽。为了实现这些槽,我们需要访问大多数对
话框的子窗口,所以我们在私有字段中保留了这些子窗口的指针。和 signals 一样,关键字
slots 也是一个构造后可以被 C++编译器辩识的宏。
对于私有变量,我们使用了它们的类的预定义。这是被编译器所允许的,因为他们都
是指针,而我们在头文件中并不需要访问他们,所以编译器并不需要完整的类定义。我们可
以在头文件中包含使用这些类所需要的头文件(<QCheckBox>,<QLabel>,等等),但是使用
预定义可以在某种程度上加快编译过程。
现在我们来看源文件 finddialog.cpp。源文件里包括了 FindDialog 类的实现。
1 #include <QtGui>
2 #include "finddialog.h"
首先,我们包含了<QtGui>。这个头文件包含了对于 Qt 的 GUI 类的定义。 Qt 包括一些
模块,每一个模块都依赖于自己的库文件。最重要的几个模块分别是 QtCore, QtGui,
QtNetwork, QtOpenGL, QtSpl, QtSvg 和 QtXml。头文件<QtGui>包括了 QtCore 和 QtGui 模块
中所有类的实现。包含此头文件使我们不用单独地列出每个类所需要的头文件。
在 filedialog.h 中,我们也可以简单地包含<QtGui>,而不是像我们之前做的那样包括
<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(),这些字符串被标记为可以翻译成别的语言。函数 tr()在
QObject 和每个含有 Q_OBJECT 宏的子类中被定义。将没一个用户可见的字符串都用 TR()
包括起来是一个很好的习惯,即使你现在并没有将程序翻译为别的语言的计划。对于 Qt 应
用程序的翻译将在后述章节中详细呈现。
在字符串中,我们用操作符’&’来指出快捷键。例如,第 11 行创建了一个”Find”按钮。
在支持快捷键的平台上用户可以按下 Alt+F 来切换到这个按钮上。操作符’&’也可以用来控
制程序焦点:在第 6 行我们创建了一个拥有快捷键(Alt+W)的标签,在第 8 行我们给这个
标签设置一个伙伴(buddy):lineEdit。一个 buddy 就是当标签的快捷键被按下的时候,接
收程序焦点的窗口。所以,当用户按下 Alt+W 的时候,程序焦点转移到字符编辑框上。
在第 12 行,通过调用函数 setDefault(true),我们将按钮 Find 设置为程序的默认按钮(所
谓的默认按钮就是当用户按下回车键时被触发的按钮)。在第 13 行,我们将按钮 Find 设置
为不可用。当一个窗口被设置为不可用的时候,它通常显示为灰色,并不会和用户产生任何
交互。
15 connect (lineEdit, SIGNAL (textChanged (const QString &)),
16 this, SLOT (enableFindButton (const QString &)));
17 connect (findButton, SIGNAL (clicked ()),
18 this, SLOT (findClicked ()));
19 connect (closeButton, SIGNAL (clicked ()),
20 this, SLOT (close ()));
当字符编辑框中的文字被改变的时候,私有的槽 enableFindButton(const QString &)被调
用。私有槽 findClicked()在用户点击 Find 按钮时被调用。当用户点击关闭的时候,对话框将
关闭自身。槽 close()是从 QWidget 继承而来的,它的默认行为是隐藏窗口对象(而不是删
除它)。我们马上就能看到槽 enableFindButton()和 findClicked()的代码。
由于 QObject 是 FindDialog 的一个父类,所以我们可以在调用 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);
接下来,我们使用布局管理器来对子窗口部件进行布局。布局管理器可以包括窗口,
也可以包括其它的布局管理器。通过对 QHBoxLayout,QVBoxLayout 和 QGridLayout 这三个布局管理类的嵌套使用,就可以生成非常复杂的对话框了。
如上图所示,对于对话框 Find,我们使用了两个 QHBoxLayout 和两个 QVBoxLayout。
最外层的布局是主要的布局,它在第 35 行被安装并负责响应对话框的全部区域。另外的三
个布局是子布局。图的右下方有一个“弹簧”,这是个空白的区域。在按钮 Find 和 Close
的下方使用空白是为了保证这些按钮出现在它们所在的布局的上方。
一个比较微妙的地方是布局管理类并不是窗口对象。它们从 QLayout 继承而来,而
QLayout 从 QObject 继承而来。在上图中,窗口以实线标记,而布局以虚线标记。在一个正在运行的程序当中,布局是不可见的。
当子布局被添加到父布局中的时候(代码的第 25 行,33 行和 34 行),子布局自动子
类化。当主布局被安装的时候(第 35 行),它成为了对话框的一个子类,所以在布局当中的
所有窗口对象都成为了对话框的子类。本例中各个类的继承层次在下图中表明。
36 setWindowTitle (tr ("Find"));
37 setFixedHeight (sizeHint ().height ());
38 }
在代码的最后,我们将对话框标题栏的内容设置为“Find”,然后给窗口设置一个合适
的高度。由于这个对话框中没有任何子窗口可能占据多余的垂直空间,函数
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  findButton->setEnabled (! text.isEmpty ());
53 }
当用户按下 Find 按钮时, 按钮会发射 findPrevious()或者 findNext()信号, 槽 findClicked()
会被调用。关键字 emit 在 Qt 里很特殊,和其它的 Qt 扩展名一样,它在被传递给标准 C++
编译器之前会被 C++预处理器转换。
当用户改变字符编辑框中的内容时,槽 enableFindButton()被调用。也就是说,当字符
编辑框中有内容时,Find 按钮是可见的;当编辑框中没有内容的时候,Find 按钮不可见。
这两个槽被定义之后,我们关于这个对话框的内容就完成了。现在可以创建一个名为
main.cpp 的文件来试验一下 FindDialog 窗口。
1 #include <QApplication>
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 }
现在可以编译并运行这个程序了。如果你的平台支持快捷键,尝试着使用快捷键
Alt+W, Alt+C, Alt+B 和 Alt+F 来触发正确的行为。按下 Tab 键来切换各个窗口。默认的 tab
顺序在程序生成时已经确定。如果想更改这个顺序,可以调用函数 QWidget::setTabOrder()。
以上我们派生了 QDialog 来生成对话框。同样的道理,我们也通过可以派生
QMainWindow 来生成程序主窗口,然后在主窗口中创建菜单和工具条。也就是说,我们可
以通过只编写代码来生成一个完整的程序。
5、关于“信号和槽”(signal and slot)
通过上几节我们已经看到了“信号与槽”的运用。下面我们详细解释这个机制以及一些
相关的内容。使用信号与槽的基本格式为:
connect (sender, SIGNAL (signal), receiver, SLOT (slot));
这里的 sender 和 receiver 是指向 QObject 的指针,而 signal 和 slot 是无参数名的函数信
号。
“信号和槽”机制用于 Qt 对象间的通讯。 “信号/槽”机制是一种关于无缝对象通讯的
机制,它是 Qt 的一个中心特征,也是 Qt 与其它工具包的最不相同的部分。
在图形用户界面编程中,我们经常希望一个窗口部件的一个变化被通知给另一个窗口部
件。更一般地,我们希望任何一类的对象可以和其它对象进行通讯。例如,如果我们正在解
析一个 XML 文件,当我们遇到一个新的标签时,我们也许希望通知列表视图我们正在用来
表达 XML 文件的结构。
较老的工具包使用一种被称作回调(callback)的通讯方式来实现同一目的。回调是指
一个函数的指针,如果你希望一个处理函数通知你一些事件,你可以把另一个函数(回调)
的指针传递给处理函数。处理函数在适当的时候调用回调。回调有两个主要缺点: 首先他们
不是类型安全的。我们从来都不能确定处理函数使用了正确的参数来调用回调。其次回调和
处理函数是非常强有力地联系在一起的,因为处理函数必须知道要调用哪个回调。
在 Qt 中我们有一种可以替代回调的技术。我们使用信号和槽。当一个特定事件发生的
时候,一个信号被发射。Qt 的窗口部件有很多预定义的信号,但是我们总是可以通过继承
来加入我们自己的信号。 ‘槽’就是一个可以被调用处理特定信号的函数。Qt 的窗口部件也
有很多预定义的槽,但是通常的习惯是你可以加入自己的槽,这样你就可以处理你所感兴趣
的信号。
“信号和槽” 的机制是类型安全的:一个信号的签名必须与它的接收槽的签名相匹配(实
际上一个槽的签名可以比它接收的信号的签名少,因为它可以忽略额外的签名) 。因为签名
是一致的,编译器就可以帮助我们检测类型不匹配。信号和槽是宽松地联系在一起的:一个
发射信号的类不用知道也不用注意哪个槽要接收这个信号。Qt 的“信号和槽”的机制可以
保证如果你把一个信号和一个槽连接起来,槽会在正确的时间使用信号的参数而被调用。信
号和槽可以使用任何数量、任何类型的参数。它们是完全类型安全的:不会再有回调核心转
储(core dump)。
从 QObject 类, 或者它的一个子类(比如 QWidget 类) 所继承出的所有类,都可以包含
信号和槽。当对象改变它们的状态的时候,信号被发送,这就是所有的对象通讯时所做的一
切。它不执导也不注意有没有对象接收它所发射的信号。槽用来接收信号,但它们同时也是
对象中正常的成员函数。一个槽不知道它是否被任意信号连接。此外,对象并不知道关于这
种通讯的机制。你可以把很多信号和你所希望的单一槽相连,并且一个信号也可以和你所期
望的许多槽相连。把一个信号和另外一个信号直接相连也是可行的(这种情况下,只要第一
个信号被发射,第二个信号就会被立即发射)。
▲  一个小例子
一个最小的 C++类声明如下:
class Foo
{
public:
Foo ();
int value () const {return val ;}
void setValue (int);
private:
int val;
};
一个小的 Qt 类声明如下:
Class Foo: public QObject
{
Q_OBJECT
Public:
Foo ();
int value () const {return val ;}
public slots:
void setValue(int);
signals:
void valueChanged (int);
private:
int val;
}
这个类有同样的内部状态,和公有方法访问状态,但是另外它也支持使用信号和槽的
组件编程:这个类可以通过发射一个信号: valueChanged()来告诉外界它的状态发生了变
化,并且它有一个槽,其它对象可以发送信号给这个槽。所有包含信号和/或者槽的类必须
在它们的声明中提到 Q_OBJECT。
槽可以由应用程序的编写者来实现。这里是 Foo::setValue()一个可能的实现:
V oid Foo::setValue (int v)
{
if (v != val) {
val = v;
emit valueChanged (v);
}
}
emit valueChanged(v)这一行从对象中发射 valueChanged 信号。正如你所能看到的,你
通过使用 emit signal(arguments)来发射信号。
下面是把两个对象连接在一起的一种方法:
Foo a, b;
connect (&a, SIGNAL (valueChanged (int)), &b, SLOT (setValue (int)));
b.setValue (11); // a == undefined b == 11
a.setValue (79); // a == 79 b == 79
b.value ();  
调用 a.setValue(79)会使 a 发射一个 valueChanged() 信号, b 将会在它的 setValue()
槽中接收这个信号,也就是 b.setValue(79) 被调用。接下来 b 会发射同样的 valueChanged()
信号,但是因为没有槽被连接到 b 的 valueChanged()信号,所以没有发生任何事(信号消
失了)。
注意:只有当 v != val 的时候 setValue()函数才会设置这个值并且发射信号。这样就
避免了在循环连接的情况下(比如 b.valueChanged() 和 a.setValue()连接在一起)出现无
休止的循环的情况。
这个例子说明了对象之间可以在互相不知道的情况下一起工作,只要在最初的时在它
们中间建立连接。
预处理程序改变或者移除了 signals、slots 和 emit 这些关键字,这样就可以使用标
准的 C++编译器。
在一个定义有信号和槽的类上运行 moc。这样就会生成一个可以和其它对象文件编译
和连接成引用程序的 C++源文件。
▲  信号
当对象的内部状态发生改变,信号就被发射,只有定义了一个信号的类和它的子类才能
发射这个信号。
例如,一个列表框同时发射 highlighted()和 activated()这两个信号。绝大多数对象也
许只对 activated()这个信号感兴趣,但是有时我们也想知道列表框中的哪个条目在当前是
高亮的。如果两个不同的类对同一个信号感兴趣,你可以把这个信号和这两个对象连接起来。  
当一个信号被发射,它所连接的槽会被立即执行,就像一个普通函数调用一样。信号/
槽机制完全不依赖于任何一种图形用户界面的事件回路。当所有的槽都返回后  emit 也将返
回。   
如果几个槽被连接到一个信号,当信号被发射时,这些槽就会被按任意顺序一个接一个
地执行。
信号会由 moc 自动生成并且一定不要在.cpp 文件中实现。它们也不能有任何返回类型
(比如使用 void)。
关于参数需要注意。我们的经验显示如果信号和槽不使用特殊的类型,它们都可以多次
使用。如果 QScrollBar::valueChanged()  使用了一个特殊的类型,比如 hypothetical
QRangeControl::Range,它就只能被连接到被设计成可以处理  QRangeControl 的槽。
▲  槽
当一个和槽连接的信号被发射的时候,这个操被调用。槽也是普通的 C++函数并且可
以像它们一样被调用;它们唯一的特点就是它们可以被信号连接。槽的参数不能含有默认值,
并且和信号一样,为了槽的参数而使用自己特定的类型是很不明智的。
因为槽就是普通成员函数,但却有一点非常有意思的东西,它们也和普通成员函数一
样有访问权限。一个槽的访问权限决定了谁可以和它相连:
一个 public slots:区包含了任何信号都可以相连的槽。这对于组件编程来说非常有用:
你生成了许多对象,它们互相并不知道,把它们的信号和槽连接起来,这样信息就可以正确
地传递,并且就像一个铁路模型,把它打开然后让它跑起来。
一个 protected slots:区包含了之后这个类和它的子类的信号才能连接的槽。这就是说这
些槽只是类的实现的一部分,而不是它和外界的接口。
一个 private slots:区包含了之后这个类本身的信号可以连接的槽。这就是说它和这个类
是非常紧密的,甚至它的子类都没有获得连接权利这样的信任。
你也可以把槽定义为虚的,这在实践中被发现也是非常有用的。
6、关于元对象系统(Meta-Object System)
Qt 的一个最主要的特点可能就是它扩展了 C++的机制,可以创建独立的软件组件,这
些组件可以被绑定在一起,而不需要互相的任何了解。
这个机制被成为元对象系统,它提供了两个关键服务:信号/槽、运行时的类型信息和
动态属性系统(内省机制)。内省机制对于实现信号和槽是必须的,并且允许应用程序员在
程序运行时获得“元信息”(包括被对象支持的信号和槽的列表,以及这些信号/槽所在的类
的名称)。内省机制同时支持“道具”(对于 Qt Designer)和文本翻译(国际化) ,它还是
Qt 应用程序脚本(Qt Script for Application)的基础。
标准的 C++并不提供对于 Qt 的元对象系统所需要的动态元信息的支持。Qt 提供了一
个单独的工具:元对象编译器(moc)来解决这个问题。Moc 用来解析 Q_OBJECT 类的定
义,使这些信息在 C++函数中可用。由于 moc 使用纯粹的 C++函数来实现,所以 Qt 的元对
象系统在任何 C++编译器下都可以工作。
元对象系统这样工作:
●  Q_OBJECT 宏声明一些内省函数(metaObject(),TR(),qt_matacall()和少量其他的函
数)。这些函数必须在所有的 QObject 的子类中被实现。
●  Qt 的 moc 工具负责执行被 Q_OBJECT 宏声明的函数,同时负责执行所有的信号
函数。
●  QObject 的成员函数,例如 connect()和 disconnect(),使用内省函数来工作。
元对象系统基于以下三类:
1)、QOBJECT 类;
2)、类声明中的私有段的 Q_OBJECT 宏;
3)、元对象编译器。
Moc 读取 C++源文件。如果它发现其中包含一个或多个类的声明中含有 Q_OBJECT 宏,
它就会给含有 Q_OBJECT 宏的类生成另一个含有元对象代码的 C++源文件。这个生成的源
文件可以被类的源文件包含(#include)到或者和这个类的实现一起编译和连接。
除了提供对象间通讯的信号和槽机制之外(这也是介绍这个系统的主要原因), QObject
中的元对象代码也实现其它特征:
1)、className()函数在运行的时候以字符串返回类的名称,不需要 C++编译器中的运
行时刻类型识别(RTTI)的支持。
2)、 inherits()函数返回这个对象是否是一个继承于 QObject 继承树中一个特定类的类的
实例。
3)、tr()和 trUtf8()两个函数是用于国际化的字符串翻译。
4)、setPorperty()和 property()两个函数是用来通过名称动态设置和获得对象属性的。
5)、metaObject()函数返回这个类所关联的元对象。
虽然使用 QObject 作为一个基类而不使用 Q_OBJECT 宏和元对象代码是可以的,但是
如果 Q_OBJECT 宏没有被使用,那么这里的信号和槽以及其它特征描述都不会被提供。根
据元对象系统的观点,一个没有元代码的 QObject 的子类和它含有元对象代码的最近的祖先
相同。举例来说就是, className()将不会返回你的类的实际名称,返回的是它的这个祖先的
名称。我们强烈建议 QObject 的所有子类使用 Q_OBJECT 宏,而不管它们是否实际使用了
信号、槽和属性。
三、  Qt 设计器(Qt Designer)
1、概述
Qt 允许程序员不通过任何设计工具,以纯粹的 C++代码来设计一个程序。但是更多的
程序员更加习惯于在一个可视化的环境中来设计程序,尤其是在界面设计的时候。这是因为
这种设计方式更加符合人类思考的习惯,也比书写代码要快速的多。
Qt 也提供了这样一个可视化的界面设计工具:Qt 设计器(Qt Designer)。其开始界面
如上图所示。Qt 设计器可以用来开发一个应用程序全部或者部分的界面组件。以 Qt 设计器
生成的界面组件最终被变成 C++代码,因此 Qt 设计器可以被用在一个传统的工具链中,并
且它是编译器无关的。
在不同的平台上启动 Qt Designer 的方式有一定差别。在 Windows 环境下你可以在“开
始->程序->Qt”这个组件中找到 Qt Designer 的图标并点击;在 Unix 环境下,在命令行模式
下输入命令: “designer”;在 Mac Os 下,在 X    Finder 下双击 Designer 图标。
默认情况下,Qt Designer 的用户界面是由几个顶级的窗口共同组成的。如果你更习惯
于一个 MDI-style 的界面(由一个顶级窗口和几个子窗口组成的界面),可以在菜单 Edit->User
Inte**ce Mode 中选择 Docked Window 来切换界面。上图显示的就是 MDI-style 的界面风格。
2、开始学习
在这个小节中,我们将使用 Qt Designer 来生成一个对话框: Go-to-cell。对话框如下图
所示。
不管我们是使用 Qt Designer 还是编码来实现一个对话框,都包括以下相同的步骤:
1)、创建并初始化子窗口部件。
2)、将子窗口部件放置到布局当中。
3)、对 Tab 的顺序进行设置。
4)、放置信号和槽的连接。
5)、完成对话框的通用槽的功能。
现在开始工作。首先在 Qt Designer 的菜单中选择“File->New Form”。程序将弹出一
个窗口如下:
可以看到在窗口左上方有一个“templates\forms”的菜单,下面有四个可供选择的模板。
第一个和第二个都是对话框,区别在于对话框中按钮的位置不同。第三个是主窗口,第四
个是窗口部件。本例中我们需要选择第四个选项(Widget)。 现在你应该可以看到 Qt Designer
为你生成了一个窗口,标题栏是“Untitled”(也许你觉得第一个模板更加适合我们的例子,
不过,在这里,我们将手动添加“OK”和“Cancel”这两个按钮)。
我们按照上面讲过的顺序来设计这个窗口。首先需要生成子窗口部件并将它们放置
在工作台上。在 Qt Designer 工作界面的左侧,我们可以看到很多程序设计经常用到的窗口
部件。如果你需要它们中的那一个,用鼠标把它拖到工作台上就可以了。我们在菜单“Display
Widgets”中选择一个“Label”,在菜单“Input Widgets”中选择一个“Line Edit”,在菜单
“Spacers”中选择一个“Horizontal Spacer”(这个空白组件在最终形成的窗口中是不可见的,
在 Qt Designer 中,空白组件的样子就像是一个蓝色的弹簧),在菜单“Buttons”中选择两个
“Push Button”。按照下图的位置,将它们摆放起来。
你可以看到,我们的工作界面显的太大了一些,可以用鼠标拉住边框让它改变大小,
直到你满意为止。一个类似下图的组件是不是已经出现了?记住不要花费太多的时间来摆放
这些窗口部件的位置,只要大概类似就可以了,因为他们并不是不可调整的。Qt 的布局管
理器将会对他们的位置和大小自动进行一些优化。
现在我们已经创建了这些子窗口部件,并把他们放置在了合适的位置,接下来要做
的就是初始化他们。这需要设定这些子窗口的属性。在 Qt Designer 工作界面的右侧也同样
有一些窗口,这些就是属性窗口。 可以在这些窗口中找到所有部件需要设置的属性,并更改
它们,就可以达到我们的目的了。
1)、点击 TextLabel,确认它的“objectName”属性是“label”,然后将它的“text”属
性设置为“&Cell Location”。
2)、点击 line editor (窗口中的空白编辑框),确认它的“objectName”属性是“lineEdit”。
3)、点击第一个按钮(左侧),将其“objectName”属性设置为“OKButton”,“enable”
属性设置为“false”,“text”属性设置为“OK”,“default”属性设置为“true”。
4)、点击第二个按钮(右侧),将其“objectName”属性设置为“cancelButton”,“text”
属性设置为“Cancel”。
5)、点击工作平台的背景,这样我们可以选择整个的界面。这也是一个窗口,也拥有
自己的属性。我们把它的“objectName”属性设置为“GoToCellDialog”,“windowtTitle”属
性设置为“Go to Cell”。
完成后的 Form 变成了下图的形式:
接下来我们给 Label 设置一个伙伴(buddy),在这个例子中, Label 的伙伴当然是后面
的字符编辑框 line editor。在 Qt Designer 的菜单中进行选择:Edit->Edit Buddies。这样我们
进入 Buddy 模式,可以设置子窗口的伙伴了。点击 Label,Label 将会变成红色的,同时出
现一条线,将这条线拖拽到后面的 line editor 上,然后松开。这时两个窗口都将变成红色的,
中间有一条红线相连。移动鼠标到别处并点击,窗口将变成蓝色的。这说明我们已经设置成
功了(如果设置错误,则可以用鼠标在连接窗口的线条上点击,这时相连的窗口又会变成红
色的,此时按 Delete 键就可以取消设置)。选择:Edit->Edit Widget,可以退出这个模式,回
到主菜单中。如下图所示:
接下来我们对工作台上的各个子窗口进行布局:
1)、 点击标签“Cell Location”,按住 Shift 键,再点击后面的字符编辑框“line editor”,
这样它们两个窗口被同时选中。选择菜单:Form->Lay Out Horizontally。这样这两个窗口将
被一个红色边框的矩形包围。
2)点击空白子窗口“Spacer”,按住 Shift 键,再点击后面的两个按钮,同时选定这三
个窗口,然后选择菜单:Form->Lay Out Horizontally。这样这三个窗口将被一个红色边框的
矩形包围。
3)、点击工作平台的背景,取消任何已经选择的组件,然后选择:Form->Lay Out
Vertically。这样我们可以将第 1 步和第 2 步所生成的两个水平布局进行垂直布局。
4)、选择菜单:Form->Adjust Size。这样我们可以调整主窗口的大小。最后效果如下
图(图中的红线在程序最终生成的时候不会被显示):
然后我们对 Tab 的顺序进行设置。选择菜单:Edit->Edit Tab Order。一个带有数字的
蓝色矩形会显示在每一个窗口部件上(由于我们将 Label 和 line editor 设置为 buddy,这样
它们在指定 Tab 的时候被视为一个组件)。点击这些带数字的矩形可以调整到你想要的顺序
上,然后选择:Edit->Edit Widgets 离开这个模式。
现在我们的对话框的外形已经完成了。选择菜单:Form->Preview。这样你可以预览我
们设计的窗口。可以反复按下 Tab 键来验证顺序是否和我们设置的一致。点击顶部的关闭按
钮就可以关闭这个预览窗口。
所有的界面设计工作已经完成了,现在我们要做的就是保存下来。选择菜单: File->Save
Form As:。建立一个名为“gotocell”的文件夹,将这个界面保存为名称为“gotocelldialog.ui”
的文件中,放置到新建的文件夹中。
下面我们试着显示这个对话框。在文件夹“gotocell”中建立一个文件 main.cpp。在 cpp
文件中编写代码如下:
#include <QApplication>
#include <QDialog>
#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 工具来生成一个工程文件 (.pro)和一个 makefile (qmake –project;
qmake gotocell.pro)。qmake 是一个足够聪明的工具,它可以侦测到当前文件夹中的用户界
面文件(在本例中就是我们刚才保存的 gotocelldialog.ui),并且自动生成合适的 makefile
规则来调用 uic(uic 就是 Qt 的用户界面编译器)。Uic 工具将把文件 gotocelldialog.ui 的内
容转换成 C++代码,写入自动生成的头文件“ui_gotocelldialog.h”中。
生成的头文件“ui_gotocelldialog.h”包括了对类 Ui::GoToCellDialog 的定义。这是一
个和 gotocelldialog.ui 文件内容相同的定义,只不过它是用 C++表示的。这个类声明了存储
各个子窗口和布局的成员变量,还包括一个 setupUi()的成员函数,用来初始化程序窗口。生
成的类就像下面所显示的一样:
class Ui::GoToCellDialog
{
public:
QLabel *label;
QLineEdit *lineEdit;
QSpacerItem *spacerItem;
QPushButton *okButton;
QPushButton *cancelButton;
...
void setupUi (QWidget *widget) {
...
}
};
这个自动生成的类并没有继承任何 Qt 的类。当我们在 main.cpp 中使用这个界面的时
候,我们创建一个 QDialog 并把它传递给函数 setupUi()。
现在可以编译并运行这个程序了。程序界面已经可以开始工作了,但是你会发现一些
问题——界面的功能并没有完全实现:
1)、按钮“OK”总是不可见的。
2)、按钮“Cancel”虽然可以显示,但它什么也不能做。
3)、字符编辑框接受任何字符,而不是像我们期望的那样只接受合法的数字。
显然我们应该通过编辑一些代码来使对话框函数正确地工作。最干净利落的方法是类
继承。我们创建一个新的类,这个类同时继承 QDialog(程序中创建的,被传递给函数
setupUi())和 Ui::GoToCellDialog,并完成这些未实现的功能(这也验证了一条名言:任何
软件的问题都可以通过添加一个间接的层来解决)。对于这样的一个类,我们的命名习惯是:
把 uic 生成的类去掉前缀“Ui::”作为我们的类名称。本例中,这个类叫 GoToCellDialog。
创建一个头文件 gotocelldialog.h,在里面编辑代码如下:
#ifndef GOTOCELLDIALOG_H
#define GOTOCELLDIALOG_H
#include <QDialog>
#include "ui_gotocelldialog.h"
class GoToCellDialog: public QDialog, public Ui::GoToCellDialog
{
Q_OBJECT
public:
GoToCellDialog (QWidget *parent = 0);
private slots:
void on_lineEdit_textChanged ();
};
#endif
有了头文件,我们还需要源文件。再创建一个 gotocelldialog.cpp,编辑代码如下:
#include <QtGui>
#include "gotocelldialog.h"
GoToCellDialog::GoToCellDialog (QWidget *parent)
: QDialog (parent)
{
setupUi (this);
QRegExp regExp ("[A-Za-z] [1-9] [0-9] {0, 2}");
lineEdit->setValidator (new QRegExpValidator (regExp, this));
connect (okButton, SIGNAL (clicked ()), this, SLOT (accept ()));
connect (cancelButton, SIGNAL (clicked ()), this, SLOT (reject ()));
}
void GoToCellDialog::on_lineEdit_textChanged ()
{
okButton->setEnabled (lineEdit->hasAcceptableInput ());
}
在构造函数中,我们调用了 setupUi()来初始化工作平台。由于采用了多重继承,这使
得我们可以直接访问类 Ui::GoToCellDialog 的成员变量。在用户界面生成之后,函数 setupUi()
将会把任何遵循命名规则:on_objectName_signalName()而命名的槽和“objectName”中的
信号 signalName()连接起来。在这个例子中,这意味着函数 setupUi()将会建立如下的连接:
connect (lineEdit, SIGNAL (textChanged (const QString &)),
this, SLOT (on_lineEdit_textChanged ()));
同样的,在构造函数中,我们设置了一个验证器(validator)来限制输入的范围。Qt
有三个内置的验证器,他们分别是:QIntValidator,QDoubleValidator 和 QRegExpvalidator。
这里我们使用了一个 QRegExpValidator 验证器,其规则表达式为"[A-Za-z] [1-9] [0-9] {0, 2}"。
它表示:允许一个大写或者小写的字母,这个字母后面紧跟着一个取值范围为[1-9]的数字,
再紧跟位数取值范围为[0-2] (即不可超过 100)的数字,这个数字每一位的取值范围是[0-9]。
我们把一个 this 指针传递给验证器 QRegExpValidator 的构造函数,这样我们使它成为
对象 GoToCellDialog 的一个子对象。通过这样,我们就不必在后面花费心思考虑对于这个
QRegExpValidator 的删除——它会在它的父对象被删除时自动销毁。
Qt 的父子机制是在 QObject 中实现的。当我们从父对象中产生一个子对象时(可以是
一个 widget,一个 validator 或者任何形式),父对象就把这个对象加入到它的子对象链表中。
当父对象被删除时,它会遍历这个链表并销毁每一个子对象。这些子对象然后再继续销毁属
于它们的子对象。如此的循环,直到没有对象剩下为止。
这种机制极大地简化了程序的内存管理任务,减少了内存泄露的危险。当我们删除父
窗口时,子窗口不光从内存中消失,从屏幕上也会消失。
在构造函数的最后,我们将按钮 OK 和 QDialog 的槽 accept()相连;将按钮 Cancel 和
槽 reject()相连。这两个槽都会关闭窗口,但是 accept()将对话框的结果传给 QDialog::Accepted
(实际上是整数 1);而 reject()则将对话框的结果传给 QDialog::Rejected (实际上是整数 0) 。
当我们使用这个对话框的时候,我们可以查看它的返回值来确认用户是否按下了按钮 OK;
程序运行是否正确。
槽 on_lineEdit_textChanged()的作用是:根据字符编辑框中的内容是否合法来决定按钮
OK 可见或不可见。函数 QLineEdit::hasAcceptableInput()使用了我们在构造函数中看到过的
验证器。
以上我们已经完成了对话框的所有工作。现在我们重写 main.cpp:
#include <QApplication>
#include "gotocelldialog.h"
int main (int argc, char *argv [])
{
QApplication app (argc, argv);
GoToCellDialog *dialog = new GoToCellDialog;
dialog->show ();
return app.exec ();
}
重新编译并执行程序。我们的程序可以正常工作了。
使用 Qt Designer 的好处之一是把程序员从必须修改他们的代码来适应界面的改变这
个烦恼中解脱出来。如果你使用纯粹的代码来编写界面,改变界面设计将耗费大量的时间。
应用 Qt Designer 将不会有任何时间上的损失: uic 工具会根据界面的改变自动更新代码。对
话框用户界面被保存为  .ui  文件的格式(基于 XML 的文件格式), 通过派生 uic 工具生成的
类,这种普遍的泛函性得到了实现。
四、  Qmake
1、  qmake 简介
qmake 是 Trolltech 公司创建的用来为不同的平台和编译器书写 Makefile 的工具。
手写 Makefile 是比较困难并且容易出错的,尤其是需要给不同的平台和编译器组合写
几个 Makefile。使用 qmake,开发者创建一个简单的“项目”文件并且运行 qmake 生成适当的
Makefile。qmake 会注意所有的编译器和平台的依赖性,可以把开发人员解放出来,只关心
他们的代码。Trolltech 公司使用 qmake 作为 Qt 库和 Qt 所提供的工具的主要连编工具。
qmake 也注意了 Qt 的特殊需求,可以自动的包含 moc(元对象编译器)和 uic 的连编
规则。
qmake 提供了一个面向项目的系统,通过 qmake 可以控制应用程序,库文件以及其他
程序组件的建立过程。QT 的这个附属程序使开发人员能够控制所用到的资源文件,并能够
在一个单独的文件中简要却富有代表性地描述程序构件过程中的每一步。这个文件就是
*.pro 文件。qmake 将每个工程文件中的信息输入到一个 makefile 文件中,并以此执行程序
编译和链接过程中所必须的命令和动作。
▲描述一个项目(Describing a Project)
项目是由工程文件(*.pro)中的内容来进行描述的。qmake 使用这些信息来生成 makefile
文件,所生成的 makefile 文件中包含了在构建一个项目时所需要使用的所有命令。典型的工
程文件(*.pro)中包括一个代码文件和头文件的列表,普通的配置信息,和其它的一些应用上
的特殊的细节——比如说,一个在链接时所需要的额外库文件的列表,或者是一个额外的包
含路径的列表。
工程文件(*.pro)可以包含许多不同的元素,包括注释,变量声明,内置函数和一些简单
的控制结构。在大多数简单的项目中,只需要在工程文件中说明在编译时所需要的源文件和
头文件,加上一些基本的配置信息就可以了。
完整的工程文件的示例可以在“qmake 指南”中找到,对于工程文件的介绍在“qmake
工程文件”这个章节中,而更多细节方面的描述都在“qmake 参考”中向您展现。
▲构建一个项目(Building a Project)
对于简单的项目来说,只需要在项目的最高级目录中运行 qmake 就可以。这个时候,
qmake 会在默认情况下工作,生成一个 makefile 文件。通过这个文件就可以编译你的项目了,
当然,你也可以运行你熟悉的工作平台上的 make 工具来编译这个项目。
Qmake 还可以用来生成工程文件。在“运用 qmake”这个章节中你可以找到对于 qmake
的命令行模式的详细的介绍。
▲使用预编译头文件(Using Precompiled Headers)
在面对一个大型的项目时,我们可以使用预编译头文件的一些优点来加速编译的过程。
这些特性的详细介绍可以在“使用预编译头文件”这个章节中找到。
2、qmake 工程文件
工程文件中包含了在构建应用程序、库文件或者是界面等程序时, qmake 所需要的所有
的信息。项目中所用到的资源通常是通过一系列的声明来进行说明的,不过 qmake 对于简
单化程序构造的支持使得我们可以在不同的平台和环境下声明不同的构造过程。
1)、工程文件构成(Project File Elements)
Qmake 所用到的工程文件对于简单和相对复杂的编译系统都能够支持。简单的工程文
件使用直接声明的方式,直接定义变量来说明在项目中用到的源文件和头文件。而复杂的项
目则可能使用控制流程结构来更好的保证构造的过程。
下面的内容说明在工程文件中可能用到的不同形式的元素。
▲变量(Variables):
在一个工程文件中,变量用来跟踪字符串的链表。在简单的项目中,这些变量使得 qmake
确认所用到的配置选择或者提供项目构造过程中用到的文件名和路径。
Qmake 会在每一个工程文件中搜索特定的变量,根据这些变量的内容决定如何书写
makefile 文件。例如,变量“HEADERS”和“SOURCES”的内容就能提示 qmake 在工程文
件的当前目录下寻找这些头文件和源文件。
变量也可以用来临时保存某些值作为中转。已经存在的值的列表在出现新的值的情况下
将被改写或者替代。
下面这一行表明了一系列的值是如何被赋给变量的:
HEADERS = mainwindow.h paintwidget.h
只有在同一行之内写出的值才能赋给变量。如果要将多于一行的值赋给变量,那么行与
行之间需要加上符号:‘  \  ’
保存在变量中的一系列值可以被扩展。下面是例子:
SOURCES = main.cpp mainwindow.cpp \
Paintwigdet.h
CONFIG += qt
“CONFIG”是 qmake 在生成 makefile 时要用到的另一个特殊的变量。在本章节稍后的“普
通配置”部分会讨论这个变量。在上面的例子中,qt 将会被加入到变量 CONFIG 已经存在
的值的列表中。
下面是 qmake 承认的变量,并且对这些变量的内容做了简要说明。
变量(Variables)  内容(Contents)
CONFIG  通用的项目配置的选择
DESTDIR  生成的可执行文件或者二进制文件的存放目录
FORMS  一个需要被 uic 处理的 ui 文件的列表
HEADERS  在编译项目时要用到的头文件的列表
QT  有关 qt 特殊的配置选项
RESOURCES  在最终的项目中需要包括的资源文件(.rc 文件)的列表
SOURCES  编译项目时需要用到的源文件的列表
TEMPLATE  项目用到的模板。这里决定了编译过程输出的结果是应用程序,库文件
还是一个程序界面
在变量名前加上符号  $$  可以把这个变量的值读出来。利用这个功能可以将一个变量的
值赋给另外一个。例如:   
TEMP_SOURCES = $$SOURCES
操作符$$被广泛运用于处理字符串和值的链表的内置函数中。这些特性将在“qmake
高级运用”这个章节详细介绍。
一般地,变量在包含一些值的时候,这些值用空格隔开。但是,总有些时候,一个值或
文件名中会有空格,这个时候,需要用双引号将这个值包括起来。例如:
DEST =  “Program Files”
被引用的字符串将会被认为是一个单独的文件名或者值。
▲注释(Comments)
在工程文件中可以有注释。以符号#开头的语句直至这一行的结束,都被认为是
注释。例如:
#Comments usually start at the beginning of a line, but they
#can also follow other content on the same line.
如果在变量中存在#字符,则必须使用内置变量 LITERAL_HASH,更多的信息位于“变
量参考”这一章节中。
▲内置函数和控制流程(Built-in Functions and Control Flow)
Qmake 提供了大量的内置函数,使用他们可以处理变量的内容。在简单的工程文件中最
常用到的函数是 include 函数。Include 函数把一个文件名作为自己的自变量。在 include 函
数出现的地方,它的变量的内容(其实就是一些文件名)就将被包括在工程文件中。 Include
函数经常被用来包含其他的工程文件。例如:
Include (other.pro)
Qmake 也支持条件结构。这看起来就像是编程语句一样:
Win32 {
SOURCES += paintwigdet_win.cpp
}
被大括号包起来的内容,只有在条件为真的时候才会起作用。在上面的例子中,这个特
殊的变量(win32)必须被设置:这通常自动出现在 windows 工作环境下,但是在其他的平
台上通过设置 qmake 运行的模式为“win32 command line”也可以起到相同的作用。需要注
意的是,左边的大括号必须和条件判断语句位于同一行。
简单的循环是通过函数的内置功能反复写值的列表来实现的。下面的代码实现了一个功
能:如果目录存在,则将这个目录加入到变量 SUBDIRS 中。
EXTRAS = handlers tests docs
for (dir, EXTRAS) {
exists ($$dir) {
SUBDIRS += $$dir
}
}
更多对于变量的复杂操作一般需要用到一些内置函数(例如 find, unique 和  count)提
供的循环。这些函数和其他的许多函数可以用来操作字符串和路径名,支持用户输入,并且
调用其他额外的工具。在“qmake 高级运用”这一章节中可以找到一个列表,那里介绍了所
有可用的函数。
2)  项目模板(Project Templates)
变量 TEMPLATE 用来定义项目将被编译成那种类型。如果在工程文件中没有定义这个
变量,那么 qmake 将会默认编译该项目生成一个应用程序,并生成一个适合的 makefile 文
件(或者其他功能相同的文件)。
下面的表格说明了可以使用的项目类型和 qmake 将会生成的文件。
模板(Template)  Qmake 生成的文件(Description of qmake output)
app(default)  生成一个用于应用程序的 makefile
lib  生成一个用于库文件的 makefile
subdirs  生成一个通过变量 SUBDIRS 定义的子目录的编译规则。每一个子目录
都必须包含自己的工程文件
vcapp  生成一个 Visual Studio 的应用程序工程文件
vclib  生成一个 Visual Studio 的库文件工程文件
请查阅“qmake 指南”,获得更多的关于书写应用 app 和 lib 模板的工程文件的信息。
当模板的值被设为 subdirs 时,qmake 将会生成一个 makefile,检查每一个声明的子目
录,执行子目录中找到的工程文件,生成一个新的 makefile 文件,并据此执行当前工作平台
下的 make 工具。
在 subdirs 模板下,唯一被承认的系统变量是 SUBDIRS。这个变量包含了一个所有拥有
要被处理的工程文件的子目录的列表。需要注意的是,每一个子目录的工程文件名称必须和
该子目录的名称相同,这样 qmake 才能准确地寻找到这个工程文件。例如,如果子目录名
称是 myapp,那么这个目录下的工程文件的名称应该是 myapp.pro。
3)、通用构造(General Configuration)
变量 CONFIG 说明了编译的选项和特点以及库文件如何连接。变量 CONFIG 可以加入
任何的值,但是 qmake 在内置的情况下只承认下面表格中的选择。下面的选项用来控制项
目的编译方式。
选项(Option)  介绍(Description)
release  项目将在 release 模式下被编译。如果 debug 模式也被声明,则此选项被
忽略
debug  项目将在 debug 模式下被编译。
debug_and_release  项目将同时在 release 和 debug 模式下编译
build_all  如果选项 debug_and_release 被选择,项目将在默认情况下同时在两种模
式下编译。
ordered  当用到模板 subdirs 的时候,这个选项将要求所有的子目录按照它们被
给定的顺序依次处理。
warn_on  编译器将尽可能多的输出警告信息。如果 warn_off 被选择,此项被忽略。
warn_off  编译器将尽可能少的输出警告信息。
当选项 debug_and_release 被选择时,使得项目将会在两种模式下分别建立。在这种情
况下, qmake 所产生的 makefile 文件包含了一个编译所有版本的规则,这个规则可以用以下
的方式被调用:
Make all
将选项 build_all 加入到变量 CONFIG 中可以使得这个规则被默认调用,安装目标将会
同时生成 debug 和 release 模式。
需要注意的是在变量 CONFIG 中所有的选项也可以成为一个条件范围。通过调用内置
函数 CONFIG(),可以查看当前的 config 选项。下面的例子说明了函数 CONFIG()将一个
选项作为一个范围条件来检验选项 openg1 是否被使用:
CONFIG (opengl) {
message (Building with OpenGL support.)
} else {
message (OpenGL support is not available.)
}
通过这种调用可以使得在 release 和 debug 模式下运用不同的选项,在“高级应用”这
一章节中有详细的探讨。
下一个表格中的选项定义了项目将被编译成哪种类型。注意,有些选项只有在特定的平
台上才有意义,在别的平台上,这些选项没有任何意义。
选项(Option)   介绍(Description)
Qt  X 项目将被编译成一个 qt 应用程序并和 qt 的库文件相连。你可以使用变量
QT 来控制任何应用程序所需要的 qt 附加的部件。
Thread  项目是一个多线程的应用程序。
X11  项目是一个 x11 应用程序或者是库文件。
当运用模板 application 或者 library 时,有更多特殊的构造选项可以使用,以优化编译
过程。这些细节在“qmake 普通项目”这一章节中有详细表述。
例如,如果应用程序使用 qt 库文件并且你想在 debug 模式下编译一个多线程的程序,
那么你的工程文件应该包括下面这一个语句:
CONFIG += qt    thread    debug
注意,这里使用了符号“+=”而不是“=”,否则 qmake 将无法使用 qt 的构造来判断你
的项目所需要的设定。
4)、声明 qt 库
如果变量 CONFIG 包括 qt 这个值,那么 qmake 就将支持 qt 应用。这使得优化应用程序
中用到的 qt 组件成为可能。通过设置变量 QT 的值,我们可以声明需要用到的 qt 扩展组件。
通过下面的示例我们使 qt 的 XML 和 network 这两个组件成为可用:
CONFIG += qt
QT += network xml
注意:变量 QT 在默认情况下包括内核组件和图形用户界面组件,所以上面的声明将
XML 和 network 这两个组件加入到 QT 的默认列表中。下面的声明则会忽略默认组件,这
将导致编译应用程序代码的时候出现错误:
QT = network xml  # this will omit the core and GUI modules.
如果你在建立工程时不想加入 GUI 内核,则需要用到操作符“-=”。因为变量 QT 在
默认情况下包括内核组件和图形用户界面组件,所以下面的声明将会使这个项目在最小化的
情况下编译:
QT -= GUI # only the core module is used.
下表显示了变量 QT 可以使用的值以及这些值的代表意义:
选项(Option)  特征(Features)
core(默认包含)    QT 内核组件
GUI(默认包含)    QT 用户图形界面组件
network    QT 网络组件
opengl    QT 的 opengl 组件
sql    QT 的数据库 sql 组件
svg    QT 的 svg 组件
xml    QT 的 XML 组件
qt3support    QT3 的支持组件
需要注意的是,将 opengl 选项加入到变量 QT 中和将这个选项加入到变量 CONFIG 的
效果是相同的。因此,对于 QT 的应用程序,没有必要同时在变量 CONFIG 和 QT 中添加选
项 opengl。
5)、构造特征(Configuration Features)
Qmake 可以用在特征文件(.prf)中声明的额外构造特征来设置。这些额外的特征通常
用来对编译过程中经常要使用的工具提供支持。想要为编译过程添加特征,就需要把这个特
征名称(特征文件名的词干)添加到变量 CONFIG 中。
例如,qmake 可以在编译过程中使用 pkg-config 所支持的额外的库(例如 D-BUS 库和
ogg 库)。下面是声明语句:
CONFIG += link_pkgconfig
PKGCONFIG += ogg dbus -1
在“qmake 高级应用”这一章节中的“添加新的构造特征”部分,可以找到更多的信息。
6)、声明其他的库
如果在项目中使用了其他 qt 所支持的库,你需要在工程文件中进行声明。
Qmake 在编译的时候要搜寻并连接的库文件和特别的库的列表会被添加到变量 LIBS 当
中。这些库的路径可以是自己给出的,或者是一个 UNIX 格式的解释。
下面的语句说明了一个库是怎样被声明的:
LIBS += -L/usr/locsl/lib   -lmath
通过变量 INCLUDEPATH,也可以用类似的方法声明需要包含的头文件。
下面的例子说明了如何添加库文件搜索时的路径:
INCLUDEPATH = c : /msdev/include    d: /stl/include
3、运行 qmake(Running qmake)
当 qmake 在命令行中按照声明的不同的选项运行时,它的行为是可以被定制的。这可
以优化编译过程,提供有用的诊断信息,还可以用来指定项目的目标平台。
1) 、命令行选项(Command-Line Options)
▲句法(Syntax)
运行 qmake 的句法如下所示:
Qmake [mode] [options] files
Qmake 支持两种不同的操作:在默认的模式下,qmake 将会使用工程文件中的定义来
生成 makefile 文件,不过你也可以运用 qmake 自己来生成工程文件。如果你希望准确的设
定模式,那么必须在所有的选项之前声明它。Qmake 可以有以下两种模式:
1)  -makefile
Qmake 的输出将会是一个 makefile 文件。
2)  -project
Qmake 的输出将会是一个工程文件。
下面将要讲述的选项用来声明普通和特殊模式的设定。只在 makefile 模式下起作用的选
项在“makefile 模式的选项”下讲述;可以影响工程文件生成的选项在“工程文件模式”下
讲述。
如果文件代表一个或者多个工程文件的列表,那么多个工程文件的名称中间用空格隔
开。
▲  选项(Options)
在命令行模式下运行 qmake 有很多可供选择的选项,用来规范编译过程和更改目前工
作平台的默认设置。下面这些基础的选项可以提供有用的信息,声明 qmake 生成输出文件
的地址,并控制将要输出到控制台的调试信息的等级。
☆  help  :  qmake 将核对这些特征,并给出一些有用的建议。
☆  -o file  :qmake 将会直接把输出结果写到给出的文件中。如果这个选项没有被声
明,qmake 将会根据其运行模式,为输出文件寻找一个适合的名称。如果’-’被
声明,则 qmake 的输出将会被放到标准输出队列中。
☆  -d  :  qmake 将会输出调试信息。
当项目包括多个子目录,并且需要在不同的目标平台上编译时,可以使用以下的选项
来运行 qmake,在每一个工程文件中设置与目标平台相对应的变量。
☆  –UNIX  : qmake 将会以 UNIX 模式运行。在这种模式下, UNIX 的文件名称和路
径转换将会被应用,对于 UNIX 的附加测试(作为一个范围)成为可用。这种模式是
所有类 UNIX 操作系统下的默认模式。
☆  –macx  :qmake 将会在 MAC OS X  模式下运行。在这种模式下,UNIX 的文件
名称和路径转换将会被应用,对于 macx 的附加测试成为可用。这种模式是 MAC OS
下的默认模式。
☆  –win32  :qmake 将会在 WIN32 模式下运行。在这种模式下,WINDOWS 的文件
名称和路径转换将会被使用,对于 WIN32 的附加测试成为可用。这种模式是
WINDOWS 操作系统下的默认模式。
警告信息的等级也是可以被调整的,这可以帮助你寻找到你的工程文件中的错误。以
下是相关选项。
☆  -Wall  :qmake 将会报告所有发现的警告信息。
☆  –Wnone  :qmake 不会输出任何警告信息。
☆  –Wparser  :qmake 将只输出文字解析的警告信息。这会提醒你注意工程文件中普
通的文字缺陷和在工程文件解析过程中有可能出现的问题。
☆  –Wlogic  : qmake 将会对工程文件的文字缺陷和潜在的问题提出警告。例如, qmake
将会报告一个文件是否被多次重复的加入到一个文件队列中,或者一个文件不能被找
到。
2) 、Makefile 文件模式的选项(Makefile Mode Options)
句法:
qmake –makefile [options] files
在 makefile 模式下,qmake 将会生成一个在编译项目时要使用的 makefile 文件。这种
模式下,可以附加地使用下面的选项来决定工程文件生成的方式。
☆  -after  :qmake 会执行在特定文件后出现在命令行中的命令。
☆  -nocache  :qmake 会忽略后缀名为“.qmake.cache”的文件。
☆  -nodepend  :qmake 不生成任何依赖信息。
☆  -cache file  :qmake 使用给出的文件作为缓存文件,忽略其余任何后缀名为
“.qmake.cache”的文件。
☆  –spec spec  : qmake 使用给出的 spec 作为平台和编译信息的路径,变量
QMAKESPEC 的值将被忽略。
也可以在命令行中为 qmake 指派任务:这些任务将会在所有声明文件被处理之前执行。
例如:
qmake –makefile –unix -o Makefile  “CONFIG+=test”  test.pro
执行此命令将会根据工程文件 “test.pro”、用 UNIX 路径产生一个 Makefile 文件。不过,
很多选项都不用书写,因为他们都是默认值。因此,上面的命令在 UNIX 环境下可以简化为:
qmake  “CONFIG+=test”  test.pro
如果你确定需要在处理某个特定文件之后再处理你的变量,那么也许你要用到 pass 选
项。当这个选项被声明之后,所有位于这个选项之后的任务都将会被推迟到这些特定文件处
理完毕之后才开始执行。
3)、项目模式选项(Project Mode Options)
句法:
qmake  –project [options] files
在项目模式下,qmake 将会生成一个工程文件。在这种模式中,你可以应用以下附加
的选项:
☆  –r  :qmake 将会在给出的目录中循环查找。
☆  –nopwd  :qmake 不在当前工作目录下寻找源代码,而是只使用特别指出的文件。
在这种模式下,文件的自变量可以是一个文件或者目录的列表。如果一个目录被声明,
它将被包含在变量 DEPENDPATH 中,和它们相关的代码会被包含在生成的工程文件中。如
果指定了一个文件,则其将被附加到一个合适的变量上(这取决于文件的扩展名)。例如: .ui
文件会被加入到变量 FORMS 上,而.cpp 文件会被加入到变量 SOURCES 中。
这种模式下同样可以在命令行中分配任务。如果你是这么做的,那么这些分配的
任务将会被放置到所产生的工程文件的最后。
4、开始使用 qmake
1)、创建一个工程文件
qmake 使用储存在项目(.pro)文件中的信息来决定 Makefile 文件中该生成什么。
一个基本的工程文件包含关于应用程序的信息,比如,编译应用程序需要哪些文件,并
且使用哪些配置设置。
这里是一个简单的示例工程文件:
SOURCES = hello.cpp
HEADERS = hello.h
CONFIG += qt warn_on release
我们将会提供简要解释:
SOURCES = hello.cpp
这一行指定了实现应用程序的源程序文件。在这个例子中,恰好只有一个文件,
hello.cpp。大部分应用程序需要多个文件,这种情况下可以把文件列在一行中,以空格分隔,
就像这样:
SOURCES = hello.cpp main.cpp
另一种方式,每一个文件可以被列在一个分开的行里面,通过反斜线另起一行,就像这
样:
SOURCES = hello.cpp \
main.cpp
一个更冗长的方法是单独地列出每一个文件,就像这样:
SOURCES += hello.cpp
SOURCES += main.cpp
这种方法中使用“+=”比“=”更安全,因为它只是向已有的列表中添加新的文件,而不是
替换整个列表。
HEADERS 这一行中通常用来指定为这个应用程序创建的头文件,举例来说:
HEADERS += hello.h
列出源文件的任何一个方法对头文件也都适用。
CONFIG 这一行是用来告诉 qmake 关于应用程序的配置信息。
CONFIG += qt warn_on release
在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使
用“=”那样替换已经指定的所有选项是更安全的。
CONFIG 一行中的 qt 部分告诉 qmake 这个应用程序是使用 Qt 来连编的。这也就是说
qmake 在连接和为编译添加所需的包含路径的时候会考虑到 Qt 库的。
CONFIG 一行中的 warn_on 部分告诉 qmake 要把编译器设置为输出警告信息的。
CONFIG 一行中的 release 部分告诉 qmake 应用程序必须被连编为一个发布的应用程序。
在开发过程中,程序员也可以使用 debug 来替换 release,稍后会讨论这里的。
工程文件就是纯文本(比如,可以使用像记事本、vim 和 xemacs 这些编辑器)并且必
须存为“.pro”扩展名。应用程序的执行文件的名称必须和工程文件的名称一样,但是扩展
名是跟着平台而改变的。举例来说,一个叫做“hello.pro”的工程文件将会在 Windows 下生
成“hello.exe”,而在 Unix 下生成“hello”。
2)、生成 Makefile
当你已经创建好你的工程文件,生成 Makefile 就很容易了,你所要做的就是先到你所
生成的工程文件那里然后输入:
Makefile 可以像这样由“.pro”文件生成:
qmake -o Makefile hello.pro
对于 Visual Studio 的用户,qmake 也可以生成“.dsp”文件,例如:
qmake -t vcapp -o hello.dsp hello.pro
3)  、使应用程序可以调试
应用程序的发布版本不包含任何调试符号或者其它调试信息。在开发过程中,生成一个
含有相关信息的应用程序的调试版本是很有用处的。通过在工程文件的 CONFIG 变量中添
加“debug”就可以很简单地实现。
例如:
CONFIG += qt debug
HEADERS += hello.h
SOURCES += hello.cpp
SOURCES += main.cpp
像前面一样使用 qmake 来生成一个 Makefile 并且你就能够调试你的应用程序了。
4)、添加特定平台的源文件
在编了几个小时的程序之后,你也许开始为你的应用程序编写与平台相关的部分,并且
决定根据平台的不同编写不同的代码。所以现在你有两个源文件要包含到你的工程文件中-
hello_win.cpp 和 hello_x11.cpp。我们不能仅仅把这两个文件放到 SOURCES 变量中,因为那
样的话会把这两个文件都加到 Makefile 中。所以我们在这里需要做的是根据 qmake 所运行
的平台来使用相应的作用域来进行处理。
为 Windows 平台添加的依赖平台的文件的简单的作用域看起来就像这样:
win32 {
SOURCES += hello_win.cpp
}
所以如果 qmake 运行在 Windows 上的时候,它就会把 hello_win.cpp 添加到源文件列表
中。如果 qmake 运行在其它平台上的时候,它会很简单地把这部分忽略。现在接下来我们
要做的就是添加一个 X11 依赖文件的作用域。
当你做完了这部分,你的工程文件应该和这样差不多:
CONFIG += qt debug
HEADERS += hello.h
SOURCES += hello.cpp
SOURCES += main.cpp
win32 {
SOURCES += hello_win.cpp
}
x11 {
SOURCES += hello_x11.cpp
}
像前面一样使用 qmake 来生成 Makefile。
5)、如果一个文件不存在,停止 qmake
如果某一个文件不存在的时候,你也许不想生成一个 Makefile。我们可以通过使用 exists()
函数来检查一个文件是否存在。我们可以通过使用 error()函数把正在运行的 qmake 停下来。
这和作用域的工作方式一样。只要很简单地用这个函数来替换作用域条件。对 main.cpp 文
件的检查就像这样:
!exists (main.cpp) {
error (“No main.cpp file found”)
}
“!”用来否定这个测试,比如,如果文件存在,exists( main.cpp )是真,如果文件不存
在,!exists( main.cpp )是真。
CONFIG += qt debug
HEADERS += hello.h
SOURCES += hello.cpp
SOURCES += main.cpp
win32 {
SOURCES += hello_win.cpp
}
x11 {
SOURCES += hello_x11.cpp
}
!exists (main.cpp) {
error (“No main.cpp file found”)
}
像前面一样使用 qmake 来生成 Makefile。如果你临时改变 main.cpp 的名称,你会看到
信息,并且 qmake 会停止处理。
6)、检查多于一个的条件
假设你使用 Windows 并且当你在命令行运行你的应用程序的时候你想能够看到
qDebug()语句。除非在连编你的程序的时候使用 console 设置,你不会看到输出。我们可以
很容易地把 console 添加到 CONFIG 行中,这样在 Windows 下,Makefile 就会有这个设置。
但是如果告诉你我们只是想在当我们的应用程序运行在 Windows 下并且当 debug 已经在
CONFIG 行中的时候,添加 console。这需要两个嵌套的作用域;只要生成一个作用域,然
后在它里面再生成另一个。把设置放在最里面的作用域里,就像这样:
win32 {
debug {
CONFIG += console
}
}
嵌套的作用域可以使用冒号连接起来,所以最终的工程文件看起来像这样:
CONFIG += qt debug
HEADERS += hello.h
SOURCES += hello.cpp
SOURCES += main.cpp
win32 {
SOURCES += hello_win.cpp
}
x11 {
SOURCES += hello_x11.cpp
}
!exists (main.cpp) {
error (“No main.cpp file found”)
}
win32: debug {
CONFIG += console
}
五、  Qt Linguist
1、  概述
Qt 提供了比较好的语言翻译功能,在一个国际化软件内有大量显示文本需要翻译成本
地语言,例如:窗口标题、菜单项、弹出帮助文本、按钮标示等等。
为此,在开发过程中我们必须遵循下列步骤:
1)  把程序代码中需要用多语言显示的文本用下列形式引用
QString    strHello = tr ("Hello world!");
2)  在 Qt 工程文件中加入下列条目
TRANSLATIONS = tr/hellotr_la.ts
3) 、  使用 lupdate 工具生成 tr/hellotr_la.ts 文件,此文件中收集了本工程代码中全部采用 tr()
引用的文本
4) 、  使用 linguist 编辑 tr/hellotr_la.ts 文件,并翻译相应文本为所需要的语言
5) 、  使用 lrelease 生成 tr/hellotr_la.qm 语言文件
6) 、  在程序入口出,使用 QTranslator 加载生成的 tr/hellotr_la.qm 翻译文件
随后编译该工程,生成可执行程序,运行,就可以看到界面显示文本已经翻译成相应
的语言。
2、一个例子
下面我们使用一个简单的例子程序演示显示文本的引用方法。
/*---------main.cpp -------------------*/
#include <QApplication>
#include <QPushButton>
#include <QTranslator>
int main (int argc, char *argv [])
{
QApplication app (argc, argv);
QTranslator translator;
translator.load ("tr/hellotr_la");
app.installTranslator (&translator);
QPushButton hello (QPushButton::tr ("Hello world!"));
hello.resize (100, 30);
hello.show ();
return app.exec ();
}
1)、在 Qt 的工程文件中加入下列条目
每个 Qt 工程都有一个  .pro 文件,类似于 VC 的工程文件。在这个文件里添加下列条目(红
色部分),来标志这个工程里有那些翻译文件。
######################################################################
# trdemo.pro #
# Automatically generated by qmake (2.01a)    9 14:03:16 2007
######################################################################
TEMPLATE = app
TARGET = trdemo
DEPENDPATH += .
INCLUDEPATH    += .
# Input
SOURCES += main.cpp
TRANSLATIONS = tr/hellotr_la.ts
2)、使用 lupdate 工具生成 tr/hellotr_la.ts 文件
Lupdate 是一个简单的命令行工具,它读取 trdemo.pro 文件,在工程中源代码文件、头
文件、 UI 文件中的搜集所有需要翻译的字符串。然后生成或者更新在 trdemo.pro 中列出的.ts
文件。按下图所示的命令生成 tr/hellotr_la.ts 文件
图  1
3)、使用 linguist 编辑 tr/hellotr_la.ts 文件
运行 linguist 工具,打开 tr/hellotr_la.ts。如下图显示,选择需要翻译的字串,按所需要
的语言进行翻译,然后保存。
图  2
4)、使用 lrelease 生成 tr/hellotr_la.qm 语言文件
Lrelease 是另外一个简单的命令行工具,它读取.pro 文件并生成程序使用的.qm 文件。
每个在.pro 列出的.ts 文件都对应一个.qm 文件。具体操作见图 1 中 lrelease 命令。
5)、使用 QTranslator 加载生成的 tr/hellotr_la.qm 翻译文件
在程序入口(一般指 main()内),对应每个.qm 文件,声明体格 QTranslator  对象,分别
加载该.qm 文件。见主程序(粉红色部分)。
如果需要生成多个语言版本,可重复上述步骤,分别生成相应的.ts 和.qm 文件。然后针
对每种语言的安装环境,分别加载相应的.qm 文件。
6)、运行程序
完成上述步骤,就可以运行程序了。我们分别看看,  tr/目录下有 hellotr_la.qm 文件和
没有 hellotr_la.qm 文件的运行结果(图 3、图 4):
图 3  没有翻译文件
图 4  有翻译文件
附录  QT 的安装
在红旗 Linux 桌面版 4.1 中安装 Qt 4.0.1
要安装 QT,首先要有 QT 的安装包。要想免费的使用 QT,就要下载其开源版。QT 开
源版是遵守 GPL 和 QPL 的。如果想要使用 QT 库来写商业软件,需要使用 QT 的商业版。
这里讲解的是 QT 开源版。
QT 开源版可以从这里获得:http://www.trolltech.com/download/opensource.html。
1、为避免麻烦,请用 root 用户登录。
2、下载完将近 20M 的 QT 源码包后将其解压。解压用在图形界面就能很好的进行。右
键点击下载的文件(qt-x11-opensource-src-4.0.1.tar.gz),选择“解压”,在弹出的窗口中点击“确
定”按钮。或者在终端下键入“tar –zxvf t-x11-opensource-src-4.0.1.tar.gz”并回车。
这 样 会 在 该 文 件 的 同 一 目 录 生 成 qt-x11-opensource-src-4.0.1 目 录 。 将
qt-x11-opensource-src-4.0.1 改名为 qt-4.0.1
3、将 qt-4.0.1 目录复制到/tmp 目录下。
4、进入 qt-4.0.1 目录,点击菜单“工具-->打开终端”。这样可以打开一个终端,并且当
前目录为/tmp/qt-4.0.1
5、在终端中键入如下命令“./configure”,经过两分钟左右,完成对 QT 库的设置,生成
makefile。之后程序会提示使用“gmake    install”安装。
6、按照提示,在终端键入“gmake install”。键入“yes”并回车同意使用 GPL、QPL 协议
(QT 默认安装到/usr/local/Trolltech/Qt-4.0.1,如果想更改安装目录,使用-prefix 参数,具体
如何使用,请参照其他相关文档)。
7 、 若 只 想 root 用 户 使 用 QT , 则 打 开 “/root/.bash_profile” 文 件 , 在 其 中 加 入
“PATH=/usr/local/Trolltech/Qt-4.0.1/bin:$PATH”和“export PATH”两行。重新登录 root 用户,
设置生效,现在你可以使用 QT 了。在终端中键入“qtdemo”可以打开 QT 的演示程序,运行
“designer”可以打开 QT Designer,开始你的 QT 界面设计之旅了。
如果想以后新建的用户也可以使用 QT,则在/etc/skel/.bash_profile 文件中加入
“PATH=/usr/local/Trolltech/Qt-4.0.1/bin:$PATH”和“export PATH”两行。所加两行要视你的 QT
安装路径而定。
QT/Windows OpenSource 版本的安装
1、  下载安装文件
首先去官方网站下载 QT OpenSource  (以下简称 QT ),现在的最新版本是 4.0.1 ,
下载连接:     http://www.trolltech.com/download/qt/windows.html
官方网站申明 QT OpenSource 版本只支持 MinGW 编译器,所以在安装 QT 之前最好
先安装 MinGW 。不事先安装也可以,  QT 安装时会让你指定 MinGW 的安装位置,如果未安
装,则 QT 的安装程序会引导你进行 MinGW 的网络安装。不过,我还是建议不要使用这种
方式,因为网络安装 MinGW 的速度实在是慢,而且经常下载一半就失去连接。不支持断点
续传,让你前功尽弃。
下载 MinGW 的安装程序,也花了我很大的功夫。使用官方网站 http://www.mingw.org/
提供的安装文件 MinGW-3.1.0-1.exe 之后,在 QT 安装时总是报告无法找到 MinGW 编译器
的错误,估计是缺失了什么文件,到现在也没搞明白。解决方法就是安装 Dev-cpp ,我安
装的是 devcpp-4.9.9.2_setup.exe ,里面包含了 MinGW 编译器。  Dev-cpp 的官方网站是
http://www.bloodshed.net/ ,可是我从来没成功登陆过。
2、  执行安装文件
先执行 devcpp-4.9.9.2_setup.exe 文件。其中,需要注意的是 [ 组件选择 ] 对话框。
在[组件选择]对话框中,请勾选上[Mingw compiler system]。
再执行 qt-win-opensource-4.0.1-mingw.exe 文件。其中,安装程序会让你指定已安
装的 MinGW 编译器的位置,如果事先没有安装,则可以在此时选择网络安装 MinGW。
另外,安装过程中要记得选择[设置环境变量]
Qt Designer 是一个非常好用的界面编辑工具,它生成窗体文件的后缀名是.ui,最好选择
将.ui 文件和 Qt Designer 程序关联起来。
3、  后续工作
运行:[开始]>[程序]>[Qt by Trolltech v4.0.1 (OpenSource)]>[Qt 4.0.1 (Build Debug
Libraries)],[Qt 4.0.1 (Build Debug Libraries)]安装 QT 的 Debug 库。
QT4 和 VS2005 的整合安装
1.下载源码包。
2.解压到 C:\Qt\4.2-msvc2005\    (如果整合 Visual Studio 2005)
3.下载 acs_4.2.2-patch1.zip 并解压到 C:\Qt\4.2-msvc2005\中。,
4.修改 C:\Program Files\Microsoft Visual Studio 8 Express\Common7\Tools\vsvars.bat 文件。
代码如下:
@SET VSINSTALLDIR=c:\Program Files\Microsoft Visual Studio 8
@SET VCINSTALLDIR=c:\Program Files\Microsoft Visual Studio 8\VC
@SET FrameworkDir=c:\WINDOWS\Microsoft.NET\Framework
@SET FrameworkVersion=v2.0.50727
@SET FrameworkSDKDir=c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0
@if "%VSINSTALLDIR%"=="" goto error_no_VSINSTALLDIR
@if "%VCINSTALLDIR%"=="" goto error_no_VCINSTALLDIR
@echo Setting environment for using Microsoft Visual Studio 2005 x86 tools.
@rem
@rem Root of Visual Studio IDE installed files.
@rem
@set DevEnvDir=c: \Program Files\Microsoft Visual Studio 8\Common7\IDE
@set QTDIR=C:\Qt\qtwin-4.2.2
@set QMAKESPEC=win32-msvc2005
@set PATH=%QTDIR%\bin; c:\Program Files\Microsoft Visual Studio 8\Common7\IDE; c:\Program Files\Microsoft
Visual Studio 8\VC\BIN; c:\Program Files\Microsoft Visual Studio 8\Common7\Tools; c:\Program Files\Microsoft Visual
Studio 8\SDK\v2.0\bin;c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727;c:\Program Files\Microsoft Visual Studio
8\VC\VCPackages; %PATH%
@set INCLUDE=%QTDIR%\include; C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include;
c:\Program Files\Microsoft Visual Studio 8\VC\INCLUDE; %INCLUDE%
@set LIB=%QTDIR%\lib; C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib; c:\Program
Files\Microsoft Visual Studio 8\VC\LIB; c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\lib;%LIB%
@set LIBPATH=c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
@goto end
:error_no_VSINSTALLDIR
@echo ERROR: VSINSTALLDIR variable is not set.
@goto end
:error_no_VCINSTALLDIR
@echo ERROR: VCINSTALLDIR variable is not set.
@goto end
:end
5.  运行程序 -> Microsoft Visual Studio 2005-->Visual Studio Tools&#61664;Visual Studio 2005
Command Prompt。
6.  cd C:\Qt\4.2-msvc2005\  
7.  installpatch42.bat(运行此文件)
8.  qconfigure.bat    msvc2005    –debug-and-release -no-stl
9.  nmake sub-src
10.  nmake
11.  添加环境变量。PATH 中添加 C:\Qt\4.2-msvc2005\bin
12.  设置 QMAKESPEC 变量值为 win32-msvc2005
13.  当编写完一个程序之后,调用 Visual Studio 2005 Command Prompt。
14.  qmake -project -t vcapp -o projectname.pro
15.  qmake
将产生 projectname.vcproj 文件。
15.用 vs2005 打开之后,需要在 project-〉 property-〉 configuration properties-〉 linker-〉
input 添加 Imm32.lib, Ws2_32.lib, winmm.lib
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋| 飞凌嵌入式 ( 冀ICP备12004394号-1 )

GMT+8, 2024-11-22 12:18

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表