QT详解

QT详解
OQSQT详解
- Qt 可以做什么?
Qt 虽然经常被当做一个 GUI 库,用来开发图形界面应用程序,但这并不是 Qt 的全部;Qt 除了可以绘制漂亮的界面(包括控件、布局、交互),还包含很多其它功能,比如多线程、访问数据库、图像处理、音频视频处理、网络通信、文件操作等,这些 Qt 都已经内置了。 总起来说,Qt 主要用于桌面程序开发和嵌入式开发。
- Qt 来开发 Windows 桌面程序有以下优点:
简单易学:Qt 封装的很好,几行代码就可以开发出一个简单的客户端,不需要了解 Windows API。
资料丰富:资料丰富能够成倍降低学习成本,否则你只能去看源码,关于 DirectUI、Htmlayout、aardio 的资料就很少。
漂亮的界面:Qt 很容易做出漂亮的界面和炫酷的动画,而 MFC、WTL、wxWidgets 比较麻烦。
独立安装:Qt 程序最终会编译为本地代码,不需要其他库的支撑,而 Java 要安装虚拟机,C# 要安装 .NET Framework。
跨平台:如果你的程序需要运行在多个平台下,同时又希望降低开发成本,Qt 几乎是必备的。
- 学习QT的基础
C++依旧是 Qt 的主要编程语言,Qt 5 也并没有忽略它,Qt 5 添加了很多新的 C++ API,而且会持续更新。引入 QML 只是 Qt 5 提供的另外一种选择,并不是让它成为唯一的选择。C++ 是 Qt 的基础,无论如何都要掌握总的来说,C++ 对于 Qt 是不可或缺的,而 QML 只是一个加分项。
- 认识一下Qt用到的开发工具
Qt不是凭空产生的,它是基于现有工具链打造而成的,它所使用的编译器、链接器、调试器等都不是自己的,Qt 官方只是开发了上层工具。下面我们分几个部分讲解 Qt 使用到的工具链。
GNU 工具集 在 GNU 工具集里面,开发时常见到的几个罗列如下(这些工具通常位于 Linux 或 Unix 系统里的 /usr/bin/ 目录):
| 工具 | 说明 |
|---|---|
| gcc | GNU C 语言编译器。 |
| g++ | GNU C++ 语言编译器。 |
| ld | GNU 链接器,将目标文件和库文件链接起来,创建可执行程序和动态链接库。 |
| ar | 生成静态库 .a ,可以编辑和管理静态链接库。 |
| make | 生成器,可以根据 makefile 文件自动编译链接生成可执行程序或库文件。 |
| gdb | 调试器,用于调试可执行程序。 |
| ldd | 查看可执行文件依赖的共享库(扩展名 .so,也叫动态链接库)。 |
MinGW
原本 GNU 工具只在 Linux/Unix 系统里才有,随着 Windows 系统的广泛使用, 为了在 Windows 系统里可以使用 GNU 工具,诞生了 MinGW(Minimalist GNU for Windows) 项目,利用 MinGW 就可以生成 Windows 里面的 exe 程序和 dll 链接库。 需要注意的是,MinGW 与 Linux/Unix 系统里 GNU 工具集的有些区别:
MinGW 里面工具带有扩展名 .exe, Linux/Unix 系统里工具通常都是没有扩展名的。
MinGW 里面的生成器文件名为 mingw32-make.exe,Linux/Unix 系统里就叫 make。
MinGW 在链接时是链接到 *.a 库引用文件,生成的可执行程序运行时依赖 *.dll,而 Linux/Unix 系统里链接时和运行时都是使用 *.so 。
另外 MinGW 里也没有 ldd 工具,因为 Windows 不使用 .so 共享库文件。如果要查看 Windows 里可执行文件的依赖库,需要使用微软自家的 Dependency Walker 工具。Windows 里面动态库扩展名为 .dll,MinGW 可以通过 dlltool 来生成用于创建和使用动态链接库需要的文件,如 .def 和 .lib。 MinGW 原本是用于生成 32 位程序的,随着 64 位系统流行起来, 从 MinGW 分离出来了 MinGW-w64 项目,该项目同时支持生成 64 位和 32 位程序。Qt 的 MinGW 版本库就是使用 MinGW-w64 项目里面的工具集生成的。
MSYS(Minimal SYStem)
另外提一下,由于 MinGW 本身主要就是编译链接等工具和头文件、库文件,并不包含系统管理、文件操作之类的 Shell 环境, 这对希望用类 Unix 命令的开发者来说还是不够用的。 所以 MinGW 官方又推出了 MSYS(Minimal SYStem),相当于是一个部署在 Windows 系统里面的小型 Unix 系统环境, 移植了很多 Unix/Linux 命令行工具和配置文件等等,是对 MinGW 的扩展。 MSYS 对于熟悉 Unix/Linux 系统环境或者要尝试学习 Unix/Linux 系统的人都是一种便利。MSYS 和 MinGW 的安装升级都是通过其官方的 mingw-get 工具实现,二者是统一下载安装管理的。 对于 MinGW-w64 项目,它对应的小型系统环境叫 MSYS2(Minimal SYStem 2),MSYS2 是 MSYS 的衍生版,不仅支持 64 位系统和 32 位系统,还有自己的独特的软件包管理工具,它从 Arch Linux 系统里移植了 pacman 软件管理工具,所以装了 MSYS2 之后,可以直接通过 pacman 来下载安装软件,而且可以自动解决依赖关系、方便系统升级等。装了 MSYS2 之后,不需要自己去下载 MinGW-w64,可以直接用 pacman 命令安装编译链接工具和 git 工具等。 MinGW 项目主页(含 MSYS): http://www.mingw.org/
MinGW-w64 项目主页: https://sourceforge.net/projects/mingw-w64/
MSYS2 项目主页: https://sourceforge.net/projects/msys2/
CMake
CMake(Cross platform Make)是一个开源的跨平台自动化构建工具, 可以跨平台地生成各式各样的 makefile 或者 project 文件, 支持利用各种编译工具生成可执行程序或链接库。 CMake 自己不编译程序, 它相当于用自己的构建脚本 CMakeLists.txt,叫各种编译工具集去生成可执行程序或链接库。 一般用于编译程序的 makefile 文件比较复杂,自己去编写比较麻烦, 而利用 CMake ,就可以编写相对简单的 CMakeLists.txt ,由 CMake 根据 CMakeLists.txt 自动生成 makefile,然后就可以用 make 生成可执行程序或链接库。 本教程里面是使用 Qt 官方的 qmake 工具生成 makefile 文件,没有用 CMake。这里之所以提 CMake,是因为整个 KDE 桌面环境的茫茫多程序都是用 CMake 脚本构建的,另外跨平台的程序/库如 Boost C++ Libraries、OpenCV、LLVM、Clang 等也都是用 CMake 脚本构建的。以后如果接触到这些东西,是需要了解 CMake 的。 CMake 项目主页:https://cmake.org/
KDE 项目主页:https://www.kde.org/
Qt 工具集
Qt 官方的开发环境安装包里有自己专门的开发工具,之前用过 qmake 命令。qmake 是 Qt 开发最核心的工具,既可以生成 Qt 项目文件 .pro ,也可以自动生成项目的 Makefile 文件。 这里将常用的 Qt 开发工具列表如下:
| 工具 | 说明 |
|---|---|
| qmake | 核心的项目构建工具,可以生成跨平台的 .pro 项目文件,并能依据不同操作系统和编译工具生成相应的 Makefile,用于构建可执行程序或链接库。 |
| uic | User Interface Compiler,用户界面编译器,Qt 使用 XML 语法格式的 .ui 文件定义用户界面,uic 根据 .ui 文件生成用于创建用户界面的 C++ 代码头文件,比如 ui_*****.h 。 |
| moc | Meta-Object Compiler,元对象编译器,moc 处理 C++ 头文件的类定义里面的 Q_OBJECT 宏,它会生成源代码文件,比如 moc_*****.cpp ,其中包含相应类的元对象代码,元对象代码主要用于实现 Qt 信号/槽机制、运行时类型定义、动态属性系统。 |
| rcc | Resource Compiler,资源文件编译器,负责在项目构建过程中编译 .qrc 资源文件,将资源嵌入到最终的 Qt 程序里。 |
| qtcreator | 集成开发环境,包含项目生成管理、代码编辑、图形界面可视化编辑、 编译生成、程序调试、上下文帮助、版本控制系统集成等众多功能, 还支持手机和嵌入式设备的程序生成部署。 |
| assistant | Qt 助手,帮助文档浏览查询工具,Qt 库所有模块和开发工具的帮助文档、示例代码等都可以检索到,是 Qt 开发必备神器,也可用于自学 Qt。 |
| designer | Qt 设计师,专门用于可视化编辑图形用户界面(所见即所得),生成 .ui 文件用于 Qt 项目。 |
| linguist | Qt 语言家,代码里用 tr() 宏包裹的就是可翻译的字符串,开发人员可用 lupdate 命令生成项目的待翻译字符串文件 .ts,用 linguist 翻译多国语言 .ts ,翻译完成后用 lrelease 命令生成 .qm 文件,然后就可用于多国语言界面显示。 |
| qmlscene | 在 Qt 4.x 里是用 qmlviewer 进行 QML 程序的原型设计和测试,Qt 5 用 qmlscene 取代了旧的 qmlviewer。新的 qmlscene 另外还支持 Qt 5 中的新特性 scenegraph 。 |
- Qt编程涉及的术语和名词
本节我们来介绍一下使用 Qt编程过程中常用的术语和名字,它们不一定专属于 Qt,在其它的 C/C++开发过程中也会使用到。
Project
Project 的中文翻译是“项目”或者“工程”,这里的项目是指为实现某个相对独立功能的程序代码合集,这些代码不单单是放在一块,而是有相互之间的关联性,并且有专门负责管理该项目的项目文件,比如:
Qt 使用 .pro 文件管理项目;
VC++ 则使用 .vcproj 作为项目文件。
集成开发环境通常都是依据项目文件(.pro/.vcproj)管理和构建项目。
Makefile
即生成脚本,虽然可以直接调用编译器如 g++ 编译程序,但是如果项目里的代码文件变多了,哪些代码文件更新了需要重新编译,哪些代码没有改不需要重新编译等等,靠程序员自己记忆去处理是比较麻烦的事,还有哪些代码需要预处理或是链接哪些库文件, 这些都是繁杂的过程。为了规范程序的编译生成过程,产生了规范化的生成脚本,就是 Makefile,生成器 make 可以依据规范的 Makefile 自动生成目标程序或库文件。 简单的说,就是定义好 Makefile ,让程序员只需要去关注如何编写代码,而生成程序过程中的脏活累活都交给 make 程序。 现在 Makefile 通常都有工具自动生成,如 qmake 工具, 这样就大量减轻了程序员的负担。
Debug 和 Release
Debug 即调试,Release 即发行。代码编写之后,生成的目标程序或库文件通常不会绝对正确,或多或少有些毛病(bug), 因此需要进行纠错调试(Debug)。调试过程中需要源代码和二进制目标程序之间一一对应的关系, 这样才能定位到错误代码,所以 Debug 版本的程序是臃肿而不进行优化的。 与之相对的是 Release 发行版,在纠正了发觉到的错误后,需要发布程序用于实际用途,实际应用时强调运行效率高,减少冗余代码,因此会对二进制程序进行大量优化,提升性能。这样发布的二进制目标程序就是 Release 版。 Debug 版本和 Release 版本使用的库文件不一样:
Debug 版本程序通常链接的也是 Debug 版本的库文件,比如 libQt5Guid.a/Qt5Guid.dll,库文件的简短名(不含扩展名)都是以 d 结尾的,Debug 库通常都比较大 。
Release 版本程序链接的通常就是 Release 版本的库文件,Release 版本库文件名字比 Debug 版本库文件少一个字母 d ,如 libQt5Gui.a/Qt5Gui.dll,而且 Release 版本库一般都比 Debug 版本小很多,运行效率也高很多。
C++11 标准
时代在变化,C++ 标准也在前进。C++ 正式公布标准有 C++98、C++03、C++11。最新的 C++11 标准是2011年8月12日公布的,在公布之前该标准原名为 C++0x 。这是一次较大的修订和扩充,建议读者专门学一下。 Qt 从 4.8 版本就开始用 C++11 新特性了。编译器里面开始支持 C++11 的版本是 MSVC 2010、GCC4.5、Clang 3.1,这之后版本的编译器都在逐步完善对 C++11 的支持,现在新版本编译器对新标准的支持都比较全面了。 Qt 官方在编译 Qt5 库的时候都是开启 C++11 特性的,如果我们要在自己项目代码启用新标准,需要在 .pro 文件里面添加一行:
CONFIG += c++11
如果是 Qt4 版本则是添加:
gcc:CXXFLAGS += -std=c++0x
MSVC 编译器默认开启 C++11 特性,GCC(g++命令)则需要自己添加选项 -std=c++0x ,上面 CXXFLAGS 就是为 GCC 编译器(g++命令)添加 -std=c++0x 选项。
Dynamic Link 和 Static Link
Dynamic Link 即动态链接,Static Link 即静态链接。
动态链接库
目标程序通常都不是独立个体,生成程序时都需要链接其他的库,要用到其他库的代码。对于多个程序同时运行而言,内存中就可能有同一个库的多个副本,占用了太多内存而干的活差不多。 为了优化内存运用效率,引入了动态链接库(Dynamic Link Library),或叫共享库(Shared Object)。使用动态链接库时,内存中只需要一份该库文件,其他程序要使用该库文件时,只要链接过来就行了。由于动态库文件外置,链接到动态库的目标程序相对比较小,因为剥离了大量库代码,而只需要一些链接指针。 使用动态库,也意味着程序需要链接到如 *.dll 或 *.so 文件,得提前装好动态库文件,然后目标程序才能正常运行。
静态链接库
静态库就是将链接库的代码和自己编写的代码都编译链接到一块,链接到静态库的程序通常比较大,但好处是运行时依赖的库文件很少,因为目标程序自己内部集成了很多库代码。
库文件后缀
Linux/Unix 系统里静态库扩展名一般是 .a,动态库扩展名一般是 .so 。Windows 系统里 VC 编译器用的静态库扩展名一般是 .lib,动态库扩展名一般是 .dll 。 MinGW 比较特殊,是将 GNU 工具集和链接库从 Linux/Unix 系统移植到 Windows 里, 有意思的情况就出现了,MinGW 使用的静态库扩展名为 .a ,而其动态库扩展名则为 .dll, .a 仅在生成目标程序过程中使用,.dll 则是在目标程序运行时使用。
Explicit Linking 和 Implicit Linking
Explicit Linking 即显式链接,Implicit Linking 即隐式链接,这两种都是动态链接库的使用方式。 动态链接库通常都有其导出函数列表, 告知其他可执行程序可以使用它的哪些函数。可执行程序使用这些导出函数有两种方式:一是在运行时使用主动加载动态库的函数,Linux 里比如用 dlopen 函数打开并加载动态库,Windows 里一般用 LoadLibrary 打开并加载动态库,只有当程序代码执行到这些函数时,其参数里的动态库才会被加载,这就是显式链接。显式链接方式是在运行时加载动态库,其程序启动时并不检查这些动态库是否存在。 隐式链接是最为常见的,所有的编译环境默认都是采用隐式链接的方式使用动态库。隐式链接会在链接生成可执行程序时就确立依赖关系,在该程序启动时,操作系统自动会检查它依赖的动态库,并一一加载到该程序的内存空间,程序员就不需要操心什么时候加载动态库了。比如 VC 编译环境,链接时使用动态库对应的 .lib 文件(包含动态库的导出函数声明,但没有实际功能代码),在 .exe 程序运行前系统会检查依赖的 .dll,如果找不到某个动态库就会出现类似下图对话框:
MinGW 是将动态库的导出函数声明放在了 .a 文件里,程序运行依赖的动态库也是 .dll 。 请注意,VC 链接器使用的 .lib 文件分两类,一种是完整的静态库,体积比较大,另一种是动态库的导出声明,体积比较小。MinGW 链接器使用的 .a 文件也是类似的,Qt 官方库都是按照动态库发布的,静态库只有自己编译才会有。
- Qt Creator的初步使用
启动 QtCreator,出现如图 1 所示的主窗口:

