初级篇
第1章 Qt初步实践
作为Qt程序开发之旅的第一站,本章将阐述如何使用Qt开发一个简单的GUI用户界面程序。在这一章,将学习如何建立Qt主程序、建立qmake工程,还将接触到“信号(signal)”和“槽(slot)”以及“Qt布局”等基本概念。随着学习的不断深入,将在第3章和第5章对这些概念进行深入的讲解,并演示它们在GUI用户界面设计中的应用。
1.1 第一个Qt程序
在这一节,学习创建第一个较简单的Qt应用程序。在这个程序中,用户界面将显示一行中文“同一个世界,同一个梦想!”通过这个程序,将学会使用两种手段建立Qt应用程序:KDevelop集成开发环境和vim编辑器。
1.1.1 建立主程序
首先,看一下第一个Qt GUI应用程序hello的源代码,其内容如下所示(为了便于读者查阅相关源代码,代码的第一行注释了该文件在源代码中的路径)。
// chapter01/hello/src/hello.cpp. #include <QtGui/QApplication> #include <QtGui/QWidget> #include <QtGui/QLabel> #include <QtCore/QTextCodec> int main(int argc, char* argv[]) { QApplication app(argc, argv); QTextCodec::setCodecForTr(QTextCodec::codecForName("gb18030")); QWidget* pWidget = new QWidget; QLabel label(pWidget); label.setText(QObject::tr("同一个世界,同一个梦想!")); pWidget->show(); return app.exec(); }
这个程序的功能是在一个窗口中显示“同一个世界,同一个梦想!”,运行效果如图1-1所示。
图1-1 第一个Qt程序
注意
窗口(Window)和窗口部件(Widget)
本书中,多次使用了窗口和窗口部件的概念。把一个图形用户界面称为窗口,它往往具有标题栏、窗口边框(frame)、能够通过鼠标拖动和改变大小等特性,最典型的窗口就是对话框。例如,第一个Qt应用程序的用户界面就是一个窗口。当文中使用窗口的时候,就是特指这种情况。
一般的,窗口部件是对所有图形用户界面的统称,它既可以作为单独的窗口出现,也可以出现在一个窗口的内部。
1.1.2 建立工程
现在暂不分析第一个Qt应用程序是如何运行的,而是先为它建立qmake工程,然后进行调试和运行。
在Linux系统中,可以有多种方法输入、编辑上述Qt程序,此处将使用vim文本编辑器和KDevelop工具建立上述主程序。
1.在vim中建立Qt应用程序
在vim中建立Qt应用程序,步骤如下(如果读者觉得在vim文本编辑器中编辑、调试以及运行Qt应用程序比较麻烦,可以直接跳过这一部分,而选择在KDevelop集成开发环境中建立该Qt应用程序):
打开控制台程序konsole,将当前目录切换到相应的路径下,执行控制台命令“mkdir hello”建立hello目录,执行 “cd hello”命令进入该目录。
在控制台执行“vim hello.cpp”命令(如果文件hello.cpp已经存在,则打开文件;否则新建hello.cpp文件),打开vim文本编辑器。
进入vim编辑器后按“i”键(即打开vim编辑器的修改编辑功能),然后输入第一个Qt应用程序hello的源代码。
按Esc键,退出vim文本编辑器的编辑功能。
最后,在vim文本编辑器的命令行输入命令“:wq”,按回车键后vim将保存文件并退出。
现在,第一个Qt应用程序hello已经输入到vim文本编辑器中了。关于vim编辑器的使用读者可查阅相关帮助文档,在此不再赘述。
2.在KDevelop中建立Qt应用程序
KDevelop是集编辑、编译、调试和运行C++程序等诸工具于一身的应用程序集成开发环境。在KDevelop中建立Qt应用程序并进行编译、调试、运行都是比较简单的,但需要建立KDevelop工程(这和使用VC++进行应用程序开发是类似的),对于初学者来说过程有些复杂。具体步骤如下:
打开KDevelop后,选择菜单“工程”|“新建工程”,如图1-2所示。
在“建立新工程”对话框的“所有工程”选项卡中,选择“C++ | QMake project | Basic Qt4 Application”,选择或者输入存放位置(例如,“/home/lcf/book/chapter01”),输入应用程序名称“hello”(KDevelop将会在/home/lcf/book/chapter01/路径下建立hello目录),单击“下一步”按钮,如图1-3所示。
设置“工程选项”,在此输入Qt4的qmake和Qt设计器的绝对路径,直接单击“下一步”按钮,如图1-4所示。
设置“版本控制系统”,略过,单击“下一步”按钮,如图1-5所示。
图1-2 新建KDevelop工程
图1-3 建立hello工程
图1-4 设置工程选项
图1-5 设置版本控制
在“h文件的模板”选项(如图1-6所示)中,可以设置头文件 .h的格式(在此省略);单击“下一步”按钮进入“cpp文件的模板”选项卡(如图1-7所示),与“h文件的模板”类似。
最后,单击“完成”按钮,KDevelop会自动生成一个标准的C++主程序。在此,编辑修改为第一个Qt应用程序hello的源代码,如图1-8所示。
图1-6 设置头文件模板
图1-7 设置实现文件模板
图1-8 第一个KDevelop工程
到此,在KDevelop中已经建立了一个KDevelop工程,并且输入了第一个Qt应用程序hello。接下来,建立Qt应用程序的qmake工程文件。
有两种方法建立qmake工程:自动生成和手动建立。下面分别描述如何使用这两种方法建立应用程序hello的qmake工程。
3.自动建立qmake工程
对于比较简单的小应用程序,使用qmake命令自动建立的qmake工程完全可以满足需要。
前面,已经在vim文本编辑器中输入了第一个Qt应用程序hello的源代码,现在为它建立相应的qmake工程。
首先,在控制台konsole中将当前目录切换到hello.cpp文件所在的目录,运行“qmake –project”命令。此时Qt的qmake工具将在当前目录下自动生成应用程序hello的工程文件hello.pro,其内容如下。
TEMPLATE = app TARGET = DEPENDPATH += . INCLUDEPATH += . # Input SOURCES += hello.cpp
下面分析一下这个qmake工程文件。
前3行文本是qmake工具自动添加的注释文本,它描述了该qmake工程文件是由qmake工具自动生成的,以及文件生成的时间和qmake工具的版本号。
变量TEMPLATE描述了为建立目标文件而采用何种模板,即生成何种形式的Makefile文件。qmake工具定义了5种模板:
● 应用程序app,为建立一个Qt应用程序创建Makefile文件;
● 库lib,为建立应用程序库而创建Makefile文件;
● 子工程subdirs,为建立子目录下的目标文件创建一个Makefile文件,子目录通过变量SUBDIRS指定(子目录下的工程文件也需要指出使用何种模板);
● VC应用程序vcapp,为Visual Studio生成一个应用程序工程,仅仅用于Windows操作系统。
● VC库vclib,为Visual Studio生成一个应用程序库工程,仅仅用于Windows操作系统。
由于第一个Qt程序是一个可直接执行的应用程序,因此采用“应用程序app”模板。
变量TARGET描述了目标文件的名称,即生成的应用程序的名字。qmake工具自动生成的qmake工程文件采用默认方式(TARGET的值为空),即应用程序的名字采用工程文件hello.pro所在的文件夹的名字hello。
变量DEPENDPATH描述了建立应用程序所依赖的其他文件所在的路径。hello.pro工程文件中将该值设置为当前目录。
变量INCLUDEPATH描述了编译该工程时编译器需要搜索的#include路径。hello.pro工程文件中将该值设置为当前目录。
变量SOURCES选项告诉编译器,源代码文件的路径(相对于工程文件hello.pro的位置)及其文件名字。应用程序hello比较简单,只包含一个hello.cpp源文件。
现在,在KDevelop中建立qmake工程文件。
事实上,在KDevelop中建立KDevelop工程的时候,KDevelop会在约定的目录下建立了一个与应用程序同名的hello目录,它的主要目录结构如图1-9所示。
其中,
● bin目录存放可执行的目标文件;
● src目录存放源文件(包括qmake工程文件src.pro以及资源文件等);
● templates目录存放KDevelop生成源文件.cpp和头文件.h时使用的模板;
● hello目录下的其他文件都是关于KDevelop工程的(hello.pro是自动生成的qmake工程文件)。
图1-9 第一个Qt程序的目录结构
可以看到,KDevelop在上面的目录结构中生成了两个qmake工程文件:hello目录下的hello.pro主工程文件和hello/src目录下的src.pro子工程文件。
主工程文件hello.pro的内容如下。
TEMPLATE=subdirs SUBDIRS=src
第1行的TEMPLATE变量的值为subdirs,表示hello.pro工程在子文件夹中还包含子工程。子工程文件所在的目录由第2行SUBDIRS变量指定。在运行qmake生成Makefile文件的时候,qmake工具会根据主工程hello.pro中的SUBDIRS选项自动到相应的目录下寻找src.pro子工程文件,qmake工具将联合两个工程文件分别生成两个前后相关的Makefile文件。
qmake子工程文件src.pro的内容如下。
SOURCES=hello.cpp TARGET=../bin/hello
该工程文件中,大多数变量(或选项)采用了默认值,仅仅定义了SOURCES和TARGET工程选项,在此不再详述。
注意
工程文件中的源文件.cpp和头文件.h的位置,既可以采用绝对路径表示,也可以采用相对路径表示。相对路径意味着源文件或头文件相对于工程文件.pro所在目录的路径。例如,在第一个Qt应用程序的例子中,src.pro工程文件在./hello/src目录下,hello.cpp源文件也在./hello/src目录下。那么采用相对路径时,工程文件src.pro的选项SOURCES的值为:
./hello.cpp(或hello.cp)
而采用绝对路径时,则值为:
< KDevelop工程所在的绝对路径>/src/hello.cpp
其他工程选项也是类似的,比如src.pro工程文件中的TARGET变量值为../bin/hello,表示目标文件将被放置在< KDevelop工程所在的绝对路径> /bin目录下面。
4.手动建立qmake工程
建立较大型Qt应用程序时,使用qmake工具生成的qmake工程文件将无法满足编程的需要。尽管qmake工具生成的工程文件完全可以满足hello程序的编译需求,但作为一个例子,笔者生成了一个对应于vim输入的源代码文件的较简单的hello.pro文件,它位于hello目录下。其内容如下:
TEMPLATE = app TARGET = hello DESTDIR = . CONFIG += debug \ warn_on OBJECTS_DIR= ./tmp SOURCES += hello.cpp
下面,简单地分析一下这个工程文件。
● 变量TARGET定义了可执行目标文件的名字为hello。
● 变量DESTDIR定义了存放可执行目标文件hello的路径,即在hello目录下。
● 变量CONFIG定义了编译选项,即:
debug表示建立的目标代码是调试版本(相对于release发布版本);
warn_on要求编译器在编译应用程序时打开警告开关。
● 变量OBJECTS_DIR描述了编译/连接应用程序过程中产生的中间文件存放的位置,即将编译器生成的中间文件hello.o放置在工程文件所在目录的tmp子目录下(hello/tmp)。
在为应用程序hello建立qmake工程后,就可以编译运行第一个Qt应用程序了。
1.1.3 编译/运行第一个Qt应用程序
下面,通过两种方式编译、运行第一个Qt应用程序。
在控制台模式下,依次在工程文件所在的目录下执行下述命令:
● qmake,生成Makefile文件;
● make,编译、连接并生成可执行代码;
● ./hello,执行应用程序。
在KDevelop中,编译、运行Qt应用程序的步骤如下:
在右侧“QMake管理器”选项卡的“src”子工程下单击右键,选择菜单命令“运行qmake”,建立Makefile文件,如图1-10所示。
选择“编译”菜单中的“编译工程”命令(或单击工具栏中的“编辑工程”工具按钮),编译、连接Qt程序,如图1-11所示。
选择“编译”菜单中的“执行主程序”命令(或单击工具栏中的“执行主程序”工具按钮),运行hello程序。应用程序的运行效果如图1-12所示。
图1-10 KDevelop中运行qmake
图1-11 执行编辑工程命令
图1-12 第一个Qt程序运行效果
1.1.4 第一个Qt程序的代码分析
现在再回过头来逐行地分析一下源代码,看看第一个Qt应用程序是如何运行的。
这是一个非常简单的Qt应用程序,它不包含任何自定义的类,且仅仅有一个hello.cpp主程序文件。
// chapter01/hello/src/hello.cpp. #include <QtGui/QApplication> #include <QtGui/QWidget> #include <QtGui/QLabel> #include <QtCore/QTextCodec>
首先包含必要的Qt类的头文件。
● #include <QtGui/QApplication> 表示包含Qt的应用程序类QApplication的头文件,其中的QtGui表示Qt的QtGui模块,从目录结构来讲,QtGui是文件夹。<QApplication>是Qt定义QApplication类的头文件(与类名相同);QApplication类是每一个Qt GUI应用程序所必需的。
● #include <QtGui/QWidget> 将Qt的基础窗口部件QWidget的定义包含进来;接下来的#include<QtGui/QLabel> 将Qt的标签QLabel头文件包含进来。事实上,QLabel头文件中已经包含了QWidget的定义,因此也可以去掉对QWidget头文件的包含。
● #include <QtCore/QTextCodec> 用于包含QtCore模块下的QTextCodec类的头文件。类QtextCodec封装定义了显示文本(例如,“同一个世界,同一个梦想”)字符集的转化功能。
扩展阅读
Qt4定义了多个模块,每个模块包含相对独立的库文件并实现各自相应的功能。Qt4的软件开发模块有:
● QtCore,Qt4的基本模块,定义了其他模块使用的Qt核心的非GUI类,所有其他的模块都依赖于该模块。
● QtGui,定义了图形用户界面类。
● QtMultimedia,提供了对低级多媒体编程的支持。
● QtNetwork,定义了Qt的网络编程类。
● QtOpenGL,定义了OpenGL的支持类。
● QtOpenVG,定义了对OpenVG的支持类。
● QtScript,该模块提供了对脚本的支持。
● QtScriptTools,脚本调试器。
● QtSql,定义了访问数据库的类。
● QtSvg,定义了显示和生成SVG(Scalable Vector Graphics)类。
● QtWebkit,定义了显示和编辑Web内容的类型。
● QtXml,定义了处理XML(eXtensible Markup Language)语言的类。
● QtXmlPatters,为XML和自定义数据模型提供了XQuery和XPath引擎。
● QtDeclarative,声明式UI引擎。
● Phono,多媒体框架。
● Qt3Support,定义了同Qt4以前版本Qt3兼容的类,以使得Qt3的程序能够更容易地移植到Qt4。
还有一些模块属于Qt工具,具体有:
● QtDesigner,定义了扩展Qt设计器(Qt Designer)的类,该模块使得程序员能够为Qt设计器创建自定义的Qt窗口部件插件(widget plugins),以及创建能够访问Qt设计器组件的类;
● QtUiTools,定义了在应用程序中直接处理ui(User Interface)文件的类,它使得应用程序能够在运行时使用ui文件构建用户界面。
● QtHelp,为应用程序提供了加载Qt助手(Qt Assistant),以支持在线帮助(online help)的功能。
● QtTest,定义了对Qt应用程序和库进行单元测试(unit testing)的类。
UNIX平台的Qt4版本还包含QtDBus扩展模块,该模块提供了使用D-Bus进行进程间通信(Inter-Process Communication,IPC)的Qt类。
此外,Windows平台的Qt版本还包含两个扩展模块:
(1)QAxContainer,定义了访问ActiveX控件和COM(Component Object Model)对象的扩展;
(2)QAxServer,一个静态库,用于将一个标准的Qt二进制代码转化为COM服务器(COM server)。
在qmake工程中,默认情况下已经包含了QtCore和QtGui模块(如果不想使用QtGui模块,而仅仅使用QtCore连接程序,可以在qmake工程文件中通过使用“QT -= gui”来取消对QtGui模块的包含),因此无需配置就可以使用这两个模块中的类。而对于Qt的其他模块,在使用之前必须在qmake工程文件中通过QT选项进行配置(将在各个章节中详细阐述)。
一般可以在应用程序中通过 #include <QtGui/QtGui>包含整个QtGui模块所有类的头文件,其中第一个QtGui是模块名,第二个QtGui是QtGui模块(文件夹)下的预定义头文件(或者使用#include <QtGui>,其效果相同,不过此时<QtGui>是QtGui模块(文件夹)下的预定义头文件);也可以单独包含某个类的头文件:#include <QtGui/QApplication>(或者 #include <QApplication>)。
int main ( int argc, char* argv[] ) { QApplication app( argc, argv );
创建一个QApplication对象并将用户在控制台输入的参数传递给该应用程序对象。QApplication对象管理Qt GUI应用程序的控制流程和主要的设置选项。使用Qt设计的任何GUI应用程序,都必须包含一个QApplication对象。而对于非GUI的Qt应用程序,可以使用不依赖于QtGui库的QCoreApplication。
QTextCodec::setCodecForTr( QTextCodec::codecForName("gb18030"));
该行代码设置QObject::tr()使用的字符集。在第一个Qt应用程序中,显示的是中文字符“同一个世界,同一个梦想!”,因此程序采用的是字符集“GB18030”(GB18030-2000是取代GBK1.0(1995年)的正式国家标准,该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030)。如果不采用正确的字符集,Qt应用程序中的中文字符将显示为乱码。如果仅仅显示英文字符的话,可以不使用该行代码,并且将下面的设置标签文本的代码修改为label.setText(“one world, one dream.”)。
QWidget* pWidget = new QWidget;
创建一个QWidget对象。
QLabel label( pWidget ); label.setText( QObject::tr( "同一个世界,同一个梦想!" ) );
创建一个QLabel对象,并将该标签的父窗口部件设置为pWidget,这将使得QLabel对象放置在QWidget界面上。QLabel::setText()函数设置QLable对象的显示文本为“同一个世界,同一个梦想!”。
pWidget->show(); return app.exec(); }
函数QWidget::show()将创建的图形用户界面呈现在显示器上。
最后程序返回Qt应用程序对象app执行的结果,并退出。
QApplication::exec()语句的执行,将使得Qt GUI进入一个主事件循环,直到程序中调用exit()、quit()或关闭应用程序的主窗口。主事件循环开始后,它将会接收用户界面事件以及其他事件源的事件,并向相应的窗口进行分发和处理。此外,它还完成Qt应用程序的初始化和应用程序运行结束后的善后处理,并提供会话管理(session management)。
注意
在Linux平台下,编译应用程序时,编译器经常会给出“no newline at end of file.”的警告。它的意思是说,在应用程序代码文件的最后没有新行。为了消除这个编译器告警,必须在应用程序代码的最后加一行没有任何字符(也不能包含任何的空格字符)的空行。