Qt框架的C++编程

linzx0403

贡献于2012-11-30

字数:41981 关键词: UI开发框架 C/C++ QT

一、 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模块 通过SAX接口和DOM Level 1的XML解析器 X 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 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的类,都会有一个同名的头文件,头文件里包含了这个类的定义。因此,你如果在程序中使用了一个类的对象,那么在程序中就必须包括这个头文件。 第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,一些QToolBar,一个QStatusBar和其它的一些部件的窗口。绝大多数应用程序使用一个QMainWindow或者一个QDialog作为程序界面,但是Qt允许任何窗口部件成为窗口。在这个例子中,QLabel窗口部件就是作为应用程序主窗口的。 第7行使我们创建的QLabel可见。当窗口部件被创建的时候,它总是隐藏的,必须调用show()来使它可见。通过这个特点我们可以在显示这些窗口部件之前定制它们,这样就不会出现闪烁的情况。 第8行就是main()将控制权交给Qt。在这里,程序进入了事件循环。事件循环是一种stand-by的模式,程序会等待用户的动作(比如按下鼠标或者是键盘)。用户的动作将会产生程序可以做出反应的事件(也被称为“消息”)。程序对这些事件的反应通常是执行一个或几个函数。 为了简单起见,我们没有在main()函数的结尾处调用delete来删除QLabel对象。这种内存泄露是无害的,因为像这样的小程序,在结束时操作系统将会释放程序占用的内存堆。 下面我们来编译这个程序。建立一个名为hello的目录,在目录下建立一个名为hello.cpp的c++源文件,将上面的代码写入文件中。 运行“开始à程序àQt by TrolltechàQt Command Prompt”。 在命令行模式下,切换目录到hello下,然后输入命令:qmake –project。这个命令将产生一个依赖于工作平台的工程文件(hello.pro)。 再输入命令:qmake hello.pro。这个命令通过工程文件产生一个可以在特定工作平台上使用的makefile。 最后输入命令:make来产生应用程序。运行这个程序,可以得到如下的程序界面。 Qt也支持XML。我们可以把程序的第6行替换成下面的语句: QLabel *label = new QLabel ("

Hello " "Qt!

"); 重新编译程序,我们发现界面拥有了简单的HTML风格。如下图: 2、调用退出 第二个例子展示了如何使应用程序对用户的动作进行响应。这个应用程序包括了一个按钮,用户可以点击这个按钮来退出程序。程序代码与上一个程序非常相似,不同之处在于我们使用了一个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程序的窗口部件发射信号(signals)来指出一个用户的动作或者是状态的变化。在这个例子中,当用户点击这个按钮的时候,QPushButton就会发射一个信号——clicked()。一个信号可以和一个函数(在这种情况下我们把这个函数叫做“槽(slot)”)相连,当信号被发射的时候,和信号相连的槽就会自动执行。在这个例子中,我们把按钮的信号“clicked()”和一个QApplication对象的槽“quit()”相连。当按钮被按下的时候,这个程序就退出了。 3、窗口布局 在本小节,我们将用一个样例来展现如何在窗口中规划各个部件的布局,并学习使用信号和槽来使两个窗口部件同步。这个应用程序要求输入用户的年龄,使用者可以通过一个旋转窗口或者一个滑块窗口来输入。 这个应用程序包括三个窗口部件:一个QSpinBox,一个QSlider和一个QWidget。窗口部件QWidget是程序的主窗口。QSpinBox和QSlider被放置在QWidget中;他们是QWidget的子窗口。当然,我们也可以说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->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 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++编译器辩识的宏。 对于私有变量,我们使用了它们的类的预定义。这是被编译器所允许的,因为他们都是指针,而我们在头文件中并不需要访问他们,所以编译器并不需要完整的类定义。我们可以在头文件中包含使用这些类所需要的头文件(,,等等),但是使用预定义可以在某种程度上加快编译过程。 现在我们来看源文件finddialog.cpp。源文件里包括了FindDialog类的实现。 1 #include 2 #include "finddialog.h" 首先,我们包含了。这个头文件包含了对于Qt的GUI类的定义。Qt包括一些模块,每一个模块都依赖于自己的库文件。最重要的几个模块分别是QtCore, QtGui, QtNetwork, QtOpenGL, QtSpl, QtSvg和QtXml。头文件包括了QtCore和QtGui模块中所有类的实现。包含此头文件使我们不用单独地列出每个类所需要的头文件。 在filedialog.h中,我们也可以简单地包含,而不是像我们之前做的那样包括,并且给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 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()一个可能的实现: Void 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 Interface 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 #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工具来生成一个工程文件(.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 #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 #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 #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 #include #include 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à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

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ]
0 人已下载

下载文档

相关文档