Qt Creator 的界面很简洁。上方是主菜单栏,左侧是主工具栏,窗口的中间部分是工作区。根据设计内容不同,工作区会显示不同的内容。 图 1 是在左侧主工具栏单击“Welcome(欢迎)”按钮后显示实例的界面。这时工作区的左侧有 “Projects”、“Examples(示例)”、“Tutorials(教程)”、“Get Started Now”几个按钮,单击后会在主工作区显示相应的内容:
单击“Projects”按钮后,工作区显示新建项目按钮和最近打开项目的列表。
单击“Examples(示例)”按钮后,工作区显示 Qt 自带的大量实例,选择某个实例就可以在 Qt Creator 中打开该项目源程序。
单击“Tutorials(教程)”按钮后,工作区显示各种视频教程,查看视频教程需要联网并使用浏览器打开。
单击“Get Started Now”按钮,工作区显示“Qt Creator Manual”帮助主题内容。
主窗口左侧是主工具栏,主工具栏提供了项目文件编辑、窗体设计、程序调试、项目设置等各种功能按钮。
Qt Creator 的设置
对 Qt Creator 可以进行一些设置,如刚安装好的 Qt Creator 界面语言可能是中文,也可以选择将 Qt Creator 的界面语言设置为英文。

单击 Qt Creator 菜单栏的 Tools→Options 菜单项会打开选项设置对话框(如图 2 所示)。对话框的左侧是可设置的内容分组,单击后右侧出现具体的设置界面。常用的设置包括以下几点:
Environment(环境) 设置:在 Interface 页面可以设置语言和主题,本教程全部以中文界面的 Qt Creator 进行讲解,所以语言选择为 Chinese(China);为了使界面抓图更清晰,设置主题为 Flat Light。更改语言和主题后需要重新启动 Qt Creator 才会生效。
Text Editor(文本编辑器)设置:在此界面可以设置文本编辑器的字体,设置各种类型文字的字体颜色,如关键字、数字、字符串、注释等字体颜色,也可以选择不同的配色主题。编辑器缺省字体的大小为 9,可以修改得大一些。
Build & Run(构建和运行)设置:图 2 显示的是 Build & Run 的设置界面,它有以下几个页面。
Kits(构建套件)页面显示 Qt Creator 可用的编译工具。
Qt Versions 页面显示安装的 Qt 版本。
Compliers(编译器)页面显示系统里可用的 C 和 C++编译器,由于安装了 MinGW 和 Visual Studio 2015,Qt Creator 会自动检测出这些编译器。
Debuggers 页面显示 Qt Creator 自动检测到的调试器,有 GNU gdb for MinGW 调试器和 Windows 的 CDB 调试器。
编写第一个Qt程序
学习一种编程语言或编程环境,通常会先编写一个“Hello World”程序。我们也用 QtCreator 编写一个“Hello World”程序,以初步了解 Qt Creator 设计应用程序的基本过程,对使用 Qt Creator 编写 Qt C++应用程序建立初步的了解。
新建一个项目
单击 Qt Creator 的菜单项文件->新建文件或项目,出现如图 1 所示的对话框。在这个对话框里选择需要创建的项目或文件的模板。

Qt Creator 可以创建多种项目,在最左侧的列表框中单击“Application”,中间的列表框中列出了可以创建的应用程序的模板,各类应用程序如下:
Qt Widgets Application,支持桌面平台的有图形用户界面(Graphic User Interface,GUI) 界面的应用程序。GUI 的设计完全基于 C++ 语言,采用 Qt 提供的一套 C++ 类库。
Qt Console Application,控制台应用程序,无 GUI 界面,一般用于学习 C/C++ 语言,只需要简单的输入输出操作时可创建此类项目。
Qt Quick Application,创建可部署的 Qt Quick 2 应用程序。Qt Quick 是 Qt 支持的一套 GUI 开发架构,其界面设计采用 QML 语言,程序架构采用 C++ 语言。利用 Qt Quick 可以设计非常炫的用户界面,一般用于移动设备或嵌入式设备上无边框的应用程序的设计。
Qt Quick Controls 2 Application,创建基于 Qt Quick Controls 2 组件的可部署的 Qt Quick 2 应用程序。Qt Quick Controls 2 组件只有 Qt 5.7 及以后版本才有。
Qt Canvas 3D Application,创建 Qt Canvas 3D QML 项目,也是基于 QML 语言的界面设计,支持 3D 画布。
在图 1 显示的对话框中选择项目类型为 Qt Widgets Application 后,单击“Choose…”按钮,出现如图 2 所示的新建项目向导:

在图 2 中,选择一个目录,如“E:\QtDemo”,再设置项目名称为 Demo, 这样新建项目后,会在“E:\QtDemo”目录下新建一个目录,项目所有文件保 存在目录“E:\QtDemo\Demo\”下。 在图 2 中设置好项目名称和保存路径后,单击“Next”按钮,出现如图 3 所示的选择编译工具的界面:

可以将这几个编译工具都选中,在编译项目时再选择一个作为当前使用的编译工具,这样可以编译生成不同版本的可执行程序。

在图 3 显示的界面中单击“Next”按钮,出现如图 4 所示的界面。在此界面中选择需要创建界面的基类(base class)。有 3 种基类可以选择:
QMainWindow 是主窗口类,主窗口具有主菜单栏、工具栏和状态栏,类似于一般的应用程序的主窗口;
QWidget 是所有具有可视界面类的基类,选择 QWidget 创建的界面对各种界面组件都可以 支持;
QDialog 是对话框类,可建立一个基于对话框的界面;
在此选择 QMainWindow 作为基类,自动更改的各个文件名不用手动去修改。勾选“创建界面”复选框。这个选项如果勾选,就会由 Qt Creator 创建用户界面文件,否则,需要自己编程手工创建界面。初始学习,为了了解 Qt Creator 的设计功能,勾选此选项。 然后单击“Next”按钮,出现一个页面,总结了需要创建的文件和文件保存目录,单击“完成”按钮就可以完成项目的创建。
项目的文件组成和管理
完成了以上新建项目的步骤后,在 Qt Creator 的左侧工具栏中单击“编辑”按钮,可显示如图 5 所示的窗口。

窗口左侧有上下两个子窗口,上方的目录树显示了项目内文件的组织结构,显示当 前项目为 Demo。项目的名称构成目录树的一个根节点,Qt Creator 可以打开多个项目,但是只有一个活动项目,活动项目的项目名称节点用粗体字体表示。 在项目名称节点下面,分组管理着项目内的各种源文件,几个文件及分组分别为以下几项:
Demo.pro 是项目管理文件,包括一些对项目的设置项。
Headers 分组,该节点下是项目内的所有头文件(.h),图 5 中所示项目有一个头文件 mainwindow.h,是主窗口类的头文件。
Sources 分组:该节点下是项目内的所有 C++源文件(.cpp),图 5 中所示项目有两个 C++ 源文件,mainwindow.cpp 是主窗口类的实现文件,与 mainwindow.h 文件对应。main.cpp 是主函数文件,也是应用程序的入口。
Forms 分组:该节点下是项目内的所有界面文件(.ui)。图 5 中所示项目有一个界面文件mainwindow.ui,是主窗口的界面文件。界面文件是文本文件,使用 XML 语言描述界面的组成。
左侧上下两个子窗口的显示内容可以通过其上方的一个下拉列表框进行选择,可以选择的显示内容包括项目、打开文档、书签、文件系统、类视图、大纲等。在图 5 中,上方的子窗口显示了项目的文件目录树,下方显示打开的文件列表。可以在下方选择显示类视图,这样下方则显示项目内所有的类的结构,便于程序浏览和快速切换到需要的代码位置。 双击文件目录树中的文件mainwindow.ui,出现如图 6 所示的窗体设计界面:

