1、一、Qt 概述1、关于 QtQt 是 Trolltech 公司的一个产品。Qt 是一个多平台的 C+图形用户界面应用程序框架。它提供给应用程序开发者建立图形用户界面应用程序所需的所有功能。Qt 是完全面向对象的,它很容易扩展,并且允许真正的组件编程。自从 1996 年早些时候,Qt 进入商业领域,它已经成为全世界范围内数千种成功的应用程序的基础。Qt 也是流行的 Linux 桌面环境KDE 的基础。(KDE 是所有主要的 Linux 发行版的一个标准组件) Qt 支持下述平台: MS/Windows - 95、98、NT 4.0、ME、和 2000Unix/X11 - Linux、Sun So
2、laris 、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/嵌入式自由版
3、:是 Qt 为了开发自由软件提供的嵌入式版本。在 GNU 通用公共许可证下,它是免费的。下表是关于 Qt 在 Windows 环境下各个版本的区别。 (Qt 为 Windows 只提供了专业版和企业版,不过自由版本的 Qt 仍然可以在 Windows 环境下使用)组成模块 自由版 专业版 企业版Qt 的基本模块(工具、核心、窗口部件、对话框)与平台无关的 Qt 图形用户界面工具包和应用类X X XQt 设计器可视化的 Qt 图形用户界面的生成器X X X图标视图模块几套图形用户交互操作的可视化效果。X X工作区模块多文档界面(MDI)支持X XOpenGL 三维图形模块在 Qt 中集成了 Op
4、enGLX网络模块一些套接字,TCP,FTP 和异步 DNS 查询并与平台无关的类X画布模块为可视化效果,图表和其它而优化的二维图形领域X表格模块灵活的,可编辑的表格/电子表格XXML 模块通过 SAX 接口和 DOM Level 1 的 XML 解析器XSQL 模块SQL 数据库访问类X3、Qt 的组成Qt 提供了一组范围相当广泛的 C+类库,并包含了几种命令行和图形界面的工具,有效地使用这些工具可以加速开发过程。Qt Designer:Qt 设计器。用来可视化地设计应用程序界面。Qt Linguist:Qt 语言学家。用来翻译应用程序。以此提供对多种语言的支持。Qmake:使用此工具可以由
5、简单的、与平台无关的工程文件来生成编译所需的Makefile。Qt Assistant:关于 Qt 的帮助文件。类似于 MSDN。可以快速地发现你所需要的帮助。moc:元对象编译器。uic:用户界面编译器。在程序编译时被自动调用,通过 ui_*.h 文件生成应用程序界面。qembed:转换数据,比如,将图片转换为 C+代码。4、Qt 的安装安装的过程对于不同的 Qt 平台是不同的。在 Windows 环境下安装 Qt,需要先安装MinGW。MinGW,即 Minimalist GNU For Windows。它是一些头文件和端口库的集合,该集合允许人们在没有第三方动态链接库的情况下使用 GCC
6、(GNU Compiler C)产生 Windows32 程序。 在基本层,MinGW 是一组包含文件和端口库,其功能是允许控制台模式的程序使用微软的标准 C 运行时间库( MSVCRT.DLL), 该库在所有的 NT OS 上有效,在所有的 Windows 95 发行版以上的 Windows OS 有效,使用基本运行时间,你可以使用 GCC 写控制台模式的符合美国标准化组织(ANSI)程序,可以使用微软提供的 C 运行时间扩展。该功能是 Windows32 API 不具备的。下一个组成部分是 w32api 包,它是一组可以使用 Windows32 API 的包含文件和端口库。与基本运行时间相
7、结合,就可以有充分的权利既使用 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
8、1、Hello, Qt!我们以一个非常简单的 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 第 1 行和第 2 行包含了两个类的定义:QApplication 和 QLabel。对于每一个 Qt 的类,都
9、会有一个同名的头文件,头文件里包含了这个类的定义。因此,你如果在程序中使用了一个类的对象,那么在程序中就必须包括这个头文件。第 3 行是程序的入口。几乎在使用 Qt 的所有情况下,main()函数只需要在把控制权转交给 Qt 库之前执行一些初始化,然后 Qt 库通过事件来向程序告知用户的行为。argc 是命令行变量的数量,argv 是命令行变量的数组。这是一个 C/C+特征。它不是 Qt 专有的,无论如何 Qt 需要处理这些变量第 5 行定义了一个 QApplication 对象 App。QApplication 管理了各种各样的应用程序的广泛资源,比如默认的字体和光标。App 的创建需要 a
10、rgc 和 argv 是因为 Qt 支持一些自己的命令行参数。在每一个使用 Qt 的应用程序中都必须使用一个 QApplication 对象,并且在任何 Qt 的窗口系统部件被使用之前创建此对象是必须的。App 在这里被创建并且处理后面的命令行变量(比如在 X 窗口下的-display) 。请注意,所有被 Qt 识别的命令行参数都会从 argv 中被移除(并且 argc 也因此而减少) 。第 6 行创建了一个 QLabel 窗口部件(widget) ,用来显示“Hello,Qt!” 。在 Qt 和 Unix的术语中,一个窗口部件就是用户界面中一个可见的元素,它相当于 Windows 术语中的“
11、容器”加上“控制器” 。按钮(Button) 、菜单(menu) 、滚动条(scroll bars)和框架(frame )都是窗口部件的例子。窗口部件可以包含其它的窗口部件。例如,一个应用程序界面通常就是一个包含了 QMenuBar,一些 QToolBar,一个 QStatusBar 和其它的一些部件的窗口。绝大多数应用程序使用一个 QMainWindow 或者一个 QDialog 作为程序界面,但是Qt 允许任何窗口部件成为窗口。在这个例子中,QLabel 窗口部件就是作为应用程序主窗口的。第 7 行使我们创建的 QLabel 可见。当窗口部件被创建的时候,它总是隐藏的,必须调用 show(
12、)来使它可见。通过这个特点我们可以在显示这些窗口部件之前定制它们,这样就不会出现闪烁的情况。第 8 行就是 main()将控制权交给 Qt。在这里,程序进入了事件循环。事件循环是一种stand-by 的模式,程序会等待用户的动作(比如按下鼠标或者是键盘) 。用户的动作将会产生程序可以做出反应的事件(也被称为“消息” ) 。程序对这些事件的反应通常是执行一个或几个函数。为了简单起见,我们没有在 main()函数的结尾处调用 delete 来删除 QLabel 对象。这种内存泄露是无害的,因为像这样的小程序,在结束时操作系统将会释放程序占用的内存堆。下面我们来编译这个程序。建立一个名为 hello
13、 的目录,在目录下建立一个名为hello.cpp 的 c+源文件,将上面的代码写入文件中。运行“开始程序 Qt by TrolltechQt Command Prompt”。在命令行模式下,切换目录到 hello 下,然后输入命令:qmake project。这个命令将产生一个依赖于工作平台的工程文件(hello.pro)。再输入命令:qmake hello.pro。这个命令通过工程文件产生一个可以在特定工作平台上使用的 makefile。最后输入命令:make 来产生应用程序。运行这个程序,可以得到如下的程序界面。Qt 也支持 XML。我们可以把程序的第 6 行替换成下面的语句:QLabel
14、 *label = new QLabel (“Hello “ “Qt! “);重新编译程序,我们发现界面拥有了简单的 HTML 风格。如下图:2、调用退出第二个例子展示了如何使应用程序对用户的动作进行响应。这个应用程序包括了一个按钮,用户可以点击这个按钮来退出程序。程序代码与上一个程序非常相似,不同之处在于我们使用了一个 QPushButton 来代替 QLabel 作为我们的主窗口,并且我们将一个用户动作(点击一个按钮)和一些程序代码连接起来。1 #include 2 #include 3 int main (int argc, char *argv )4 5 QApplication ap
15、p (argc, argv);6 QPushButton *button = new QPushButton (“Quit“);7 QObject:connect (button, SIGNAL (clicked (),8 9 button-show ();10 return app. exec ();11 Qt 程序的窗口部件发射信号(signals )来指出一个用户的动作或者是状态的变化。在这个例子中,当用户点击这个按钮的时候,QPushButton 就会发射一个信号clicked()。一个信号可以和一个函数(在这种情况下我们把这个函数叫做“槽(slot)”)相连,当信号被发射的时候,和信
16、号相连的槽就会自动执行。在这个例子中,我们把按钮的信号“clicked()”和一个 QApplication 对象的槽“quit()”相连。当按钮被按下的时候,这个程序就退出了。3、窗口布局在本小节,我们将用一个样例来展现如何在窗口中规划各个部件的布局,并学习使用信号和槽来使两个窗口部件同步。这个应用程序要求输入用户的年龄,使用者可以通过一个旋转窗口或者一个滑块窗口来输入。这个应用程序包括三个窗口部件:一个 QSpinBox,一个 QSlider 和一个 QWidget。窗口部件 QWidget 是程序的主窗口。QSpinBox 和 QSlider 被放置在 QWidget 中;他们是QWid
17、get 的子窗口。当然,我们也可以说 QWidget 是 QSpinBox 和 QSlider 的父窗口。QWidget 本身没有父窗口,因为它被当作一个顶级的窗口。QWidget 以及所有它的子类的构造函数都拥有一个参数:QWidget *,这说明了它的父窗口。下面是程序的代码: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-setWin
18、dowTitle (“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 (valueC
19、hanged (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(
20、)来设置窗口的标题栏。第 10 行和第 11 行创建了一个 QSpinBox 和一个 QSlider,第 12 行和第 13 行设置了它们的取值范围(我们假设用户最大也只有 130 岁) 。我们可以将之前创建的 QWidget 对象window 传递给 QSpinBox 和 QSlider 的构造函数,用来说明这两个对象的父窗口,但是这么做并不是必须的。原因是窗口布局系统将会自己指出这一点,自动将 window 设置为父窗口。我们一会儿就可以看到这个特性。在第 14 行和第 17 行,两个对于 QObject:connect()函数的调用确保了旋转窗口和滑块窗口的同步,这样这两个窗口总是显示同
21、样的数值。不管一个窗口对象的数值何时发生变化,它的信号 valueChanged(int)就将被发射,而另一个窗口对象的槽 setValue(int)会接受到这个信号,使得自身的数值与其相等。第 18 行将旋转窗口的数值设置为 50。当这个事件发生的时候,QSpinBox 发射信号valueChanged(int),这个信号包括一个值为 50 的整型参数。这个参数被 QSlider 的槽setValue(int)接受,就会将滑块的值也设置为 50。由于 QSlider 的值被改变,所以 QSlider也会发出一个 valueChanged(int)信号并触发 QSpinBox 的 setVal
22、ue(int)槽。但是在这个时候,QSpinBox 不会再发出任何信号,因为旋转窗口的值已经被设置为 50 了。这将有效地防止信号的无限循环。从第 19 行到第 22 行,我们通过使用一个 layout 管理器对旋转窗口和滑块窗口进行了布局设置。一个布局管理者就是一个根据窗口作用设置其大小和位置的对象。Qt 有三个主要的布局管理类:QHBoxLayout:将窗口部件水平自左至右设置(有些情况下是自右向左) 。QVBoxLayout:将窗口部件垂直自上向下设置。QGridLayout: 以网格形式设置窗口部件。第 22 行我们调用 QWidget:setLayout()函数在对象 window
23、上安装布局管理器。通过这个调用,QSpinBox 和 QSlider 自动成为布局管理器所在窗口的子窗口。现在我们明白为什么在设置子窗口时不用显式地说明父窗口了。可以看到,虽然没有明显地给出任何窗口的大小和位置,但 QSpinBox 和 QSlider 是很完美地被水平依次放置的。这是因为 QHBox-Layout 根据各个窗口的作用自动的为其设置了合理的大小和位置。这个功能使我们从烦琐的界面调整中解放出来,更加专注于功能的实现。Qt 构建用户界面的方法很容易理解,并且有很高的灵活性。Qt 程序员最常用的设计模式是:说明所需要的窗口部件,然后设置这些部件必须的特性。程序员把窗口部件添加到布局管
24、理器中,布局管理器就将自动地设置这些部件的大小和位置。而用户界面的行为是通过连接各个部件(运用信号/槽机制)来实现的。4、派生 QDialog我们现在开始尝试着在 Qt 里只用 C+语言而不是借助界面设计器来完成一个对话框:FIND。我们将这个对话框作为一个类来完成,这么做的好处是我们使这个对话框成为了一个独立的,拥有自己的信号和槽的,设备齐全的组件。程序的源代码由两部分组成:finddialog.h 和 finddialog.cpp。我们从头文件开始。1 #ifndef FINDDIALOG_H2 #define FINDDIALOG_H3 #include 4 class QCheckBo
25、x;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 QDialog9 10 Q_OBJEC
26、T11 public:12 FindDialog (QWidget *parent = 0);在类定义顶端出现了宏:Q_OBJECT。这对于所有定义了信号或槽的类都是必须的。FindDialog 的构造函数拥有 Qt 窗口类的典型特征。参数 parent 声明了父窗口。其默认值是一个空指针,表示这个对话框没有父窗口。13 signals:14 void findNext (const QString 15 void findPrevious (const QString 标记为 signals 的这一段声明了两个信号。当用户点击对话框的“Find ”按钮的时候,信号将被发射。如果选项“Sear
27、ch backward”被选中,对话框将发射消息 findPrevious();相反的,对话框将发射消息 findNext()。关键字“signals”实际上也是一个宏。 C+预处理器将在编译器看到它之前就已经将它转换为了标准的 C+。Qt:CaseSecsitivity 是一个枚举类型。它可以代表值Qt:CaseSensitive 和 Qt:CaseInsensitive。16 private slots:17 void findClicked ();18 void enableFindButton (const QString 19 private:20 QLabel *label;21
28、QLineEdit *lineEdit;22 QCheckBox *caseCheckBox;23 QCheckBox *backwardCheckBox;24 QPushButton *findButton;25 QPushButton *closeButton;26 ;27 #endif在类的 private 字段中,我们声明了两个槽。为了实现这些槽,我们需要访问大多数对话框的子窗口,所以我们在私有字段中保留了这些子窗口的指针。和 signals 一样,关键字 slots 也是一个构造后可以被 C+编译器辩识的宏。对于私有变量,我们使用了它们的类的预定义。这是被编译器所允许的,因为他们都是
29、指针,而我们在头文件中并不需要访问他们,所以编译器并不需要完整的类定义。我们可以在头文件中包含使用这些类所需要的头文件(,,等等) ,但是使用预定义可以在某种程度上加快编译过程。现在我们来看源文件 finddialog.cpp。源文件里包括了 FindDialog 类的实现。1 #include 2 #include “finddialog.h“首先,我们包含了。这个头文件包含了对于 Qt 的 GUI 类的定义。Qt 包括一些模块,每一个模块都依赖于自己的库文件。最重要的几个模块分别是 QtCore, QtGui, QtNetwork, QtOpenGL, QtSpl, QtSvg 和 QtX
30、ml。头文件包括了 QtCore 和 QtGui 模块中所有类的实现。包含此头文件使我们不用单独地列出每个类所需要的头文件。在 filedialog.h 中,我们也可以简单地包含,而不是像我们之前做的那样包括,并且给 QCheckBox,QLabel,QLineEdit,QPushButton 提供预定义。这样似乎简单一些,但是在头文件中包含另外一个大的头文件是一个很坏的方式,尤其是在比较大的应用当中。3 FindDialog:FindDialog (QWidget *parent)4 : QDialog (parent)5 6 label = new QLabel (tr (“Find 7
31、lineEdit = new QLineEdit;8 label-setBuddy (lineEdit);9 caseCheckBox = new QCheckBox (tr (“Match 10 backwardCheckBox = new QCheckBox (tr (“Search 11 findButton = new QPushButton (tr (“12 findButton-setDefault (true);13 findButton-setEnabled (false);14 closeButton = new QPushButton (tr (“Close“);在第 4
32、行,我们将参数 parent 传递给基类的构造函数。然后我们创建子窗口。对于所有的字符串我们都调用函数 tr(),这些字符串被标记为可以翻译成别的语言。函数 tr()在QObject 和每个含有 Q_OBJECT 宏的子类中被定义。将没一个用户可见的字符串都用 TR()包括起来是一个很好的习惯,即使你现在并没有将程序翻译为别的语言的计划。对于 Qt 应用程序的翻译将在后述章节中详细呈现。在字符串中,我们用操作符17 connect (findButton, SIGNAL (clicked (),18 this, SLOT (findClicked ();19 connect (closeBut
33、ton, SIGNAL (clicked (),20 this, SLOT (close ();当字符编辑框中的文字被改变的时候,私有的槽 enableFindButton(const QString 22 topLeftLayout-addWidget (label);23 topLeftLayout-addWidget (lineEdit);24 QVBoxLayout *leftLayout = new QVBoxLayout;25 leftLayout-addLayout (topLeftLayout);26 leftLayout-addWidget (caseCheckBox);27
34、 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 (
35、rightLayout);35 setLayout (mainLayout);接下来,我们使用布局管理器来对子窗口部件进行布局。布局管理器可以包括窗口,也可以包括其它的布局管理器。通过对 QHBoxLayout,QVBoxLayout 和 QGridLayout 这三个布局管理类的嵌套使用,就可以生成非常复杂的对话框了。如上图所示,对于对话框 Find,我们使用了两个 QHBoxLayout 和两个QVBoxLayout。最外层的布局是主要的布局,它在第 35 行被安装并负责响应对话框的全部区域。另外的三个布局是子布局。图的右下方有一个“弹簧”,这是个空白的区域。在按钮 Find 和 Clos
36、e 的下方使用空白是为了保证这些按钮出现在它们所在的布局的上方。一个比较微妙的地方是布局管理类并不是窗口对象。它们从 QLayout 继承而来,而QLayout 从 QObject 继承而来。在上图中,窗口以实线标记,而布局以虚线标记。在一个正在运行的程序当中,布局是不可见的。当子布局被添加到父布局中的时候(代码的第 25 行,33 行和 34 行) ,子布局自动子类化。当主布局被安装的时候(第 35 行) ,它成为了对话框的一个子类,所以在布局当中的所有窗口对象都成为了对话框的子类。本例中各个类的继承层次在下图中表明。36 setWindowTitle (tr (“Find“);37 set
37、FixedHeight (sizeHint ().height ();38 在代码的最后,我们将对话框标题栏的内容设置为“Find” ,然后给窗口设置一个合适的高度。由于这个对话框中没有任何子窗口可能占据多余的垂直空间,函数QWidget:sizeHint()将会返回一个“理想”的大小。考虑一下 FindDialog 的构造过程。由于我们使用了 new 来生成对话框的窗口和布局,看起来我们应该为每一个窗口和布局编写一个析构函数来调用 delete。事实上这不是必须的,因为在父窗口被销毁的时候,Qt 将会自动删除所有的子对象。本例中,所有的窗口和布局都是从 FindDialog 继承而来,在对话
38、框被关闭的时候,这些子对象也会被自动销毁。现在我们看一下对话框的槽:39 void FindDialog:findClicked ()40 41 QString text = lineEdit-text ();42 Qt:CaseSensitivity cs =43 caseCheckBox-isChecked ()? Qt:CaseSensitive44 : Qt:CaseInsensitive;45 if (backwardCheckBox-isChecked () 46 emit findPrevious (text, cs);47 else 48 emit findNext (text
39、, cs);49 50 51 void FindDialog:enableFindButton (const QString 53 当用户按下 Find 按钮时,按钮会发射 findPrevious()或者 findNext()信号,槽findClicked()会被调用。关键字 emit 在 Qt 里很特殊,和其它的 Qt 扩展名一样,它在被传递给标准 C+编译器之前会被 C+预处理器转换。当用户改变字符编辑框中的内容时,槽 enableFindButton()被调用。也就是说,当字符编辑框中有内容时,Find 按钮是可见的;当编辑框中没有内容的时候, Find 按钮不可见。这两个槽被定义之后
40、,我们关于这个对话框的内容就完成了。现在可以创建一个名为main.cpp 的文件来试验一下 FindDialog 窗口。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 现在可以编译并运行这个程序了。如果你的平台支持快捷键,尝试着使用快捷键Alt+W,Alt+C,Alt+B 和 Al
41、t+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 与其它工具包的最不相同的部分。