这个界面实际上是 Qt Creator 中集成的 Qt Designer。窗口左侧是分组的组件面板,中间是设计的窗体。在组件面板的 Display Widgets 分组里,将一个Label组件拖放到设计的窗体上面。双击刚刚放置的 Label 组件,可以编辑其文字内容,将文字内容更改为“Hello, World!”。还可以在窗口右下方的属性编辑器里编辑标签的 Font 属性,Point Size(点大小)更改为 12,勾选粗体。
项目的编译、调试与运行
单击主窗口左侧工具栏上的“项目”按钮,出现如图 7 所示的项目编译设置界面。

界面左侧一栏的“Build & Run”下面显示了本项目中可用的编译器工具,要使用哪一个编译器用于项目编译,单击其名称即可,选择的编译器名称会用粗体字表示。这里选择使用 MinGW 32bit 编译器。 每个编译器又有 Build 和 Run 两个设置界面。在 Build 设置界面上,有一个“Shadow build” 复选框。如果勾选此项,编译后将在项目的同级目录下建立一个编译后的文件目录,目录名称包含编译器信息,这种方式一般用于使用不同编译器创建不同版本的可执行文件。如果不勾选此项,编译后将在项目的目录下建立“Debug”和“Release”子目录用于存放编译后的文件。 在设计完 mainwindow.ui 文件,并设置好编译工具之后,就可以对项目进行编译、调试或运行。主窗口左侧工具栏下方有 4 个按钮,其功能见表 1。
| 图标 | 作用 | 快捷键 |
|---|---|---|
![]() |
弹出菜单选择编译工具和编译模式,如 Debug或 Release模式 | |
![]() |
直接运行程序,如果修改后未编译,会先进行编译。即使在程序中设置了断点,此方式运行的程序也无法调试。 | Ctrl+R |
![]() |
项目需要以Debug模式编译,点此按钮开始调试运行,可以在程序中设置断点。若是以 Release模式编译,点此按钮也无法进行调试。 | F5 |
![]() |
编译当前项目 | Ctrl+B |
首先对项目进行编译,没有错误后,再运行程序。程序运行的界面如图 8 所示。这就是一个标准的桌面应用程序,我们采用可视化的方式设计了一个窗口,并在上面显示了字符串“Hello, World!”。

在 Qt Creator 中也可以对程序设置断点进行调试,但是必须以 Debug 模式编译,并以“Start Debugging”(快捷键 F5)方式运行程序。
程序调试的方法与一般 IDE 工具类似,不再详述。注意,要在 Qt Creator 里调试 MSVC2015 编译的程序,必须安装 Windows 软件开发工具包 SDK。
- Qt项目管理文件(.pro)及其作用详解
在 QtCreator 中新建一个 Widget Application 项目 samp2_1,在选择窗口基类的页面选择 QWidget 作为窗体基类,并选中“Generate form”复选框。创建后的项目文件目录树如图 1 所示。
图 1 项目文件的目录树
这个项目包含以下一些文件:
项目管理文件 samp2_1.pro,存储项目设置的文件。
主程序入口文件 main.cpp,实现 main()函数的程序文件。
窗体界面文件 widget.ui,一个 XML 格式存储的窗体上的元件及 其布局的文件。
widget.h 是所设计的窗体类的头文件,widget.cpp 是 widget.h 里 定义类的实现文件。C++中,任何窗体或界面组件都是用类封装的,一个类一般有一个头文件(.h 文件)和一个源程序文件(.cpp 文件)。
本节先来介绍一下项目管理文件(.pro文件)。 后缀为“.pro”的文件是项目的管理文件,文件名就是项目的名称,如本项目中的 samp2_1.pro。 下面是 samp2_1.pro 文件的内容。
1 | QT += core gui |
项目管理文件用于记录项目的一些设置,以及项目包含文件的组织管理。 “Qt += core gui”表示项目中加入 core gui 模块。core gui 是 Qt 用于 GUI 设计的类库模块,如果创建的是控制台(Console)应用程序,就不需要添加 core gui。 Qt 类库以模块的形式组织各种功能的类,根据项目涉及的功能需求,在项目中添加适当的类库模块支持。例如,如果项目中使用到了涉及数据库操作的类就需要用到 sql 模块,在 pro 文件中需要增加如下一行:
Qt +=sql
samp2_1.pro 中的第 2 行是:
greaterThan(Qt_MAJOR_VERSION, 4): Qt += widgets
这是个条件执行语句,表示当 Qt 主版本大于 4 时,才加入 widgets 模块。 “TARGET = samp2_1”表示生成的目标可执行文件的名称,即编译后生成的可执行文件是 samp2_1.exe。 “TEMPLATE = app”表示项目使用的模板是 app,是一般的应用程序。 后面的 SOURCES、HEADERS、FORMS 记录了项目中包含的源程序文件、头文件和窗体文件(.ui 文件)的名称。这些文件列表是 Qt Creator 自动添加到项目管理文件里面的,用户不需要手动修改。当添加一个文件到项目,或从项目里删除一个文件时,项目管理文件里的条目会自动修改。
- Qt项目界面文件(.ui)及其作用(超详细)
Qt项目中,后缀为“.ui”的文件是可视化设计的窗体的定义文件,如 widget.ui。双击项目文件目录树中的文件 widget.ui,会打开一个集成在 Qt Creator 中的 Qt Designer 对窗体进行可视化设计,如图 1 所示。

本教程后面将称这个集成在 Qt Creator 中的 Qt Designer 为“UI 设计器”,以便与独立运行的 Qt Designer 区别开来。 图 1 中的 UI 设计器有以下一些功能区域:
组件面板:窗口左侧是界面设计组件面板,分为多个组,如Layouts、Buttons、Display Widgets等,界面设计的常见组件都可以在组件面板里找到。
中间主要区域是待设计的窗体。如果要将某个组件放置到窗体上时,从组件面板上拖放一个组件到窗体上即可。例如,先放一个 Label 和一个 Push Button 到窗体上。
Signals 和 Slots 编辑器与 Action 编辑器是位于待设计窗体下方的两个编辑器。Signals 和Slots 编辑器用于可视化地进行信号与槽的关联,Action 编辑器用于可视化设计 Action。
布局和界面设计工具栏:窗口上方的一个工具栏,工具栏上的按钮主要实现布局和界面设计。
对象浏览器(Object Inspector):窗口右上方是 Object Inspector,用树状视图显示窗体上各组件之间的布局包含关系,视图有两列,显示每个组件的对象名称(ObjectName)和类名称。
属性编辑器(Property Editor):窗口右下方是属性编辑器,是界面设计时最常用到的编辑器。属性编辑器显示某个选中的组件或窗体的各种属性及其取值,可以在属性编辑器里修改这些属性的值。
图 2 显示的是选中窗体上放置的标签组件后属性编辑器的内容。最上方显示的文字“LabDemo: QLabel”表示这个组件是一个 QLabel 类的组件,objectName 是LabDemo。

属性编辑器的内容分为两列,分别为属性的名称和属性的值。属性又分为多个组,实际上表示了类的继承关系,如在图 2 中,可以看出 QLabel 的继承关系是QObject→QWidget→QFrame→QLabel。 objectName 表示组件的对象名称,界面上的每个组件都需要一个唯一的对象名称,以便被引用。界面上的组件的命名应该遵循一定的法则,具体使用什么样的命名法则根据个人习惯而定,主要目的是便于区分和记忆,也要便于与普通变量相区分。 设置其他属性的值只需在属性编辑器里操作即可,如设置 LabDemo 的 text 属性为“Hello,World”,只需像图 2 那样修改 text 属性的值即可。
提示,标准 C++语言里并没有 property 关键字,property 是 Qt 对标准 C++ 的扩展,使得在 Qt Designer 里就可以可视化设置类的数据。
在图 1 显示的设计窗体上,放置一个 Label 和一个 Push Button 组件,它们的主要属性设置见表 3。
| ObjectName | 类名称 | 属性设置 | 备注 |
|---|---|---|---|
| LabDemo | QLabel | Text=”Hello, World” Font.PointSize=20 Font.bold=true | 设置标签显示文字和字体 |
| btnClose | QPushButton | Text=”Close” | 设置按钮的文字 |
编辑完属性之后,再为 btnClose 按钮增加一个功能,就是单击此按钮时,关闭窗口,退出程序。使用 Signals 和 Slots 编辑器完成这个功能,如图 4 所示。

在信号与槽编辑器的工具栏上单击“Add”按钮,在出现的条目中,Sender 选择 btnClose,Signal 选择 clicked(),Receiver 选择窗体 Widget,Slot 选择 close()。这样设置表示当按钮 btnClose 被单击时,就执行 Widget 的 close() 函数,实现关闭窗口的功能。 然后对项目进行编译和运行,可以出现如图 5 所示的窗口,单击“Close”按钮可以关闭程序。
图 5 具有 Close 按钮的“Hello World”程序
标签的文字内容和字体被修改了,窗口标题也显示为所设置的标题,而我们并没有编写一行程序语句,Qt 是怎么实现这些功能的呢? 为了搞清楚窗体类的定义,以及界面功能的实现原理,这里将项目进行编译。编译后在项目目录下会自动生成一个文件 ui_widget.h,这样对于一个窗体,就有 4 个文件了,各文件的功能说明见表 6。
| 文件名 | 功能 |
|---|---|
| widget.h | 定义窗体类的头文件,定义了类Widget |
| widget.cpp | Widget 类的功能实现源程序文件 |
| widget.ui | 窗体界面文件,由UI设计器自动生成,存储了窗体上各个组件的属性设置和布局 |
| ui_widget.h | 编译后,根据窗体上的组件及其属性、信号与槽的关联等自动生成的一个类的定义文件,类的名称是Ui_Widget |
下面分别分析各个文件的内容及其功能,以及它们是如何联系在一起工作,实现界面的创建与显示的。
widget.h 文件
widget.h 文件是窗体类的头文件。在创建项目时,选择窗体基类是 QWidget,在 widget.h 中定义了一个继承自 QWidget 的类 Widget。 下面是 widget.h 文件的内容:
1 | #ifndef WIDGET_H |
widget.h 文件有几个重要的部分。
namespace 声明
代码中有如下的一个 namespace 声明:
1 | namespace Ui { |
这是声明了一个名称为 Ui 的命名空间(namespace),包含一个类 Widget。但是这个类 Widget 并不是本文件里定义的类 Widget,而是 ui_widget.h 文件里定义的类,用于描述界面组件的。这个声明相当于一个外部类型声明(具体要看完 ui_widget.h 文件内的解释之后才能搞明白)。
Widget 类的定义
widget.h 文件的主体部分是一个继承于 QWidget 的类 Widget 的定义,也就是本实例的窗体类。 在 Widget 类中使用了宏 Q_OBJECT,这是使用 Qt 的信号与槽(signal 和 slot)机制的类都必须加入的一个宏(信号与槽在后面详细介绍)。 在 public 部分定义了 Widget 类的构造函数和析构函数。 在 private 部分又定义了一个指针。
Ui::Widget *ui;
这个指针是用前面声明的 namespace Ui 里的 Widget 类定义的,所以指针 ui 是指向可视化设计的界面,后面会看到要访问界面上的组件,都需要通过这个指针 ui。
widget.cpp 文件
widget.cpp 文件是类 Widget 的实现代码,下面是 widget.cpp 文件的内容。
1 | #include "widget.h" |
注意到,在这个文件的包含文件部分自动加入了如下一行内容:
#include “ui_widget.h”
这个就是 Qt 编译生成的与 UI 文件 widget.ui 对应的类定义文件。 目前只有构造函数和析构函数。其中构造函数头部是:
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
其意义是:执行父类 QWidget 的构造函数,创建一个 Ui::Widget 类的对象 ui。这个 ui 就是 Widget 的 private 部分定义的指针变量 ui。 构造函数里只有一行语句:
ui->setupUi(this)
它是执行了 Ui::Widget 类的 setupUi() 函数,这个函数实现窗口的生成与各种属性的设置、信号与槽的关联(后面会具体介绍)。 析构函数只是简单地删除用 new 创建的指针 ui。 所以,在 ui_widget.h 文件里有一个 namespace 名称为 Ui,里面有一个类 Widget 是用于描述可视化设计的窗体,且与 widget.h 里定义的类同名。在 Widget 类里访问 Ui::Widget 类的成员变量或函数需要通过 Widget 类里的 ui 指针,如同构造函数里执行 ui->setupUi( this) 函数那样。
widget.ui 文件
widget.ui 是窗体界面定义文件,是一个 XML 文件,定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等。用UI设计器可视化设计的界面都由 Qt 自动解析,并以 XML 文件的形式保存下来。在设计界面时,只需在 UI 设计器里进行可视化设计即可,而不用管 widget.ui 文件是怎么生成的。 下面是 widget.ui 文件的内容:
1 | <?xml version="1.0" encoding="UTF-8"?> |
ui_widget.h 文件
ui_widget.h 是在对 widget.ui 文件编译后生成的一个文件,ui_widget.h 会出现在编译后的目录下,或与 widget.ui 同目录(与项目的 shadow build 编译设置有关)。 文件 ui_widget.h 并不会出现在 Qt Creator 的项目文件目录树里,当然,可以手工将 ui_widget.h 添加到项目中。方法是在项目文件目录树上,右击项目名称节点,在调出的快捷菜单中选择“Add Existing Files…”,找到并添加 ui_widget.h 文件即可。 注意,ui_widget.h 是对 widget.ui 文件编译后自动生成的,widget.ui 又是通过 UI 设计器可视化设计生成的。所以,对 ui_widget.h 手工进行修改没有什么意义,所有涉及界面的修改都应该直接在UI 设计器里进行。所以,ui_widget.h 也没有必要添加到项目里。 下面是 ui_widget.h 文件的内容:
1 | /********************************************************************************** Form generated from reading UI file 'widget.ui'**** Created by: Qt User Interface Compiler version 5.9.1**** WARNING! All changes made in this file will be lost when recompiling UI file!********************************************************************************/#ifndef UI_WIDGET_H#define UI_WIDGET_H#include <QtCore/QVariant>#include <QtWidgets/QAction>#include <QtWidgets/QApplication>#include <QtWidgets/QButtonGroup>#include <QtWidgets/QHeaderView>#include <QtWidgets/QLabel>#include <QtWidgets/QPushButton>#include <QtWidgets/QWidget>QT_BEGIN_NAMESPACEclass Ui_Widget{public: QLabel *label; QPushButton *btnClose; void setupUi(QWidget *Widget) { if (Widget->objectName().isEmpty()) Widget->setObjectName(QStringLiteral("Widget")); Widget->resize(336, 216); label = new QLabel(Widget); label->setObjectName(QStringLiteral("label")); label->setGeometry(QRect(100, 70, 141, 61)); QFont font; font.setPointSize(12); font.setBold(true); font.setWeight(75); label->setFont(font); btnClose = new QPushButton(Widget); btnClose->setObjectName(QStringLiteral("btnClose")); btnClose->setGeometry(QRect(210, 150, 75, 23)); retranslateUi(Widget); QObject::connect(btnClose, SIGNAL(clicked()), Widget, SLOT(close())); QMetaObject::connectSlotsByName(Widget); } // setupUi void retranslateUi(QWidget *Widget) { Widget->setWindowTitle(QApplication::translate("Widget", "My First Demo", Q_NULLPTR)); label->setText(QApplication::translate("Widget", "Hello\357\274\214World", Q_NULLPTR)); btnClose->setText(QApplication::translate("Widget", "Close", Q_NULLPTR)); } // retranslateUi};namespace Ui { class Widget: public Ui_Widget {};} // namespace UiQT_END_NAMESPACE#endif // UI_WIDGET_H |
查看 ui_widget.h 文件的内容,发现它主要做了以下的一些工作:
定义了一个类 Ui_Widget,用于封装可视化设计的界面。
自动生成了界面各个组件的类成员变量定义。在 public 部分为界面上每个组件定义了一个指针变量,变量的名称就是设置的 objectName。比如,在窗体上放置了一个 QLabel 和一个 QPushButton 并命名后,自动生成的定义是:
QLabel *LabDemo; QPushButton *btnClose;
- 定义了 setupUi() 函数,这个函数用于创建各个界面组件,并设置其位置、大小、文字内容、字体等属性,设置信号与槽的关联。setupUi() 函数体的第一部分是根据可视化设计的界面内容,用 C++ 代码创建界面上各组件,并设置其属性。 接下来,setupUi() 调用了函数 retranslateUi(Widget),用来设置界面各组件的文字内容属性,如标签的文字、按键的文字、窗体的标题等。将界面上的文字设置的内容独立出来作为一个函数 retranslateUi(),在设计多语言界面时会用到这个函数。 setupUi() 函数的第三部分是设置信号与槽的关联,本文件中有以下两行:
QObject::connect(btnClose, SIGNAL(clicked()), Widget, SLOT(close())); QMetaObject::connectSlotsByName(Widget);
第1 行是调用 connect() 函数,将在 UI 设计器里设置的信号与槽的关联转换为语句。这里是将 btnClose 按键的 clicked() 信号与窗体 Widget 的 close() 槽函数关联起来,就是在图 4 中设置的信号与槽的关联的程序语句实现。这样,当单击 btnClose 按钮时,就会执行 Widget 的 close() 槽函数,而 close() 槽函数的功能是关闭窗口。 第 2 行是设置槽函数的关联方式,用于将 UI 设计器自动生成的组件信号的槽函数与组件信号相关联。 所以,在Widget 的构造函数里调用 ui->setupUI(this),就实现了窗体上组件的创建、属性设置、信号与槽的关联。
- 定义 namespace Ui,并定义一个从Ui_Widget 继承的类Widget。
namespace Ui { class Widget: public Ui_Widget {}; }
提示:ui_widget.h 文件里实现界面功能的类是 Ui_Widget。再定义一个类 Widget 从 Ui_Widget 继承而来,并定义在 namespace Ui 里,这样 Ui:: Widget 与 widget.h 里的类 Widget 同名,但是用 namespace 区分开来。所以,界面的 Ui:: Widget 类与文件 widget.h 里定义的 Widget 类实际上是两个类,但是 Qt 的处理让用户感觉不到 Ui:: Widget 类的存在,只需要知道在 Widget 类里用 ui 指针可以访问可视化设计的界面组件就可以了。
- Qt项目中main主函数及其作用
main.cpp 是实现 main() 函数的文件,下面是 main.cpp 文件的内容。
1 | #include "widget.h" |
main() 函数是应用程序的入口。它的主要功能是创建应用程序,创建窗口,显示窗口,并运行应用程序,开始应用程序的消息循环和事件处理。 QApplication 是 Qt的标准应用程序类,第 1 行代码定义了一个 QApplication 类的实例 a,就是应用程序对象。 然后定义了一个 Widget 类的变量 w,Widget 是本实例设计的窗口的类名,定义此窗口后再用 w.show() 显示此窗口。 最后一行用 a.exec() 启动应用程序的执行,开始应用程序的消息循环和事件处理。
- Qt界面布局管理详解
在上一节,通过一个简单的应用程序,分析了 Qt创建的 GUI 应用程序中各个文件的作用,剖析了可视化设计的UI文件是如何被转换为 C++的类定义,并自动创建界面的。这些是使用 Qt Creator 可视化设计用户界面,并使各个部分融合起来运行的基本原理。 本节再以一个稍微复杂的例子来讲解设计 GUI 的常见功能,包括界面设计时布局的管理,以及程序里如何访问界面组件。
实例程序功能
创建一个 Widget Application 项目 samp2_2,在创建窗体时选择基类 QDialog,生成的类命名为 QWDialog,并选择生成窗体。 如此新建的项目 samp2_2 有一个界面文件 qwdialog.ui,一个头文件 qwdialog.h 和源程序文件 qwdialog.cpp。此外,还有项目文件 samp2_2.pro 和主程序文件 main.cpp。 qwdialog.ui 界面文件设计时界面如图 1 所示。程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置。

在界面设计时,对需要访问的组件修改其 objectName,如各个按钮、需要读取输入的编辑框、需要显示结果的标签等,以便在程序里区分。对于不需要程序访问的组件则无需修改其 objectName,如用于界面上组件分组的 GroupBox、Frame、布局等,让 UI 设计器自动命名即可。 对图 1 中几个主要组件的命名、属性设置见表 2。
| 对象名 | 类名称 | 属性设置 | 备注 |
|---|---|---|---|
| txtEdit | QPlainTextEdit | Text=”Hello, World It is my demo. “ Font.PointSize=20 | 用于显示文字内容,可编辑 |
| chkBoxUnder | QCheckBox | Text=”Underline” | 设置字体为下划线 |
| chkBoxItalic | QCheckBox | Text=”Italic” | 设置字体为斜体 |
| chkBoxBold | QCheckBox | Text=”Bold” | 设置字体为粗体 |
| rBtnBlack | QRadioButton | Text=”Black” | 字体颜色为黑色 |
| rBtnRed | QRadioButton | Text=”Red” | 字体颜色为红色 |
| rBtnBlue | QRadioButton | Text=”Blue” | 字体颜色为蓝色 |
| btnOK | QPushButton | Text=”确定” | 返回确定,并关闭窗口 |
| btnCancel | QPushButton | Text=”取消” | 返回取消,并关闭窗口 |
| btnClose | QPushButton | Text=”退出” | 退出程序 |
| QWDialog | QWDialog | windowTitle=”Dialog by Designer” | 界面窗口的类名称是QWDialog,objectName 不要修改 |
对于界面组件的属性设置,需要注意以下几点。
objectName 是窗体上创建的组件的实例名称,界面上的每个组件需要有一个唯一的 objectName,程序里访问界面组件时都是通过其 objectName 进行访问,自动生成的槽函数名称里也有 objectName。所以,组件的 objectName 需要在设计程序之前设置好,设置好之后一般不要再改动。若设计程序之后再改动 objectName,涉及的代码需要相应的改动。
窗体的 objectName 就是窗体的类名称,在 UI 设计器里不要修改窗体的 objectName,窗体的实例名称需要在使用窗体的代码里去定义。
界面组件布局
Qt 的界面设计使用了布局(Layout)功能。所谓布局,就是界面上组件的排列方式,使用布局可以使组件有规则地分布,并且随着窗体大小变化自动地调整大小和相对位置。布局管理是 GUI 设计的必备技巧,下面逐步讲解如何实现图 1 所示的界面设计。
界面组件的层次关系
为了将界面上的各个组件的分布设计得更加美观,经常使用一些容器类,如 QgoupBox、QtabWidget、QFrame 等。 例如,将 3 个 CheckBox 组件放置在一个 GroupBox 组件里,该 GroupBox 组件就是这 3 个 CheckBox 的容器,移动这个 GroupBox 就会同时移动其中的 3 个 CheckBox。

图 3 显示的是设计图 1 界面的前期阶段。在窗体上放置了 2 个 GroupBox 组件,在 groupBox1 里放置 3 个 CheckBox 组件,在 groupBox2 里放置 3 个 RadioButton 组件。图 3 右侧 Object Inspector 里显示了界面上各组件之间的层次关系。
布局管理
Qt 为界面设计提供了丰富的布局管理功能,在 UI 设计器中,组件面板里有 Layouts 和 Spacers 两个组件面板,在窗体上方的工具栏里有布局管理的按钮(如图 4 所示)。

Layouts 和 Spacers 两个组件面板里的布局组件的功能见表 5:
| 布局组件 | 功能 |
|---|---|
| Vertical Layout | 垂直方向布局,组件自动在垂直方向上分布 |
| Horizontal Layout | 水平方向布局,组件自动在水平方向上分布 |
| Grid Layout | 网格状布局,网状布局大小改变时,每个网格的大小都改变 |
| Form Layout | 窗体布局,与网格状布局类似,但是只有最右侧的一列网格会改变大小 |
| Horizontal Spacer | 一个用于水平分隔的空格 |
| Vertical Spacer | 一个用于垂直分隔的空格 |
使用组件面板里的布局组件设计布局时,先拖放一个布局组件到窗体上,如在设计图 4 中 3 个按钮的布局时,先放一个 Horizontal Layout 到窗体上,布局组件会以红色边框显示。再往布局组件里拖放 3 个 Push Button 和 2 个 Horizontal Spacer,就可以得到图 1 中 3 个按钮的水平布局效果。 在设计窗体的上方有一个工具栏,用于调整设计器进入不同的状态,以及进行布局设计,工具栏上各按钮的功能见表 6。
| 按钮及快捷键 | 功能 |
|---|---|
| Edit Widget (F3) | 界面设计进入编辑状态,就是正常的设计状态 |
| Edit Signals/Slots(F4) | 进入信号与槽的可视化设计状态 |
| Edit Buddies | 进入伙伴关系编辑状态,可以设置一个Label 与一个组件成为伙伴关系 |
| Edit Tab Order | 进入Tab 顺序编辑状态,Tab 顺序是在键盘上按Tab 键时,输入焦点在界面各组件之间跳动的顺序 |
| Lay Out Horizontally (Ctrl+H) | 将窗体上所选组件水平布局 |
| Lay Out Vertically (Ctrl+L) | 将窗体上所选组件垂直布局 |
| Lay Out Horizontally in Splitter | 将窗体上所选组件用一个分割条进行水平分割布局 |
| Lay Out Vertically in Splitter | 将窗体上所选组件用一个分割条进行垂直分割布局 |
| Lay Out in a Form Layout | 将窗体上所选组件按窗体布局 |
| Lay Out in a Grid | 将窗体上所选组件网格布局 |
| Break Layout | 解除窗体上所选组件的布局,也就是打散现有的布局 |
| Adjust Size(Ctrl+J) | 自动调整所选组件的大小 |
使用工具栏上的布局控制按钮时,只需在窗体上选中需要设计布局的组件,然后点击某个布局按钮即可。在窗体上选择组件时同时按住 Ctrl 键,可以实现组件多选,选择某个容器类组件,相当于选择了其内部的所有组件。 例如,在图 3 的界面中,选中 groupBox1,然后单击“Lay Out Horizontally”工具栏按钮,就可以对 groupBox1 内的 3 个 CheckBox 水平布局。 在图 4 的界面上,使 groupBox1 里的 3 个 CheckBox 水平布局,groupBox2 里的 3 个 RadioButton 水平布局,下方 3个按钮水平布局。在窗体上又放置了一个 PlainTextEdit 组件。现在,改变 groupBox1、groupBox2 或按钮的水平布局的大小,其内部组件都会自动改变大小。但是当改变窗体大小时,界面上的各组件却并不会自动改变大小。 随后还需为窗体指定一个总的布局。选中窗体(即不要选择任何组件),单击工具栏上的“Lay Out Vertically”按钮,使 4 个组件垂直分布。这样布局后,当窗体大小改变时,各个组件都会自动改变大小。 在 UI 设计器里可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度和高度来实现某些需要的布局效果。
伙伴关系与 Tab 顺序
在 UI 设计工具栏上单击“Edit Buddies”按钮可以进入伙伴关系编辑状态,如设计一个窗体时,进入伙伴编辑状态之后的界面如图 7 所示。

伙伴关系(Buddy)是指界面上一个 Label 和一个组件相关联,如图 7 中的伙伴关系编辑状态,单击一个 Label,按住鼠标左键,然后拖向一个组件,就建立了 Label 和组件之间的伙伴关系。 伙伴关系是为了在程序运行时,在窗体上用快捷键快速将输入焦点切换到某个组件上。例如,在图 7 的界面上,设定“姓名”标签的 Text 属性为“姓名(&N)”,其中符号“&”用来指定快捷字符,界面上并不显示“&”,这里指定快捷字母为 N。那么程序运行时,用户按下 Alt+N,输入焦点就会快速切换到“姓名”关联的输入框内。

在 UI 设计器工具栏上单击“Edit Tab Order”按钮进入Tab 顺序编辑状态(如图 8 所示)。Tab 顺序是指在程序运行时,按下键盘上的 Tab 键时输入焦点的移动顺序。一个好的用户界面,在按 Tab 键时,焦点应该以合理的顺序在界面上移动,而不是随意地移动。 进入 Tab 顺序编辑状态后,在界面上会显示具有 Tab 顺序组件的编号,依次按希望的顺序单击组件,就可以重排 Tab 顺序了。没有输入焦点的组件是没有 Tab 顺序的,如 Label 组件。
项目功能实现
下面开始设计程序功能。对于该程序,希望它的功能如下:
单击 UnderLine、Italic、Bold 3 个 CheckBox 时,根据其状态,设置 PlainTextEdit 里的文字的字体样式;
Black、Red、Blue 3 个 RadioButton 是互斥选择的,单击某个 RadioButton 时,设置文字的颜色;
单击“确定”“取消”或“退出”按钮时,关闭窗口,退出程序。
字体样式设置
窗体在设计模式下,选中 chkBoxUnder 组件,单击右键调出其快捷菜单。在快捷菜单中单击菜单项“Go to slot…”(中文状态为“转到槽”),出现如图 9 所示的对话框。
图 9 QcheckBox的Go to slot对话框
该对话框列出了 QCheckBox 类的所有信号,第一个是 clicked(),第二个是带一个布尔类型参数的 clicked(bool)。 信号 clicked(bool) 会将 CheckBox 组件当前的选择状态作为一个参数传递,在响应代码里可以直接利用这个传递的参数。而如果用信号 clicked(),则需要在代码里读取 CheckBox 组件的选中状态。为了简化代码,选择 clicked(bool) 信号。 选择 clicked(bool),然后单击“OK”按钮,在 QWDialog 的类定义中,会在 private slots 部分自动增加一个槽函数声明,函数名是根据发射对象及其信号名称自动命名的。
void on_chkBoxUnder_clicked(bool checked);
同时,在 qwdialog.cpp 文件中自动添加了函数 on_chkBoxUnder_clicked(bool) 的框架,在此函数中添加如下的代码,实现文本框字体下划线的控制。
1 | void QWDialog::on_chkBoxUnder_clicked(bool checked) |
以同样的方法为 Italic 和 Bold 两个 CheckBox设计槽函数,编译后运行,发现已经实现了修改字体的下划线、斜体、粗体属性的功能,说明信号与槽函数已经关联了。 但是,查看 QWDialog 的构造函数,构造函数只有简单的一条语句。
1 | QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog) |
这里没有发现用 connect() 函数进行几个 CheckBox 的信号与槽函数关联的操作。这些功能是如何实现的呢? 查看编译生成的 ui_qwdialog.h 文件。构造函数里调用的 setupUi() 是在 ui_qwdialog.h 文件里实现的。查看 setupUi() 函数的内容,也没有发现用 connect() 函数进行几个 CheckBox 的信号与槽关联的操作,只是在 setupUI()里发现了如下的一条语句:
QMetaObject::connectSlotsByName(QWDialog);
秘密就在于这条语句。connectSlotsByName(QWDialog) 函数将搜索 QWDialog 界面上的所有组件,将信号与槽函数匹配的信号和槽关联起来,它假设槽函数的名称是:
void on_
例如,通过 UI 设计器的操作,为 chkBoxUnder 自动生成的槽函数是:
void on_chkBoxUnder_clicked(bool checked);
它就正好是 chkBoxUnder 的信号 clicked(bool) 的槽函数。那么,connectSlotsByName() 就会将此信号和槽函数关联起来,如同执行了下面的这样一条语句:
connect(chkBoxUnder, SIGNAL(clicked (bool)), this, SLOT (on_chkBoxUnder_clicked (bool));
这就是用 UI 设计器可视化设计某个组件的信号响应槽函数,而不用手工去将其关联起来的原因,都是在界面类的构造函数里调用 setupUi() 自动完成了关联。
字体颜色设置
设置字体的 3 个 RadioButton 是互斥性选择的,即一次只有一个 RadioButton 被选中,虽然也可以采用可视化设计的方式设计其 clicked() 信号的槽函数,但是这样就需要生成 3 个槽函数。这里可以简化设计,即设计一个槽函数,将 3 个 RadioButton 的 clicked() 信号关联到这一个槽函数。 为此,在 QWDialog 类的 private slots 部分增加一个槽函数定义如下:
void setTextFontColor();
提示 将鼠标光标移动到这个函数的函数名上面,单击右键,在弹出的快捷菜单中选择“Refactor”→“Add Definition in qwdialog.cpp”,就可以在 qwdialog.cpp 文件中自动为函数 setTextFontColor() 生成一个函数框架。 在 qwdialog.cpp 文件中,为 setTextFontColor() 编写实现代码如下:
1 | void QWDialog::setTextFontColor() |
由于这个槽函数是自定义的,所以不会自动与 RadioButton 的 clicked() 事件关联,此时编译后运行程序不会实现改变字体颜色的功能。需要在 QWDialog 的构造函数中手工进行关联,代码如下:
1 | QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog) |
在构造函数中将 3 个 RadioButton 的 clicked() 信号与同一个槽函数 setTextFontColor() 相关联。再编译后运行,就可以更改文字的颜色了。
三个按钮的功能设计
界面上还有“确定”“取消”“退出”3 个按钮,这是在对话框中常见的按钮。“确定”表示确认选择并关闭对话框,“取消”表示取消选择并关闭对话框,“退出”则直接关闭对话框。 QWDialog 是从 QDialog 继承而来的,QDialog 提供了 accept()、reject()、close() 等槽函数来表示这三种状态,只需将按钮的 clicked() 信号与相应槽函数关联即可。 下面采用可视化的方式,将按钮的 clicked() 信号与这些槽函数关联起来。在 UI 设计器里,单击上方工具栏里的“Edit Signals/Slots”按钮,窗体进入信号与槽函数编辑状态,如图 10 所示。

将鼠标移动到“确定”按钮上方,再按下鼠标左键,移动到窗体的空白区域释放左键,这时出现如图 11 所示的关联设置对话框。

在图 11 中,左侧的列表框里显示了 btnOK 的信号,选择 clicked(),右边的列表框里显示了 QWDialog 的槽函数,选择 accept(),单击“OK”按钮。 同样的方法可以将 btnCancel 的 clicked() 信号与 QWDialog 的 reject() 槽函数关联,将 btnClose 的 clicked() 信号与 QWDialog 的 close() 槽函数关联。
注意,在图 11 的右侧列表框中没有 close() 槽函数,需要勾选下方的“Show signals and slots inherited from QWidget”才会出现 close() 函数。
设置完 3 个按钮的信号与槽关联之后,在窗体下方的 Signals 和 Slots 编辑器里也显示了这 3 个关联。实际上,可以直接在 Signals 和 Slots 编辑器进行关联设置。现在编译并运行程序,单击这 3 个按钮都会关闭程序。 那么,这 3 个按钮的信号与槽函数的关联是在哪里实现的呢?答案在 setupUi() 函数里,在 setupUi() 函数里自动增加了以下 3 行代码:
1 | QObject::connect(btnOK, SIGNAL(clicked()), QWDialog, SLOT(accept())); |
这个实例程序的功能全部完成了。采用 UI 设计器设计了窗体界面,采用可视化和程序化的方式设计槽函数,设计信号与槽函数之间的关联。 从以上的设计过程可以看到,Qt Creator 和 UI 设计器为设计应用程序提供了强大的可视化设计功能。
- Qt信号与槽机制详解
信号与槽(Signal & Slot)是 Qt编程的基础,也是 Qt 的一大创新。因为有了信号与槽的编程机制,在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单。信号(Signal)就是在特定情况下被发射的事件,例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。 GUI 程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。 信号与槽关联是用 QObject::connect() 函数实现的,其基本格式是:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect() 是 QObject 类的一个静态函数,而 QObject 是所有 Qt 类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中,sender 是发射信号的对象的名称,signal() 是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver 是接收信号的对象名称,slot() 是槽函数的名称,需要带括号,有参数时还需要指明参数。 SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。例如,在 samp2_1(前面章节中的项目)的 ui_widget.h 文件中,在 setupUi() 函数中有如下的语句:
QObject::connect(btnClose, SIGNAL(clicked()), Widget, SLOT(close()));
其作用就是将 btnClose 按钮的 clicked() 信号与窗体(Widget)的槽函数 close() 相关联,这样,当单击 btnClose 按钮(就是界面上的“Close”按钮)时,就会执行 Widget 的 close() 槽函数。 关于信号与槽的使用,有以下一些规则需要注意:
- 一个信号可以连接多个槽,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int)); connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));
这是当一个对象 spinNum 的数值发生变化时,所在窗体有两个槽进行响应,一个 addFun()用于计算,一个 updateStatus() 用于更新状态。 当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行。 当信号和槽函数带有参数时,在 connect()函数里,要写明参数的类型,但可以不写参数名称。
- 多个信号可以连接同一个槽,例如在 samp2_2(前面章节中的项目)中,让三个选择颜色的 RadioButton的clicked() 信号关联到相同的一个自定义槽函数 setTextFontColor()。
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
这样,当任何一个 RadioButton 被单击时,都会执行 setTextFontColor() 函数。
- 一个信号可以连接另外一个信号,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SIGNAL (refreshInfo(int));
这样,当一个信号发射时,也会发射另外一个信号,实现某些特殊的功能。
严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT。
当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。
信号与槽机制是 Qt GUI 编程的基础,使用信号与槽机制可以比较容易地将信号与响应代码关联起来。
- Qt纯代码设计UI实例分析
UI 的可视化设计是对用户而言的,其实底层都是 C++的代码实现,只是 Qt巧妙地进行了处理,让用户省去了很多繁琐的界面设计工作。 由于界面设计的底层其实都是由 C++ 语言实现的,底层实现的功能比可视化设计更加强大和灵活。某些界面效果是可视化设计无法完成的,或者某些人习惯了用纯代码的方式来设计界面,就可以采用纯代码的方式设计界面,如 Qt 自带的实例基本都是用纯代码方式实现用户界面的。 所以,本节介绍一个用纯代码方式设计 UI 的实例,通过实例了解用纯代码设计 UI 的基本原理。与前面的可视化 UI 设计相对应,且称之为代码化 UI 设计。
实例功能
首先建立一个 Widget Appliation 项目 samp2_3,在创建项目向导中选择基类时,选择基类 QDialog,新类的名称命名为 QWDlgManual,关键是取消创建窗体,即不勾选“Generate form”(创建界面)复选框。创建后的项目文件目录树下没有 qwdlgmanual.ui 文件。 该项目通过代码创建一个对话框,实现与 samp2_2 类似的界面和功能。本例完成后的运行效果如图 1 所示,其界面和功能与 samp2_2 类似。
图 1 实例 samp2_3 运行效果
界面创建
QWDlgManual 类定义
完成功能后的 qwdlgmanual.h 文件中 QWDlgManual 类的完整定义如下:
1 | #include <QDialog> |
在 QWDlgManual 类的 private 部分,声明了界面上的各个组件的指针变量,这些界面组件都需要在 QWDlgManual 类的构造函数里创建并在窗体上布局。 在 private 部分自定义了两个函数,iniUI() 用来创建所有界面组件,并完成布局和属性设置,iniSignalSlots() 用来完成所有的信号与槽函数的关联。 在 private slots 部分声明了 4 个槽函数,分别是 3 个 CheckBox 的响应槽函数,以及 3 个颜色设置的 RadioButton 的共同响应槽函数。
注意:与可视化设计得到的窗体类定义不同,QWDlgManual 的类定义里没有指向界面的指针 ui。
这几个槽函数的功能与例 samp2_2 中的类似,只是在访问界面组件时,无需使用 ui 指针,而是直接访问 QWDlgManual 类里定义的界面组件的成员变量即可,例如 on_chkBoxUnder() 的代码:
1 | void QWDlgManual::on_chkBoxUnder(bool checked){ QFont font=txtEdit->font(); font.setUnderline(checked); txtEdit->setFont(font);} |
界面的创建,以及信号与槽函数的关联都在 QWDlgManual 的构造函数里完成,构造函数代码如下:
1 | QWDlgManual::QWDlgManual(QWidget *parent) : QDialog(parent){ iniUI(); //界面创建与布局 iniSignalSlots(); //信号与槽的关联 setWindowTitle("Form created mannually");} |
构造函数调用 iniUI() 创建界面组件并布局,调用 iniSignalSlots() 进行信号与槽函数的关联。
界面组件的创建与布局
iniUI() 函数实现界面组件的创建与布局,以及属性设置。下面是 iniUI() 的完整代码:
1 | void QWDlgManual::iniUI(){ //创建 Underline, Italic, Bold 3 个CheckBox,并水平布局 chkBoxUnder=new QCheckBox(tr("Underline")); chkBoxItalic=new QCheckBox(tr("Italic")); chkBoxBold=new QCheckBox(tr("Bold")); QHBoxLayout *HLay1=new QHBoxLayout; HLay1->addWidget(chkBoxUnder); HLay1->addWidget(chkBoxItalic); HLay1->addWidget(chkBoxBold); //创建 Black, Red, Blue 3 个RadioButton,并水平布局 rBtnBlack=new QRadioButton(tr("Black")); rBtnBlack->setChecked(true); rBtnRed=new QRadioButton(tr("Red")); rBtnBlue=new QRadioButton(tr("Blue")); QHBoxLayout *HLay2=new QHBoxLayout; HLay2->addWidget(rBtnBlack); HLay2->addWidget(rBtnRed); HLay2->addWidget(rBtnBlue); //创建确定, 取消, 退出3 个 PushButton, 并水平布局 btnOK=new QPushButton(tr("确定")); btnCancel=new QPushButton(tr("取消")); btnClose=new QPushButton(tr("退出")); QHBoxLayout *HLay3=new QHBoxLayout; HLay3->addStretch(); HLay3->addWidget(btnOK); HLay3->addWidget(btnCancel); HLay3->addStretch(); HLay3->addWidget(btnClose); //创建文本框,并设置初始字体 txtEdit=new QPlainTextEdit; txtEdit->setPlainText("Hello world\n\nIt is my demo"); QFont font=txtEdit->font(); //获取字体 font.setPointSize(20);//修改字体大小 txtEdit->setFont(font);//设置字体 //创建垂直布局,并设置为主布局 QVBoxLayout *VLay=new QVBoxLayout; VLay->addLayout(HLay1); //添加字体类型组 VLay->addLayout(HLay2);//添加字体颜色组 VLay->addWidget(txtEdit);//添加PlainTextEdit VLay->addLayout(HLay3);//添加按键组 setLayout(VLay); //设置为窗体的主布局} |
iniUI() 函数按照顺序完成了如下的功能:
创建 3 个 QCheckBox 组件,这 3 个组件的指针已经定义为 QWDlgManual 的私有变量,然后创建一个水平布局 HLay1,将 3 个 CheckBox 添加到这个水平布局里。
创建 3 个 QRadioButton 组件,并创建一个水平布局 HLay2,将 3 个 RadioButton 添加到这个水平布局里。
创建 3 个 QPushButton 组件,并创建一个水平布局 HLay3,将 3 个 PushButton 添加到这个水平布局里,并适当添加水平空格。
创建一个 QPlainTextEdit 组件,设置其文字内容,并设置其字体。
创建一个垂直布局 VLay,将前面创建的 3 个水平布局和文本框依次添加到此布局里。
设置垂直布局为窗体的主布局。
如此创建组件并设置布局后,运行可以得到如图 1 所示的界面效果。这里完全是采用代码来实现组件创建与布局的设置,而这些功能在可视化设计中是由 setupUi() 函数根据界面的可视化设计结果自动实现的。 采用代码设计实现 UI 时,需要对组件的布局有个完整的规划,不如可视化设计直观,且编写代码工作量大。
信号与槽的关联
在纯代码设计 UI 时,信号与槽的关联也需要用代码来完成。函数 iniSignalSlots() 初始化所有的信号与槽的关联,其完整代码如下:
1 | void QWDlgManual::iniSignalSlots(){ //三个颜色 QRadioButton 的clicked()信号与setTextFontColor()槽函数关联 connect(rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor())); //三个字体设置的 QCheckBox 的clicked(bool)信号与相应的槽函数关联 connect(chkBoxUnder,SIGNAL(clicked(bool)), this,SLOT(on_chkBoxUnder(bool))); connect(chkBoxItalic,SIGNAL(clicked(bool)), this,SLOT(on_chkBoxItalic(bool))); connect(chkBoxBold,SIGNAL(clicked(bool)), this,SLOT(on_chkBoxBold(bool))); //三个按钮的信号与窗体的槽函数关联 connect(btnOK,SIGNAL(clicked()),this,SLOT(accept())); connect(btnCancel,SIGNAL(clicked()),this,SLOT(reject())); connect(btnClose,SIGNAL(clicked()),this,SLOT(close()));} |
设计完成后,编译并运行程序,可以得到如图 1 所示的运行效果,且功能与 samp2_2 相同。很显然,采用纯代码方式实现 UI 界面是比较复杂的,代码设计的工作量大而繁琐。
- Qt Creator使用技巧
QtCreator 在设计界面或编辑代码时,有一些快捷键和使用技巧,熟悉这些快捷键和使用技巧,可以提高工作效率。表 1 是 Qt Creator 的一些快捷操作的总结。
| 功能 | 快捷键 | 解释 |
|---|---|---|
| Switch Header/Source | F4 | 在同名的头文件和源程序文件之间切换 |
| Follow Symbol Under Cursor | F2 | 跟踪光标下的符号,若是变量,可跟踪到变量声明的地方;若是函数体或函数声明,可在两者之间切换 |
| Switch Between Function Declaration and Definition | Shift+F2 | 在函数的声明(函数原型)和定义(函数实现)之间切换 |
| Refactor\Rename Symbol Under Cursor | Ctrl+Shift+R | 对光标处的符号更改名称,这将替换到所有用到这个符号的地方 |
| Refactor\Add Definition in .cpp | 为函数原型在 cpp 文件里生成函数体 | |
| Auto-indent Selection | Ctrl+I | 为选择的文字自动进行缩进 |
| Toggle Comment Selection | Ctrl+/ | 为选择的文字进行注释符号的切换,即可以注释所选代码,或取消注释 |
| Context Help | F1 | 为光标所在的符号显示帮助文件的内容 |
| Save All | Ctrl+Shift+S | 文件全部保存 |
| Find/Replace | Ctrl+F | 调出查找/替换对话框 |
| Find Next | F3 | 查找下一个 |
| Build | Ctrl+B | 编译当前项目 |
| Start Debugging | F5 | 开始调试 |
| Step Over | F10 | 调试状态下单步略过,即执行当前行程序语句 |
| Step Into | F11 | 调试状态下跟踪进入,即如果当前行里有函数,就跟踪进入函数体 |
| Toggle Breakpoint | F9 | 设置或取消当前行的断点设置 |
另外,在使用 Qt 时,要善于使用 Qt 自带的帮助文件,对于一个编程语言或类库来说,其自带的帮助文件是最全面最权威的资料。当光标停留在一个类名或函数上时,按 F1 可以调出其帮助文件的内容。 在 Qt Creator 主窗口左侧的主工具栏上有“Help”按钮,单击可以打开 Qt 的帮助文件系统(如图 2 所示),也可以使用“开始”菜单 Qt 程序组里的 Assistant 单独打开帮助系统。

在帮助文件显示界面上,左上方工具栏中有个下拉列表框,可以选择 Bookmarks、Contents、Index 和 Search 4 种模式:
Bookmarks 模式下,左边框里显示已存储的 Bookmarks(书签),任何帮助页面下,点击窗口上方工具栏上的“Add Bookmark”可以添加书签。
Contents 模式下,左边框里以目录树形式显示 Qt 的所有模块(如图 1 所示),可以分类浏览想看的内容。
Index 模式下,可以输入查找内容,左边框里会列出与输入内容前匹配的帮助主题列表。
Search 模式下,可以输入关键字进行搜索。
在 Qt 帮助系统里可以搜索查看每个类的详细资料,如 QTextEdit,可以看到这个类的详细资料,包括在这个类定义的公共类型、属性、公共函数、信号、公共槽等。 另外,若要查看类的继承关系,可以访问 Qt 官网的“Inheritance Hierarchy”页面。
- Qt元对象和属性系统详解
Qt是一个用标准 C++编写的跨平台开发类库,它对标准 C++ 进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。 本节将介绍 Qt 的这些核心特点,对于理解和编写高效的 Qt C++ 程序是大有帮助的。
Qt 的元对象系统
Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。 元对象系统由以下三个基础组成:
QObject 类是所有使用元对象系统的类的基类。
在一个类的 private 部分声明 Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。
MOC(元对象编译器)为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。
构建项目时,MOC 工具读取 C++ 源文件,当它发现类的定义里有 Q_OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的 C++ 源文件,这个生成的源文件连同类的实现文件一起被编译和连接。 除了信号与槽机制外,元对象还提供如下一些功能:
- QObject::metaObject() 函数返回类关联的元对象,元对象类 QMetaObject 包含了访问元对象的一些接口函数,例如 QMetaObject::className() 函数可在运行时返回类的名称字符串。
QObject *obj = new QPushButton; obj->metaObject()->className (); //返回”QPushButton”
QMetaObject::newInstance() 函数创建类的一个新的实例。
QObject::inherits(const char *className) 函数判断一个对象实例是否是名称为 className 的类或 QObject 的子类的实例。例如:
QTimer *timer = new QTimer; // QTimer 是 QObject 的子类 timer->inherits (“QTimer”); // 返回 true timer->inherits (“QObject”); // 返回 true timer->inherits (“QAbstractButton”);//返回 false,不是 QAbstractButton 的子类
QObject::tr() 和 QObject::trUtf8() 函数可翻译字符串,用于多语言界面设计,后续章会专门介绍多语言界面设计。
QObject::setProperty() 和 QObject::property() 函数用于通过属性名称动态设置和获取属性值。
对于 QObject 及其子类,还可以使用 qobject_cast() 函数进行动态投射(dynamic cast)。例如,假设 QMyWidget 是 QWidget 的子类并且在类定义中声明了 Q_OBJECT 宏。创建实例使用下面的语句:
QObject *obj = new QMyWidget;
变量 obj 定义为 QObject 指针,但它实际指向 QMyWidget 类,所以可以正确投射为 QWidget,即:
QWidget *widget = qobject_cast<QWidget *>(obj);
从 QObject 到 QWidget 的投射是成功的,因为 obj 实际是 QMyWidget 类,是 QWidget 的子类。也可以将其成功投射为 QMyWidget,即:
QMyWidget *myWidget = qobject_cast<QMyWidget *>(obj);
投射为 QMyWidget 是成功的,因为 qobject_cast() 并不区分 Qt 内建的类型和用户自定义类型。但是,若要将 obj 投射为 QLabel 则是失败的,即:
QLabel * label - qobject_cast<QLabel *>(obj);
这样投射是失败的,返回指针 label 为 NULL,因为 QMyWidget 不是 QLabel 的子类。 使用动态投射,使得程序可以在运行时对不同的对象做不同的处理。
属性系统
属性定义
Qt 提供一个 Q_PROPERTY() 宏可以定义属性,它也是基于元对象系统实现的。Qt 的属性系统与 C++ 编译器无关,可以用任何标准的 C++ 编译器编译定义了属性的 Qt C++ 程序。 在 QObject 的子类中,用宏 Q_PROPERTY() 定义属性,其使用格式如下:
Q_PROPERTY(type name (READ getFunction [WRITE setFunction] | MEMBER meznberName [(READ getFunction | WRITE setFunction)]) [RESET resetFunction] [NOTIFY notifySignal] [REVISION int] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [CONSTANT] [FINAL])
Q_PROPERTY 宏定义一个返回值类型为 type,名称为 name 的属性,用 READ、WRITE 关键字定义属性的读取、写入函数,还有其他的一些关键字定义属性的一些操作特性。属性的类型可以是 QVariant 支持的任何类型,也可以用户自定义类型。 Q_PROPERTY 宏定义属性的一些主要关键字的意义如下:
READ 指定一个读取属性值的函数,没有 MEMBER 关键字时必须设置 READ。
WRITE 指定一个设定属性值的函数,只读属性没有 WRITE 设置。
MEMBER 指定一个成员变量与属性关联,成为可读可写的属性,无需再设置 READ 和 WRITE。
RESET 是可选的,用于指定一个设置属性缺省值的函数。
NOTIFY 是可选的,用于设置一个信号,当属性值变化时发射此信号。
DESIGNABLE 表示属性是否在 Qt Designer 里可见,缺省为 true。
CONSTANT 表示属性值是一个常数,对于一个对象实例,READ 指定的函数返回值是常数,但是每个实例的返回值可以不一样。具有 CONSTANT 关键字的属性不能有 WRITE 和 NOTIFY 关键字。
FINAL 表示所定义的属性不能被子类重载。
QWidget 类定义属性的一些例子如下:
Q_PROPERTY(bool focus READ hasFocus) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
属性的使用
不管是否用 READ 和 WRITE 定义了接口函数,只要知道属性名称,就可以通过 QObject::property() 读取属性值,并通过 QObject::setProperty() 设置属性值。例如:
QPushButton *button = new QPushButton; QObject *object = button; object->setProperty(“flat”, true); bool isFlat= object->property (“flat”);
动态属性
QObject::setProperty() 函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。 动态属性可以使用 QObject::property() 查询,就如在类定义里用 Q_PROPERTY 宏定义的属性一样。 例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的 required 属性,并设置值为“true”,如:
editName->setProperty(“required”, “true”); comboSex->setProperty(“required”, “true”); checkAgree->setProperty(“required”, “true”);
然后,可以应用下面的样式定义将这种必填字段的背景颜色设置为亮绿色。
*[required=”true”]{background-color:lime}
类的附加信息
属性系统还有一个宏 Q_CLASSINFO(),可以为类的元对象定义“名称——值”信息,如:
1 | class QMyClass:public QObject { |
用 Q_CLASSINFO() 宏定义附加类信息后,可以通过元对象的一些函数获取类的附加信息,如 classlnfo(int) 获取某个附加信息,函数原型定义如下:
QMetaClassInfo QMetaObject::classInfo(int index) const
返回值是 QMetaClassInfo 类型,有 name() 和 value() 两个函数,可获得类附加信息的名称和值。
- Qt全局变量、函数和宏定义详解
<QtGlobal> 头文件包含了 Qt 类库的一些全局定义,包括基本数据类型、函数和宏,一般的 Qt 类的头文件都会包含该文件,所以不用显式包含这个头文件也可以使用其中的定义。
全局变量定义
为了确保在各个平台上各数据类型都有统一确定的长度,Qt 为各种常见数据类型定义了类型符号,如 qint8 就是 signed char 的类型定义,即:
typedef signed char qint8;
| Qt 数据类型 | 等效定义 | 字节数 |
|---|---|---|
| qint8 | signed char | 1 |
| qint16 | signed short | 2 |
| qint32 | signed int | 4 |
| qint64 | long long int | 8 |
| qlonglong | long long int | 8 |
| quint8 | unsigned char | 1 |
| quint16 | unsigned short | 2 |
| quint32 | unsigned int | 4 |
| quint64 | unsigned long long int | 8 |
| qulonglong | unsigned long long int | 8 |
| uchar | unsigned char | 1 |
| ushort | unsigned short | 2 |
| uint | unsigned int | 4 |
| ulong | unsigned long | 8 |
| qreal | double | 8 |
| qfloat16 | 2 |
其中 qreal 缺省是 8 字节 double 类型浮点数,如果 Qt 使用 -qreal float 选项进行配置,就是 4 字节 float 类型的浮点数。 qfloat16 是 Qt 5.9.0 中新增的一个类,用于表示 16 位的浮点数,要使用 qfloat16,需要包含头文件
全局函数定义
| 函数 | 功能 |
|---|---|
| T qAbs(const T &value) | 返回变量 value 的绝对值 |
| const T &qBound(const T &min, const T&value, const T &max) | 返回 value 限定在 min 至 max 范围之内的値 |
| bool qFuzzyComparc(doublc p1, double p2) | 若 p1 和 p2 近似相等,返回 true |
| bool qFuzzyIsNulI(double d) | 如果参数 d 约等于 0,返回 true |
| double qInf(() | 返回无穷大的数 |
| bool qIsFinite(double d) | 若 d 是一个有限的数,返回 true |
| bool qIsInf(double d) | 若 d 是一个无限大的数,返回 true |
| bool qIsNaN(double d) | 若 d 不是一个数,返回 true |
| constT&qMax(const T&value1, const T&value2) | 返回 value1 和 value2 中较大的值 |
| const T &qMin(const T&value1, const T&value2) | 返回 value1 和 value2 中较小的值 |
| qint64 qRound64(double value) | 将 value 近似为最接近的 qint64 整数 |
| int qRound(double value) | 将 value 近似为最接近的 int 整数 |
| int qrand() | 标准 C++ 中 rand() 函数的线程安全型版本,返回 0 至 RAND_MAX 之间的伪随机数 |
| void qsrand(uint seed) | 标准 C++ 中 srand() 函数的线程安全型版本,使用种子 seed 对伪随机数字序列初始化 |
还有一些基础的数学运算函数在
全局宏定义
QT_VERSION:这个宏展开为数值形式 0xMMNNPP (MM = major, NN = minor, PP = patch) 表示 Qt 编译器版本,例如 Qt 编译器版本为 Qt 5.9.1,则 QT_VERSION 为 0x050901。这个宏常用于条件编译设置,根据 Qt 版本不同,编译不同的代码段。
QT_VERSION_CHECK:这个宏展开为 Qt 版本号的一个整数表示,例如:
QT_VERSION_STR:这个宏展开为 Qt 版本号的字符串,如“5.9.0”。
Q_BYTE_ORDER、Q_BIG_ENDIAN 和 Q_LITTLE_ENDIAN:Q_BYTE_ORDER 表示系统内存中数据的字节序,Q_BIG_ENDIAN 表示大端字节序,Q_LITTLE_ ENDIAN 表示小端字节序。在需要判断系统字节序时会用到,例如:
Q_DECL_IMPORT 和 Q_DECL_EXPORT:在使用或设计共享库时,用于导入或导出库的内容,后续章节有其使用实例。
Q_DECL_OVERRIDE:在类定义中,用于重载一个虚函数,例如在某个类中重载虚函数 paintEvem(),可以定义如下:
void paintEvent(QPaintEvent*) Q_DECL_OVERRIDE;
使用 Q_DECL_OVERRIDE 宏后,如果重载的虚函数没有进行任何重载操作,编译器将会报错。
Q_DECL_FINAL:这个宏将一个虚函数定义为最终级别,不能再被重载,或定义一个类不能再被继承,示例如下:
Q_UNUSED(name):这个宏用于在函数中定义不在函数体里使用的参数,示例如下: 在这个函数里,id 参数没有使用。如果不用 QJJNUSED(id) 定义,编译器会出现参数未使用的警告。
foreach(variable, container):foreach 用于容器类的遍历,例如:
forever:forever用于构造一个无限循环,例如:
qDebug(const char * message,…):在debugger窗体显示信息,如果编译器设置了 Qt_NO_DEBUG_OUTPUT,则不作任何输出,例如:
qDebug(“Items in list: %d”, myList.size());
类似的宏还有 qWarning、qCritical、qFatal、qInfo 等,也是用于在 debugger 窗体显示信息。
- Qt顺序容器类和关联容器类详解
Qt提供了多个基于模板的容器类,这些容器类可以用于存储指定类型的数据项,例如常用的字符串列表类 QStringList 就是从容器类 QLiSt
QList
这样定义了一个 QList 容器类的变量 aList,它的数据项是 QString,所以 aList 可以用于处理字符串列表,例如:
aList.append(“Monday”); aList.append(“Tuesday”); aList.append(“Wednesday”); QString str=aList[0];
Qt 的容器类分为顺序容器和关联容器。
顺序容器类
Qt 的顺序容器类有 QList、QLinkedList、QVector、QStack 和 QQueue。
QList
QList 是最常用的容器类,虽然它是以数组列表的形式实现的,但是在其前或后添加数据非常快,QList 以下标索引的方式对数据项进行访问。 QList 用于添加、插入、替换、移动、删除数据项的函数有:insert()、replace()、removeAt()、move()、swap()、append()、prepend()、removeFirst() 和 removeLast() 等。 QList 提供下标索引方式访问数据项,如同数组一样,也提供 at() 函数,例如:
QList
QList 的 isEmpty() 函数在数据项为空时返回 true,size() 函数返回数据项的个数。 QList 是 Qt 中最常用的容器类,很多函数的参数传递都是采用 QList 容器类,例如 QAudioDeviceInfo 的静态函数 availableDevices() 的函数原型是:
QList
其返回数据就是 QAudioDeviceInfo 类型的 QList 列表。
QLinkedList
QLinkedList
QVector
QVector 的数据项是连续存储的。 QStack 程序会依次输出 30, 20, 10。 QQueue 程序会依次输出 10, 20,30。 Qt 还提供关联容器类 QMap、QMultiMap、QHash、QMultiHash 和 QSet。 QMultiMap 和 QMultiHash 支持一个键关联多个值,QHash 和 QMultiHash 类使用散列函数进行查找,查找速度更快。 QSet 是基于散列表的集合模板类,它存储数据的顺序是不定的,查找值的速度非常快。 QSet QSet 测试一个值是否包含于这个集合,用 contains() 函数,示例如下: if (!set.contains(“cat”)) … QMap<Key, T> 提供一个字典(关联数组),一个键映射到一个值。QMap 存储数据是按照键的顺序,如果不在乎存储顺序,使用 QHash 会更快。 定义 QMap<QString,int> 类型变量和赋值的示例代码如下: 也可以使用 insert() 函数赋值,或 remove() 移除一个键值对,示例如下: map.insert(“four”, 4); map.remove(“two”); 要查找一个值,使用运算符“[]”或 value() 函数,示例如下: int num1 = map[“one”]; int num2 = map.value(“two”); 如果在映射表中没有找到指定的键,会返回一个缺省构造值,例如,如果值的类型是字符串,会返回一个空的字符串。 在使用 value() 函数查找键值时,还可以指定一个缺省的返回值,示例如下: timeout = map.value(“TIMEOUT”,30); 这表示如果在 map 里找到键“TIMEOUT”,就返回关联的值,否则返回值为 30。 QMultiMap 是 QMap 的子类,是用于处理多值映射的便利类。 多值映射就是一个键可以对应多个值。QMap 正常情况下不允许多值映射,除非使用 QMap::insertMulti() 添加键值对。 QMultiMap 是 QMap 的子类,所以 QMap 的大多数函数在 QMultiMap 都是可用的,但是有几个特殊的,QMultiMap::insert() 等效于 QMap::insertMulti() , QMultiMap::replace() 等效于 QMap::insert()。 QMultiMap 使用示例如下: QMultiMap 不提供“[]”操作符,使用 value() 函数访问最新插入的键的单个值。如果要获取一个键对应的所有值,使用 values() 函数,返回值是 QList QHash 是基于散列表来实现字典功能的模板类,QHash<Key,T> 存储的键值对具有非常快的查找速度。 QHash 与 QMap 的功能和用法相似,区别在于以下几点: QHash 比 QMap 的查找速度快; 在 QMap 上遍历时,数据项是按照键排序的,而 QHash 的数据项是任意顺序的; QMap 的键必须提供“<”运算符,QHash 的键必须提供“==”运算符和一个名称为 qHash() 的全局散列函数。 QMultiHash 是 QHash 的子类,是用于处理多值映射的便利类,其用法与 QMultiMap 类似。 迭代器为访问容器类里的数据项提供了统一的方法,Qt有两种迭代器类:Java类型的迭代器和 STL类型的迭代器。 两者比较,Java 类型的迭代器更易于使用,且提供一些高级功能,而 STL 类型的迭代器效率更高。 对于每个容器类,有两个 Java 类型迭代器:一个用于只读操作,一个用于读写操作,各个Java 类型的容器类见表 1。 QMap 和 QHash 等关联容器类的迭代器用法相冋,QList 和 QLinkedList、QSet 等容器类的用法相同,所以下面只以 QMap 和 QList 为例介绍迭代器的用法。 Java 类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图 2 所示。 下面是遍历访问一个 QList QList QListItemtor 用于移动指针和读取数据的函数见表 3。 QListIterator 是只读访问容器内数据项的迭代器,若要在遍历过程中对容器的数据进行修改, 需要使用 QMutableListlterator。例如下面的示例代码为删除容器中数据为奇数的项: remove() 函数移除 next() 函数刚刚跳过的一个数据项,不会使迭代器失效。setValue() 函数可以修改刚刚跳过去的数据项的值。 对于关联容器类 QMap 如果是在多值容器里遍历,可以用 findNext() 或 findPrevious() 查找下一个或上一个值,如下面的代码将删除上一示例代码中 map 里值为“USA”的所有数据项: STL 迭代器与 Qt 和 STL 的原生算法兼容,并且进行了速度优化。具体类型见表 4。 对于每一个容器类,都有两个 STL 类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。 注意,在定义只读迭代器和读写迭代器时的区别,它们使用了不同的关健字,const_iterator 定义只读迭代器,iterator 定义读写迭代器。此外,还可以使用 const_reverse_iterator 和 reverse_iterator 定义相应的反尚迭代器。 STL 类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,运算符返回数据项内容。与 Java 类型的迭代器不同,STL 迭代器直接指向数据项,STL 迭代器指向位置示意图如图 5 所示。 begin() 函数使迭代器指向容器的第一个数据项,end() 函数使迭代器指向一个虚拟的表示结尾的数据项,end() 表示的数据项是无效的,一般用作循环结束条件。 下面仍然以 QList 和 QMap 为例说明 STL 迭代器的用法,其他容器类迭代器的用法类似。 下面的示例代码将 QList constBegin() 和 constEnd() 是用于只读迭代器的,表示起始和结束位置。 若使用反向读写迭代器,并将上面示例代码中 list 的数据项都改为小写,代码如下: 对于关联容器类 QMap 和 QHash,迭代器的操作符返回数据项的值。如果想返回键,使用 key() 函数。对应的,用 value() 函数返回一个项的值。 例如,下面的代码将 QMap<int,int> map 中所有项的键和值输出: Qt API 包含很多返回值为 QList 或 QStringList 的函数,要遍历这些返回的容器,必须先复制。由于 Qt 使用了隐式共享,这样的复制并无多大开销。 例如,下面的代码是正确的: 提示:隐式共享是对象的管理方法。一个对象被隐式共享,只是传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才实质复制共享对象给使用者。如在上面的代码中,splitter->sizes() 返回的是一个 QList 而下面的代码是错误的: 对于 STL 类型的迭代器,隐式共享还涉及另外一个问题,即当有一个迭代器在操作一个容器变量时,不要去复制这个容器变量。 Qt提供一个关键字 foreach (实际是 foreach (variable, container) 使用 foreach 的代码比使用迭代器更简洁。例如,使用 foreach 遍历一个 QLinkedList 用于迭代的变量也可以在 foreach 语句里定义,foreach 语句也可以使用花括号,可以使用 break 退出迭代,示例代码如下: 对于 QMap 和 QHash,foreach 会自动访问“键-值”对里的值,所以无需调用 values()。如果需要访问键则可以调用 keys(),示例代码如下: 对于多值映射,可以使用两重 foreach 语句,示例代码如下: 注意,foreach 关徤字遍历一个容器变量是创建了容器的一个副本,所以不能修改原来容器变量的数据项。 编写第一个Qt程序 编写第一个Qt程序 编写第一个Qt程序 编写第一个Qt程序 编写第一个Qt程序QStack
1
QStack<int> stack;stack.push(10);stack.push(20);stack.push(30);while (!stack.isEmpty()) cout << stack.pop() << endl;
QQueue
1
QQueue<int> queue;queue.enqueue (10);queue.enqueue(20);queue.enqueue (30);while (!queue.isEmpty()) cout << queue.dequeue() << endl;
关联容器类
QSet
QMap
1
QMap<QString, int> map;map["one"] = 1;map["two"] = 2;map["three "] = 3;
QMultiMap
1
QMultiMap<QString, int> map1, map2, map3;map1.insert("plenty", 100);mapl.insert("plenty", 2000); // map1.size() == 2map2.insert("plenty", 5000); // map2.size() == 1map3 = map1 + map2; // map3.size() == 3
1
2
3QList<int> values = map.values("plenty");
for (int i = 0; i < values.size(); ++i)
cout << values.at(i) << endl;QHash
QMultiHash
Java 类型迭代器
容器类
只读迭代器
读写迭代器
QList
QListItcrator
QMutableListItcrator
QLinkedList
QLinkedListIterator
QMutableLinkedListIterator
QVector
QVectorllcrator
QMutableVectorIterator
QSet
QSetItcrator
QMutableSetItcrator
QMap<Key, T>, QMultiMap<Key, T>
QMapIterator<Key, T>
QMutableMapIterator<Key, T>
QHash<Key, T>, QMultiHash<Key, T>
QHashIterator<Key, T>
QMutablcHashlterator<Key, T>
顺序容器类的迭代器的使用

1
QList<QString> list;list << "A" << "B" << "C" << "D";QListIterator<QString> i (list);while (i.hasNext()) qDebug () << i.next ();
1
QListIterator<QString> i (list);i.toBack();while (i.hasPrevious()) qDebug() << i.previous();
函数名
功能
void toFront()
迭代器移动到列表的最前面(第一个数据项之前)
void toBack()
迭代器移动到列表的最后面(最后一个数据项之后)
bool hasNext()
如果迭代器不是位于列表最后位罝,返回true
const T& next()
返回下一个数据项,并且迭代器后移一个位置
const T& peekNext()
返回下一个数据项,但是不移动迭代器位置
bool hasPrevious()
如果迭代器不是位于列表的最前面,返回true
const T& previous()
返回前一个数据项,并且迭代器前移一个位置
const T& peekPrevious()
返回前一个数椐项,但是不移动迭代器指针
1
QList<int> list;list <<1<<2<<3<<4<<5;QMutableListIterator<int> i (list);while (i.hasNext()) { if (i.next() % 2 != 0) i.remove();}
关联容器类的迭代器的使用
1
QMap<QString, QString> map;map.insert("Paris", "France");map.insert("New York", "USA");map.insert("Mexico City", "USA");map.insert("Moscow", "Russia");...QMutableMapIterator<QString, QString> i(map);while (i.hasNext ()) { if (i.next().key().endsWith("City")) i.remove();}
1
QMutableMapIterator<QString, QString> i(map);、while (i.findNext("USA")) i.remove();
STL类型迭代器
容器类
只读迭代器
读写迭代器
QList
QList
QList
QLinkedList
Q1. i nked List<1>: :const_iterator
QLinkedList
QVector
QVector
QVector
QSet
QSet
QSet
QMap<Key, P> QMultiMap<Kcy, T>
QMap<Key, T>::const_iterator
QMap<Key, T>:: iterator
QHash<Key, T> QMultiHash<Key, T>
QHash<Key, T>: :const_iterator
QHash<Key, T>::iterator

顺序容器类的迭代器的用法
1
QList<QString> list;list << "A" << "B" << "C" << "D";QList<QString>::const_iterator i;for (i = list.constBegin(); i != list.constEnd(); ++i) qDebug() << *i;
1
QList<QString>::reverse_iterator i;for (i = list.rbegin(); i != list.rend(); ++i) *i = i->toLower();}
关联容器类的迭代器的用法
1
QMap<int, int> map;...QMap<int, int>::const_iterator i;for (i = map.constBegin(); i != map.constEnd(); ++i) qDebug () << i.key () << ':' << i.value ();
1
const QList<int> sizes = splitter->sizes();QList<int>::const_iterator i;for (i = sizes.begin (); i != sizes.end(); ++i) ...
1
QList<int>::const_iterator i;for (i = splitter->sizes().begin(); i != splitter->sizes().end(); ++i)
1
QLinkedList<QString> list;...QString str;foreach (str, list) qDebug() << str;
1
QLinkedList<QString> list;...foreach (const QString &str, list) { if (str.isEmpty()) break; qDebug() << str;}
1
QMap<QString, int> map;...foreach (const QString &str, map.keys()) qDebug() << str << ':' << map.value(str);
1
QMultiMap<QString, int> map;...foreach (const QString &str, map.uniqueKeys()) { foreach (int i, map.values(str)) qDebug() << str << ':' << i;}













