书名:Qt 5.9 C++开发指南
ISBN:978-7-115-47868-9
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 王维波 栗宝鹃 侯春望
责任编辑 杨大可
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(76010)81055315
本书以Qt 5.9 LTS版本为开发平台,详细介绍了Qt C++开发应用程序的技术,包括Qt应用程序的基本架构、信号与槽工作机制、图形显示的Graphics/View架构、数据编辑和显示的Model/View架构、对话框和多窗口的设计与调用方法等,介绍了常用界面组件、文件读写、绘图、图表、数据可视化、数据库、多线程、网络和多媒体等模块的使用。每个编程主题都精心设计了完整的实例程序。
通过阅读本书,可了解Qt C++开发应用程序所需的基本技术。本书适合具有C++语言编程基础,希望应用Qt C++开发跨平台应用程序的读者阅读。
从1994年至今,Qt已经走过了20多年的发展历程,日趋成熟并广受欢迎。Qt是非常优秀的跨平台应用开发框架,在专业应用领域,很多应用软件都是采用Qt C++开发的,比如在地球物理研究领域,开发的专业软件一般需要在Linux、Windows或macOS等多种平台上运行,使用Qt就是非常好的选择。
本书主要介绍如何使用Qt进行C++应用程序开发。C++是使用最广泛的编程语言之一,在各个专业领域有很多大型的类库都是用C++编写的,在不同的专业研究领域可以找到很多开源的用C++编写的类库或算法程序,这对于专业软件开发是非常有利的。所以,扎实地掌握Qt C++编程如同掌握了一件利器,无论是通过编程实现自己的专业研究成果,还是从事专业的软件开发都是有长远意义的。
本书以Qt 5.9 LTS(Long Term Supported)版本为例介绍Qt C++开发,所有实例程序在Windows 7平台上测试,需要使用MSVC编译器的时候使用Visual Studio 2015。由于Qt的跨平台特性,这些实例程序在Linux和macOS系统上基本上无需修改就可以编译,所以,本书介绍的内容也适用于Linux和macOS平台上的应用程序开发。
本书不对C++语言的基本特性作介绍,需要读者已经掌握了C++语言编程的基本原理,对类的概念和使用比较熟悉。如果对C++语言还不够熟悉,可以找一本专门介绍C++语言的书学习,有很多很好的专门介绍C++语言的书。
Qt实际上是一套应用程序开发类库,Qt类库由许多模块组成,如核心的GUI组件模块Qt Widget,用于数据库访问的Qt SQL模块,用于二维图表显示的Qt Charts模块,用于数据三维显示的Qt Data Visualization模块,用于网络编程的Qt Network模块等。Qt的模块很多,在一本书里很难全面介绍到,本书只介绍应用程序设计常用的功能模块的使用。
本书为每个编程主题都精心设计了完整的实例程序(实例程序源代码可登录异步社区https://www.epubit.com,在本书页面免费下载),通过实例程序介绍类的主要接口函数的功能和使用,而不是简单地通过一些代码片段来孤立地解释类的使用。通过阅读本书,练习书中的实例,读者可以学习Qt C++类库常用模块的使用方法,学会用Qt C++设计一般应用程序的方法。
C++是一种通用的标准编程语言,使用任何编辑器都可以编写C++源程序,然后利用C++编译器对程序进行编译,就可以生成可执行的程序。
为了方便进行C++程序的编写和编译,有各种综合开发环境(Integrated Developing Environment,IDE),如Visual Studio就是Windows平台上常见的编写C++程序的IDE。一个IDE不仅提供程序的编辑和编译,一般还提供一套基本类库,用于提供支持平台应用程序开发的各种基本类,如Visual Studio使用MFC进行Windows平台的应用程序开发。
Qt是一套应用程序开发类库,但与MFC不同,Qt是跨平台的开发类库。Qt支持PC和服务器的平台,包括Windows、Linux、macOS等,还支持移动和嵌入式操作系统,如iOS、Embedded Linux、Android、WinRT等。跨平台意味着只需编写一次程序,在不同平台上无需改动或只需少许改动后再编译,就可以形成在不同平台上运行的版本。这种跨平台功能为开发者提供了极大的便利。
Qt最早是由挪威的Haavard Nord和Eirik Chambe-Eng在1991年开始开发的,在1994年发布,并成立了一家名为Trolltech的公司。Trolltech公司在2008年被诺基亚公司收购。2012年,Qt被Digia公司收购,并在2014年成立了独立的Qt公司,专门进行Qt的开发、维护和商业推广。
经过20多年的发展,Qt已经成为最优秀的跨平台开发框架之一,在各行各业的项目开发中得到广泛应用。许多大型软件都是用Qt开发的,如Autodesk Maya、Google Earth、Skype、WPS Office等。
C++语言使用广泛,长盛不衰,易在不同平台上移植,其编译生成的程序执行效率高,所以在专业研究领域很多开源的算法程序或类库都是用C++编写的。使用Qt C++编写应用程序,可以使自己的应用程序具有跨平台移植的功能,也可以利用各种开源的类库资源。所以,扎实地掌握Qt C++编程就如同掌握了一件利器,无论是通过编程实现自己的专业研究成果,还是从事专业软件开发都具有长远意义。
Qt的许可类型分为商业许可和开源许可,开源许可又分为LGPLV3和GPLV2/GPLV3。商业许可允许开发者不公开项目的源代码,其Qt版本包含更多的模块(某些模块只有商业许可的版本里才有),并能获得Qt公司的技术支持。当然,购买Qt商业许可需要支付费用。
使用开源许可的Qt无需支付费用,但是要遵循开源许可协议LGPLV3或GPLV2/GPLV3的规定。关于商业许可、开源许可的具体差别,开源许可的要求可以查看Qt官网的相关介绍。
对于Qt的学习来说,初学Qt使用开源版本的软件即可。若需要开发大型软件,并且不希望按照开源许可协议的要求公开源代码,以便对编写软件进行版权保护,则可以购买Qt的商业许可。
不同许可协议下,Qt的使用权利和要求、包含的模块、工具的对比可查看Qt官网网址。
Qt的版本更新比较快,且版本更新时会新增一些类或停止维护一些以前版本的类,例如Qt 5与Qt 4就有较大的区别,如果不是为了维护用旧版本编写的程序,一定要选用最新版本的Qt进行程序开发。
Qt公司在2017年5月底发布了Qt 5.9.0。Qt 5.9是一个长期支持(long term supported,LTS)版本,在未来至少3年内提供更新支持,而上一个LTS版本是Qt 5.6 LTS。
Qt 5.9具有更强的性能,更好的稳定性,从Qt 5.6到Qt 5.9增加了许多新的特性,一些重要的更新如下。
可访问Qt官网页面了解Qt 5.0至Qt 5.9版本更新的历程和每个版本的新增特性描述。
由于Qt 5.9 LTS是一个长期技术支持版本,在未来几年里都将有更新支持,因此,本书以Qt 5.9 LTS版本为例进行讲解,并且所有实例程序均使用Qt 5.9编译测试通过。
从Qt官网可以下载最新版本的Qt软件。根据开发项目的不同,Qt分为桌面和移动设备应用开发、嵌入式设备开发两大类不同的安装包。
桌面和移动设备应用开发就是开发在PC、服务器、手机、平板电脑等设备上运行的程序,操作系统平台可以是Windows、Linux、macOS、Android等。用于桌面和移动设备应用开发的Qt具有开源许可协议,可以免费下载和使用。
嵌入式设备开发是针对具体的嵌入式设备来开发应用程序,如物联网设备、汽车电子设备、医疗设备等特定的嵌入式设备。用于嵌入式设备开发的Qt可下载30天试用版本。
本书是介绍桌面应用程序开发的,所以下载使用的是桌面和移动设备开发的Qt 5.9.1开源版本。根据Qt官网的提示,注册用户后才可以下载Qt安装程序。
Qt 5.9.1的安装包分为在线安装包和离线安装包,为便于重复安装,最好下载离线安装包。离线安装包根据使用的操作系统平台不同,分为Linux、macOS和Windows 3个版本,本书实例都是用Windows 7平台上的Qt开发的,所以这里下载Windows版本的Qt 5.9.1离线安装包。
Qt 5.9以前版本的离线安装包即使是在Windows平台上,也会根据使用的编译器不同分为很多版本,如MinGW 32-bit版本、MSVC2015 32-bit版本、MSVC2015 64-bit版本等。而Qt 5.9在一个平台上只有一个安装包,编译器的选择放在了安装过程里,所以下载的Windows平台上的Qt 5.9.1安装包只有一个可执行文件。
双击下载后的Qt 5.9.1离线安装包可执行文件,就开始执行安装过程,安装过程与一般的Windows应用程序一样,按照向导进行操作即可。
在安装过程中会出现如图1-1所示的安装选项设置页面,在这个页面里选择需要安装的模块。“Qt 5.9.1”节点下面是Qt的功能模块,包括用于不同编译器和平台的模块,这些模块包括内容如下。
图1-1 Qt 5.9.1安装选项设置页面
“Tools”节点下面是一些工具软件,包括内容如下。
根据个人的需要设置安装选项,无需选择所有的安装选项。例如,如果不需要进行UWP平台的开发,UWP模块就可以都不选;如果不是为和以前开发的源程序兼容,过时的模块不要选择,如Qt Script就是已过时的模块。
注意
如果选择安装MSVC编译器的模块,需要在计算机上安装相应的Microsoft Visual Studio开发工具,使用免费的Community版本的Visual Studio即可。
安装完成后,在Windows“开始”菜单里建立的Qt 5.9.1程序组内容如图1-2所示。程序组中一个主要的程序是Qt Creator 4.3.1(Community),它是用于开发Qt程序的IDE,是Qt的主要工具软件。
根据选择安装的编译器模块会建立几个子分组,见图1-2中的MinGW 5.3.0 (32-bit)、MSVC 2015(32-bit)和MSVC 2015(64-bit),每个分组下面主要有3个工具软件。
图1-2 安装后“开始”菜单里的Qt 5.9.1程序组
这3个工具软件可独立使用,前两个集成到了Qt Creator里,可在Qt Creator打开。所以Qt的主要工具是Qt Creator,要编写Qt程序,运行Qt Creator即可。
启动Qt Creator,出现如图1-3所示的主窗口。
图1-3 Qt Creator主窗口
Qt Creator的界面很简洁。上方是主菜单栏,左侧是主工具栏,窗口的中间部分是工作区。根据设计内容不同,工作区会显示不同的内容。
图1-3是在左侧主工具栏单击“Welcome”按钮后显示实例的界面。这时工作区的左侧有“Projects”“Examples”“Tutorials”“Get Started Now”几个按钮,单击后会在主工作区显示相应的内容。
主窗口左侧是主工具栏,主工具栏提供了项目文件编辑、窗体设计、程序调试、项目设置等各种功能按钮。
对Qt Creator可以进行一些设置,如刚安装好的Qt Creator界面语言可能是中文,但是很多词汇翻译得并不恰当,可以将Qt Creator的界面语言设置为英文。
单击Qt Creator菜单栏的“Tools”→“Options”菜单项会打开选项设置对话框(如图1-4所示)。对话框的左侧是可设置的内容分组,单击后右侧出现具体的设置界面。常用的设置包括以下几点。
图1-4 Options的Build & Run设置页面
1.Environment设置:在Interface页面可以设置语言和主题,本书全部以英文界面的Qt Creator进行讲解,所以语言选择为English;为了使界面抓图更清晰,设置主题为Flat Light。更改语言和主题后需要重新启动Qt Creator才会生效。
2.Text Editor设置:在此界面可以设置文本编辑器的字体,设置各种类型文字的字体颜色,如关键字、数字、字符串、注释等字体颜色,也可以选择不同的配色主题。编辑器缺省字体的大小为9,可以修改得大一些。
3.Build & Run设置:图1-4显示的是Build & Run的设置界面,它有以下几个页面。
(1)Kits页面显示Qt Creator可用的编译工具,在图中可以看到有3个编译工具可用。
(2)Qt Versions页面显示安装的Qt版本,有Qt 5.9.1 MinGW 32bit、Qt 5.9.1 MSVC2015 32bit和Qt 5.9.1 MSVC2015 64bit 3个可用的版本。
(3)Compliers页面显示系统里可用的C和C++编译器,由于安装了MinGW和Visual Studio 2015,Qt Creator会自动检测出这些编译器。
(4)Debuggers页面显示Qt Creator自动检测到的调试器,有GNU gdb for MinGW调试器和Windows的CDB调试器。
注意
如果只是在计算机上安装了Visual Studio 2015,图1-4显示的界面上MSVC2015的两个编译器的图标会变为带有感叹号的一个黄色图标。Debuggers页面没有Windows的CDB调试器,可以用MSVC编译器对Qt Creator编写的程序进行编译,但是不能调试,这是因为缺少了Windows Software Development Kit (SDK)。这个SDK不会随Visual Studio一同安装,需要从Microsoft网站上下载。可以下载Windows Software Development Kit (SDK) for Windows 8.1,安装后重启计算机即可。
学习一种编程语言或编程环境,通常会先编写一个“Hello World”程序。我们也用Qt Creator编写一个“Hello World”程序,以初步了解Qt Creator设计应用程序的基本过程,对使用Qt Creator编写Qt C++应用程序建立初步的了解。
单击Qt Creator的菜单项“File”→“New File or Project”,出现如图1-5所示的对话框。在这个对话框里选择需要创建的项目或文件的模板。
图1-5 新建文件或项目对话框
Qt Creator可以创建多种项目,在最左侧的列表框中单击“Application”,中间的列表框中列出了可以创建的应用程序的模板,各类应用程序如下。
在图1-5显示的对话框中选择项目类型为Qt Widgets Application后,单击“Choose…”按钮,出现如图1-6所示的新建项目向导。
图1-6 新建项目向导第1步:项目名称和存储路径设置
在图1-6中,选择一个目录,如“G:\Qt5Book\Qt5.9Samp\chap01”,再设置项目名称为samp1_1,这样新建项目后,会在“G:\Qt5Book\Qt5.9Samp\chap01”目录下新建一个目录,项目所有文件保存在目录“G:\Qt5Book\Qt5.9Samp\chap01\samp1_1\”下。
在图1-6中设置好项目名称和保存路径后,单击“Next”按钮,出现如图1-7所示的选择编译工具的界面,可以将3个编译工具都选中,在编译项目时再选择一个作为当前使用的编译工具,这样可以编译生成不同版本的可执行程序。
在图1-7显示的界面中单击“Next”按钮,出现如图1-8所示的界面。在此界面中选择需要创建界面的基类(base class)。有3种基类可以选择:
图1-7 新建项目向导第2步:选择编译工具
图1-8 新建项目向导第3步:选择界面基类
在此选择QMainWindow作为基类,自动更改的各个文件名不用手动去修改。勾选“Generate form”复选框。这个选项如果勾选,就会由Qt Creator创建用户界面(User Interface,UI)文件,否则,需要自己编程手工创建界面。初始学习,为了了解Qt Creator的设计功能,勾选此选项。然后单击“Next”按钮,出现一个页面,总结了需要创建的文件和文件保存目录,单击“Finish”按钮就可以完成项目的创建。
完成了以上新建项目的步骤后,在Qt Creator的左侧工具栏中单击“Edit”按钮,可显示如图1-9所示的窗口。窗口左侧有上下两个子窗口,上方的目录树显示了项目内文件的组织结构,显示当前项目为samp1_1。项目的名称构成目录树的一个根节点,Qt Creator可以打开多个项目,但是只有一个活动项目(Active Project),活动项目的项目名称节点用粗体字体表示。
图1-9 项目管理与文件编辑界面
在项目名称节点下面,分组管理着项目内的各种源文件,几个文件及分组分别为以下几项。
左侧上下两个子窗口的显示内容可以通过其上方的一个下拉列表框进行选择,可以选择的显示内容包括Projects、Open Documents、Bookmarks、File System、Class View、Outline等。在图1-9中,上方的子窗口显示了项目的文件目录树,下方显示打开的文件列表。可以在下方选择显示Class View,这样下方则显示项目内所有的类的结构,便于程序浏览和快速切换到需要的代码位置。
双击文件目录树中的文件mainwindow.ui,出现如图1-10所示的窗体设计界面,这个界面实际上是Qt Creator中集成的Qt Designer。窗口左侧是分组的组件面板,中间是设计的窗体。在组件面板的Display Widgets分组里,将一个Label组件拖放到设计的窗体上面。双击刚刚放置的Label组件,可以编辑其文字内容,将文字内容更改为“Hello, World!”。还可以在窗口右下方的属性编辑器(Property Editor)里编辑标签的Font属性,Point Size更改为12,勾选Bold。
图1-10 集成在Qt Creator中UI设计器
单击主窗口左侧工具栏上的“Projects”按钮,出现如图1-11所示的项目编译设置界面。
界面左侧一栏的“Build & Run”下面显示了本项目中可用的编译器工具,要使用哪一个编译器用于项目编译,单击其名称即可,选择的编译器名称会用粗体字表示。这里选择使用MinGW 32bit编译器。
图1-11 项目编译器选择和设置界面
每个编译器又有Build和Run两个设置界面。在Build设置界面上,有一个“Shadow build”复选框。如果勾选此项,编译后将在项目的同级目录下建立一个编译后的文件目录,目录名称包含编译器信息,这种方式一般用于使用不同编译器创建不同版本的可执行文件。如果不勾选此项,编译后将在项目的目录下建立“Debug”和“Release”子目录用于存放编译后的文件。
在设计完mainwindow.ui文件,并设置好编译工具之后,就可以对项目进行编译、调试或运行。主窗口左侧工具栏下方有4个按钮,其功能见表1-1。
表1-1 编译调试工具栏按钮的作用
图标 |
作用 |
快捷键 |
---|---|---|
弹出菜单选择编译工具和编译模式,如Debug或Release模式 |
||
直接运行程序,如果修改后未编译,会先进行编译。即使在程序中设置了断点,此方式运行的程序也无法调试。 |
Ctrl+R |
|
项目需要以Debug模式编译,点此按钮开始调试运行,可以在程序中设置断点。若是以Release模式编译,点此按钮也无法进行调试。 |
F5 |
|
编译当前项目 |
Ctrl+B |
首先对项目进行编译,没有错误后,再运行程序。程序运行的界面如图1-12所示。这就是一个标准的桌面应用程序,我们采用可视化的方式设计了一个窗口,并在上面显示了字符串“Hello,World!”。
在Qt Creator中也可以对程序设置断点进行调试,但是必须以Debug模式编译,并以“Start Debugging”(快捷键F5)方式运行程序。程序调试的方法与一般IDE工具类似,不再详述。注意,要在Qt Creator里调试MSVC2015编译的程序,必须安装Windows软件开发工具包SDK。
在图1-11的界面中选择其他编译器,并且勾选“Shadow build”,用Debug和Release模式分别编译,将会在项目的同级目录下生成对应的目录,保存编译后的文件。图1-13显示的是实例samp1_1采用3种编译器,分别用Debug和Release模式编译后生成的目录结构。3种编译器,2种编译模式,生成了6个文件夹。
图1-12 实例程序samp1_1运行时界面
图1-13 勾选“Shadow build”选项后使用不同编译器和编译模式生成的输出目录
提示
除非特别说明,本书后面的实例程序均在Windows 7.0 SP1 64位平台上用Qt Creator 4.3.1开发,采用Qt 5.9.1的MinGW 32bit编译器进行编译。只有在第12章设计Qt Designer的Widget插件时才必须使用MSVC2015 32bit编译器,在第15.5节使用摄像头录像时必须使用Linux平台的Qt。
在Qt Creator中可以使用MSVC编译工具对Qt项目进行编译。若有人比较习惯于使用Visual Studio,或某些项目必须使用Visual Studio,也可以在Visual Studio里创建和管理Qt程序项目。要在Visual Studio中使用Qt,需要安装一个Visual Studio的Qt插件,这个插件程序由Qt公司提供。
目前最新的Visual Studio Qt插件是“Visual Studio Add-in 2.0.0 for Qt5 MSVC 2015”,可以从Qt官网下载并安装。安装此插件之前,需已经安装好Visual Studio 2015。
这里省略Visual Studio Add-in 2.0.0 for Qt5的安装过程。安装完成后,在Visual Studio的主菜单栏上增加了一个菜单组“Qt VS Tools”,在新建项目向导里增加了可创建Qt项目的项目模板。
在Visual Studio 2015里创建一个Qt GUI应用程序项目。创建项目时选择项目模板的对话框如图1-14所示,选择创建Qt GUI Application项目,根据向导提示完成项目samp1_2的创建。
按照向导缺省设置创建完项目后,Visual Studio管理项目的全部文件,有一个samp1_2.ui的窗体文件,双击此文件,会自动使用Qt Designer打开窗体文件进行界面设计,如同在Qt Creator里设计窗体一样。
在首次使用Visual Studio编译Qt项目之前,必须先进行一些设置,否则会提示没有设置Qt版本,无法编译项目。
图1-14 在Visual Studio 2015里创建Qt项目samp1_2
首先要设置Qt版本。单击Visual Studio菜单项“Qt VS Tools”→“Qt Options”,出现如图1-15所示的对话框。Qt Versions页面显示了可以使用的Qt版本(这是已经设置好的界面),在未设置之前,框里是空白的。单击“Add”按钮出现如图1-16所示的添加Qt版本对话框。
图1-15 Qt Options设置对话框
图1-16 添加Qt版本对话框
单击“Path”文本框后面的按钮,在出现的目录选择对话框里选择Qt 5.9.1安装目录下的MSVC编译器目录,如“D:\Qt\Qt5.9.1\5.9.1\msvc2015_64”。选择目录后,Version name编辑框里会自动出现版本名称,可以修改此名称为意义更明显的名字,如“msvc2015-64bit”。
然后,再单击Visual Studio菜单项“Qt VS Tools”→“Qt Project Settings”,为项目设置Qt版本,出现如图1-17所示的对话框。在此对话框的Properties分页下的列表框里,在Version下拉列表框中选择某个Qt版本。
图1-17 Qt项目设置对话框
完成这两项设置后,再进行编译就没有问题了。项目的运行、调试等就都是Visual Studio的操作了,这里不再赘述。
提示
在Qt Creator里就可以使用MSVC编译器对项目进行编译,并不是只有在Visual Studio里才可以使用MSVC编译器编译Qt的项目。
上一章通过一个“Hello World”实例,演示了在Qt Creator里创建应用程序、设计窗体界面、编译和运行程序的基本过程。本章将继续深入地介绍Qt Creator设计GUI应用程序的基本方法,包括Qt创建的应用程序项目的基本组织结构,可视化设计的UI界面文件的原理和运行机制,信号与槽的使用方法,窗体可视化设计的底层原理,应用程序的窗体、组件布局、菜单、工具栏、Actions等常见设计元素的使用方法。
在Qt Creator中新建一个Widget Application项目samp2_1,在选择窗口基类的页面(图1-8)选择QWidget作为窗体基类,并选中“Generate form”复选框。创建后的项目文件目录树如图2-1所示。
图2-1 项目文件的目录树
这个项目包含以下一些文件。
后缀为“.pro”的文件是项目的管理文件,文件名就是项目的名称,如本项目中的samp2_1.pro。下面是samp2_1.pro文件的内容。
Qt += core gui
greaterThan(Qt_MAJOR_VERSION, 4): Qt += widgets
TARGET = samp2_1
TEMPLATE = app
SOURCES += main.cpp\
widget.cpp
HEADERS += widget.h
FORMS += widget.ui
项目管理文件用于记录项目的一些设置,以及项目包含文件的组织管理。
“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自动添加到项目管理文件里面的,用户不需要手动修改。当添加一个文件到项目,或从项目里删除一个文件时,项目管理文件里的条目会自动修改。
后缀为“.ui”的文件是可视化设计的窗体的定义文件,如widget.ui。双击项目文件目录树中的文件widget.ui,会打开一个集成在Qt Creator中的Qt Designer对窗体进行可视化设计,如图2-2所示。
约定
本书后面将称这个集成在Qt Creator中的Qt Designer为“UI设计器”,以便与独立运行的Qt Designer区别开来。
图2-2中的UI设计器有以下一些功能区域。
图2-2 集成在Qt Creator中的UI设计器
图2-3显示的是选中窗体上放置的标签组件后属性编辑器的内容。最上方显示的文字“LabDemo: QLabel”表示这个组件是一个QLabel类的组件,objectName是LabDemo。属性编辑器的内容分为两列,Property列是属性的名称,Value列是属性的值。属性又分为多个组,实际上表示了类的继承关系,如在图2-3中,可以看出QLabel的继承关系是QObject→QWidget→QFrame→QLabel。
图2-3 界面组件的属性编辑器
objectName表示组件的对象名称,界面上的每个组件都需要一个唯一的对象名称,以便被引用。界面上的组件的命名应该遵循一定的法则,具体使用什么样的命名法则根据个人习惯而定,主要目的是便于区分和记忆,也要便于与普通变量相区分。
设置其他属性的值只需在属性编辑器里操作即可,如设置LabDemo的text属性为“Hello, World”,只需像图2-3那样修改text属性的值即可。
提示
标准C++语言里并没有property关键字,property是Qt对标准C++的扩展,使得在Qt Designer里就可以可视化设置类的数据。
在图2-2显示的设计窗体上,放置一个Label和一个Push Button组件,它们的主要属性设置见表2-1。
表2-1 界面组件的属性设置
ObjectName |
类名称 |
属性设置 |
备注 |
---|---|---|---|
LabDemo |
QLabel |
Text=”Hello, World” |
设置标签显示文字和字体 |
btnClose |
QPushButton |
Text=”Close” |
设置按钮的文字 |
编辑完属性之后,再为btnClose按钮增加一个功能,就是单击此按钮时,关闭窗口,退出程序。使用Signals和Slots 编辑器完成这个功能,如图2-4所示。
图2-4 信号与槽编辑器中设计信号与槽的关联
在信号与槽编辑器的工具栏上单击“Add”按钮,在出现的条目中,Sender选择btnClose,Signal选择clicked(),Receiver选择窗体Widget,Slot选择close()。这样设置表示当按钮btnClose被单击时,就执行Widget的close()函数,实现关闭窗口的功能。
然后对项目进行编译和运行,可以出现如图2-5所示的窗口,单击“Close”按钮可以关闭程序。标签的文字内容和字体被修改了,窗口标题也显示为所设置的标题,而我们并没有编写一行程序语句,Qt是怎么实现这些功能的呢?
图2-5 具有Close按钮的“Hello World”程序
main.cpp是实现main()函数的文件,下面是main.cpp文件的内容。
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv); //定义并创建应用程序
Widget w; //定义并创建窗口
w.show(); //显示窗口
return a.exec(); //应用程序运行
}
main()函数是应用程序的入口。它的主要功能是创建应用程序,创建窗口,显示窗口,并运行应用程序,开始应用程序的消息循环和事件处理。
QApplication 是Qt的标准应用程序类,第1行代码定义了一个QApplication类的实例a,就是应用程序对象。
然后定义了一个Widget类的变量w,Widget是本实例设计的窗口的类名,定义此窗口后再用w.show()显示此窗口。
最后一行用a.exec()启动应用程序的执行,开始应用程序的消息循环和事件处理。
为了搞清楚窗体类的定义,以及界面功能的实现原理,这里将项目进行编译。编译后在项目目录下会自动生成一个文件ui_widget.h,这样对于一个窗体,就有4个文件了,各文件的功能说明见表2-2。
表2-2 窗体相关的4个文件
文件 |
功能 |
---|---|
widget.h |
定义窗体类的头文件,定义了类Widget |
widget.cpp |
Widget类的功能实现源程序文件 |
widget.ui |
窗体界面文件,由UI设计器自动生成,存储了窗体上各个组件的属性设置和布局 |
ui_widget.h |
编译后,根据窗体上的组件及其属性、信号与槽的关联等自动生成的一个类的定义文件,类的名称是Ui_Widget |
下面分别分析各个文件的内容及其功能,以及它们是如何联系在一起工作,实现界面的创建与显示的。
widget.h文件是窗体类的头文件。在创建项目时,选择窗体基类是QWidget,在widget.h中定义了一个继承自QWidget的类Widget,下面是widget.h文件的内容。
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui { //一个命名空间Ui,包含一个类Widget
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui; //使用Ui::Widget定义的一个指针
};
#endif
widget.h文件有几个重要的部分。
(1)namespace声明
代码中有如下的一个namespace声明:
namespace Ui {
class Widget;
}
这是声明了一个名称为Ui的命名空间(namespace),包含一个类Widget。但是这个类Widget并不是本文件里定义的类Widget,而是ui_widget.h文件里定义的类,用于描述界面组件的。这个声明相当于一个外部类型声明(具体要看完ui_widget.h文件内的解释之后才能搞明白)。
(2)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的实现代码,下面是widget.cpp文件的内容。
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
注意到,在这个文件的包含文件部分自动加入了如下一行内容:
#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是窗体界面定义文件,是一个XML文件,定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等。用UI设计器可视化设计的界面都由Qt自动解析,并以XML文件的形式保存下来。在设计界面时,只需在UI设计器里进行可视化设计即可,而不用管widget.ui文件是怎么生成的。
下面是widget.ui文件的内容。
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>336</width>
<height>216</height>
</rect>
</property>
<property name="windowTitle">
<string>My First Demo</string>
</property>
<widget class="QLabel" name="LabDemo">
<property name="geometry">
<rect>
<x>60</x>
<y>50</y>
<width>211</width>
<height>51</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Hello, World</string>
</property>
</widget>
<widget class="QPushButton" name="btnClose">
<property name="geometry">
<rect>
<x>200</x>
<y>140</y>
<width>81</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections>
<connection>
<sender>btnClose</sender>
<signal>clicked()</signal>
<receiver>Widget</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>240</x>
<y>195</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>
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文件的内容。
/**********************************************************************
** Form generated from reading UI file 'widget.ui'
** Created by: Qt User Interface Compiler version 5.9.0
** 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_NAMESPACE
class Ui_Widget
{
public:
QLabel *LabDemo;
QPushButton *btnClose;
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName(QStringLiteral("Widget"));
Widget->resize(280, 168);
LabDemo = new QLabel(Widget);
LabDemo->setObjectName(QStringLiteral("LabDemo"));
LabDemo->setGeometry(QRect(50, 20, 201, 51));
QFont font;
font.setPointSize(20);
font.setBold(true);
font.setWeight(75);
LabDemo->setFont(font);
btnClose = new QPushButton(Widget);
btnClose->setObjectName(QStringLiteral("btnClose"));
btnClose->setGeometry(QRect(150, 120, 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));
LabDemo->setText(QApplication::translate("Widget", "Hello, World", Q_NULLPTR));
btnClose->setText(QApplication::translate("Widget", "Close", Q_NULLPTR));
} // retranslateUi
};
namespace Ui {
class Widget: public Ui_Widget {};
} // namespace Ui
Qt_END_NAMESPACE
#endif // UI_WIDGET_H
查看ui_widget.h文件的内容,发现它主要做了以下的一些工作。
(1)定义了一个类Ui_Widget,用于封装可视化设计的界面。
(2)自动生成了界面各个组件的类成员变量定义。在public部分为界面上每个组件定义了一个指针变量,变量的名称就是设置的objectName。比如,在窗体上放置了一个QLabel和一个QPushButton并命名后,自动生成的定义是:
QLabel *LabDemo;
QPushButton *btnClose;
(3)定义了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()槽函数关联起来,就是在图2-4中设置的信号与槽的关联的程序语句实现。这样,当单击btnClose按钮时,就会执行Widget的close()槽函数,而close()槽函数的功能是关闭窗口。
第2行是设置槽函数的关联方式,用于将UI设计器自动生成的组件信号的槽函数与组件信号相关联。
所以,在Widget的构造函数里调用 ui->setupUI(this),就实现了窗体上组件的创建、属性设置、信号与槽的关联。
(4)定义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创建的GUI应用程序中各个文件的作用,剖析了可视化设计的UI文件是如何被转换为C++的类定义,并自动创建界面的。这些是使用Qt Creator可视化设计用户界面,并使各个部分融合起来运行的基本原理。
本节再以一个稍微复杂的例子来讲解设计GUI的常见功能,包括界面设计时布局的管理,程序里如何访问界面组件,以及Qt关键的信号与槽的概念。
创建一个Widget Application项目samp2_2,在创建窗体时选择基类QDialog,生成的类命名为QWDialog,并选择生成窗体。
如此新建的项目samp2_2有一个界面文件qwdialog.ui,一个头文件qwdialog.h和源程序文件qwdialog.cpp。此外,还有项目文件samp2_2.pro和主程序文件main.cpp。
qwdialog.ui界面文件设计时界面如图2-6所示。程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置。
图2-6 实例程序samp2_2设计时界面
在界面设计时,对需要访问的组件修改其objectName,如各个按钮、需要读取输入的编辑框、需要显示结果的标签等,以便在程序里区分。对于不需要程序访问的组件则无需修改其objectName,如用于界面上组件分组的GroupBox、Frame、布局等,让UI设计器自动命名即可。
对图2-6中几个主要组件的命名、属性设置见表2-3。
表2-3 qwdialog.ui中各个组件的相关设置
对象名 |
类名称 |
属性设置 |
备注 |
---|---|---|---|
txtEdit |
QPlainTextEdit |
Text="Hello, World |
用于显示文字内容,可编辑 |
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不要修改 |
对于界面组件的属性设置,需要注意以下几点。
(1)objectName是窗体上创建的组件的实例名称,界面上的每个组件需要有一个唯一的objectName,程序里访问界面组件时都是通过其objectName进行访问,自动生成的槽函数名称里也有objectName。所以,组件的objectName需要在设计程序之前设置好,设置好之后一般不要再改动。若设计程序之后再改动objectName,涉及的代码需要相应的改动。
(2)窗体的objectName就是窗体的类名称,在UI设计器里不要修改窗体的objectName,窗体的实例名称需要在使用窗体的代码里去定义。
Qt的界面设计使用了布局(Layout)功能。所谓布局,就是界面上组件的排列方式,使用布局可以使组件有规则地分布,并且随着窗体大小变化自动地调整大小和相对位置。布局管理是GUI设计的必备技巧,下面逐步讲解如何实现图2-6所示的界面设计。
为了将界面上的各个组件的分布设计得更加美观,经常使用一些容器类,如QgoupBox、QtabWidget、QFrame等。例如,将3个CheckBox组件放置在一个GroupBox组件里,该GroupBox组件就是这3个CheckBox的容器,移动这个GroupBox就会同时移动其中的3个CheckBox。
图2-7显示的是设计图2-6界面的前期阶段。在窗体上放置了2个GroupBox组件,在groupBox1里放置3个CheckBox组件,在groupBox2里放置3个RadioButton组件。图2-7右侧Object Inspector里显示了界面上各组件之间的层次关系。
图2-7 界面组件的放置及层次关系
Qt为界面设计提供了丰富的布局管理功能,在UI设计器中,组件面板里有Layouts和Spacers两个组件面板,在窗体上方的工具栏里有布局管理的按钮(如图2-8所示)。
图2-8 用于布局可视化设计的组件面板和工具栏
Layouts和Spacers两个组件面板里的布局组件的功能见表2-4。
表2-4 组件面板上用于布局的组件
布局组件 |
功能 |
---|---|
Vertical Layout |
垂直方向布局,组件自动在垂直方向上分布 |
Horizontal Layout |
水平方向布局,组件自动在水平方向上分布 |
Grid Layout |
网格状布局,网状布局大小改变时,每个网格的大小都改变 |
Form Layout |
窗体布局,与网格状布局类似,但是只有最右侧的一列网格会改变大小 |
Horizontal Spacer |
一个用于水平分隔的空格 |
Vertical Spacer |
一个用于垂直分隔的空格 |
使用组件面板里的布局组件设计布局时,先拖放一个布局组件到窗体上,如在设计图2-8中3个按钮的布局时,先放一个Horizontal Layout到窗体上,布局组件会以红色边框显示。再往布局组件里拖放3个Push Button和2个Horizontal Spacer,就可以得到图2-8中3个按钮的水平布局效果。
在设计窗体的上方有一个工具栏,用于调整设计器进入不同的状态,以及进行布局设计,工具栏上各按钮的功能见表2-5。
表2-5 UI设计器工具栏各按钮的功能
按钮及快捷键 |
功能 |
---|---|
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键,可以实现组件多选,选择某个容器类组件,相当于选择了其内部的所有组件。例如,在图2-7的界面中,选中groupBox1,然后单击“Lay Out Horizontally”工具栏按钮,就可以对groupBox1内的3个CheckBox水平布局。
在图2-8的界面上,使groupBox1里的3个CheckBox水平布局,groupBox2里的3个RadioButton水平布局,下方3个按钮水平布局。在窗体上又放置了一个PlainTextEdit组件。现在,改变groupBox1、groupBox2或按钮的水平布局的大小,其内部组件都会自动改变大小。但是当改变窗体大小时,界面上的各组件却并不会自动改变大小。
随后还需为窗体指定一个总的布局。选中窗体(即不要选择任何组件),单击工具栏上的“Lay Out Vertically”按钮,使4个组件垂直分布。这样布局后,当窗体大小改变时,各个组件都会自动改变大小。
在UI设计器里可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度和高度来实现某些需要的布局效果。
在UI设计工具栏上单击“Edit Buddies”按钮可以进入伙伴关系编辑状态,如设计一个窗体时,进入伙伴编辑状态之后的界面如图2-9所示。
图2-9 编辑伙伴关系
伙伴关系(Buddy)是指界面上一个Label和一个组件相关联,如图2-9中的伙伴关系编辑状态,单击一个Label,按住鼠标左键,然后拖向一个组件,就建立了Label和组件之间的伙伴关系。
伙伴关系是为了在程序运行时,在窗体上用快捷键快速将输入焦点切换到某个组件上。例如,在图2-9的界面上,设定“姓名”标签的Text属性为“姓名(&N)”,其中符号“&”用来指定快捷字符,界面上并不显示“&”,这里指定快捷字母为N。那么程序运行时,用户按下Alt+N,输入焦点就会快速切换到“姓名”关联的输入框内。
在UI设计器工具栏上单击“Edit Tab Order”按钮进入Tab顺序编辑状态(如图2-10所示)。Tab顺序是指在程序运行时,按下键盘上的Tab键时输入焦点的移动顺序。一个好的用户界面,在按Tab键时,焦点应该以合理的顺序在界面上移动,而不是随意地移动。
图2-10 Tab顺序编辑状态
进入Tab顺序编辑状态后,在界面上会显示具有Tab顺序组件的编号,依次按希望的顺序单击组件,就可以重排Tab顺序了。没有输入焦点的组件是没有Tab顺序的,如Label组件。
信号与槽(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()槽函数。
关于信号与槽的使用,有以下一些规则需要注意。
(1)一个信号可以连接多个槽,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));
这是当一个对象spinNum的数值发生变化时,所在窗体有两个槽进行响应,一个addFun()用于计算,一个updateStatus()用于更新状态。
当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行。
当信号和槽函数带有参数时,在connect()函数里,要写明参数的类型,但可以不写参数名称。
(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()函数。
(3)一个信号可以连接另外一个信号,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SIGNAL (refreshInfo(int));
这样,当一个信号发射时,也会发射另外一个信号,实现某些特殊的功能。
(4)严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
(5)在使用信号与槽的类中,必须在类的定义中加入宏Q_OBJECT。
(6)当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。
信号与槽机制是Qt GUI编程的基础,使用信号与槽机制可以比较容易地将信号与响应代码关联起来。
下面开始设计程序功能。对于该程序,希望它的功能如下。
窗体在设计模式下,选中chkBoxUnder组件,单击右键调出其快捷菜单。在快捷菜单中单击菜单项“Go to slot…”,出现如图2-11所示的对话框。
图2-11 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)的框架,在此函数中添加如下的代码,实现文本框字体下划线的控制。
void QWDialog::on_chkBoxUnder_clicked(bool checked)
{
QFont font=ui->txtEdit->font();
font.setUnderline(checked);
ui->txtEdit->setFont(font);
}
以同样的方法为Italic和Bold两个CheckBox设计槽函数,编译后运行,发现已经实现了修改字体的下划线、斜体、粗体属性的功能,说明信号与槽函数已经关联了。
但是,查看QWDialog的构造函数,构造函数只有简单的一条语句。
QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog)
{
ui->setupUi(this);
}
这里没有发现用connect()函数进行几个CheckBox的信号与槽函数关联的操作。这些功能是如何实现的呢?
查看编译生成的ui_qwdialog.h文件。构造函数里调用的setupUi()是在ui_qwdialog.h文件里实现的。查看setupUi()函数的内容,也没有发现用connect()函数进行几个CheckBox的信号与槽关联的操作,只是在setupUI()里发现了如下的一条语句:
QMetaObject::connectSlotsByName(QWDialog);
秘密就在于这条语句。connectSlotsByName(QWDialog)函数将搜索QWDialog界面上的所有组件,将信号与槽函数匹配的信号和槽关联起来,它假设槽函数的名称是:
void on_<object name>_<signal name>(<signal parameters>);
例如,通过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()编写实现代码如下:
void QWDialog::setTextFontColor()
{
QPalette plet=ui->txtEdit->palette();
if (ui->rBtnBlue->isChecked())
plet.setColor(QPalette::Text,Qt::blue);
else if (ui->rBtnRed->isChecked())
plet.setColor(QPalette::Text,Qt::red);
else if (ui->rBtnBlack->isChecked())
plet.setColor(QPalette::Text,Qt::black);
else
plet.setColor(QPalette::Text,Qt::black);
ui->txtEdit->setPalette(plet);
}
由于这个槽函数是自定义的,所以不会自动与RadioButton的clicked()事件关联,此时编译后运行程序不会实现改变字体颜色的功能。需要在QWDialog的构造函数中手工进行关联,代码如下:
QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog)
{
ui->setupUi(this);
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
}
在构造函数中将3个RadioButton的clicked() 信号与同一个槽函数setTextFontColor()相关联。再编译后运行,就可以更改文字的颜色了。
界面上还有“确定”“取消”“退出”3个按钮,这是在对话框中常见的按钮。“确定”表示确认选择并关闭对话框,“取消”表示取消选择并关闭对话框,“退出”则直接关闭对话框。
QWDialog是从QDialog继承而来的,QDialog提供了accept()、reject()、close()等槽函数来表示这三种状态,只需将按钮的clicked()信号与相应槽函数关联即可。
下面采用可视化的方式,将按钮的clicked()信号与这些槽函数关联起来。在UI设计器里,单击上方工具栏里的“Edit Signals/Slots”按钮,窗体进入信号与槽函数编辑状态,如图2-12所示。将鼠标移动到“确定”按钮上方,再按下鼠标左键,移动到窗体的空白区域释放左键,这时出现如图2-13所示的关联设置对话框。
图2-12 窗体进入Signals/Slots编辑状态
图2-13 信号与槽关联编辑对话框
在图2-13中,左侧的列表框里显示了btnOK的信号,选择clicked(),右边的列表框里显示了QWDialog的槽函数,选择accept(),单击“OK”按钮。
同样的方法可以将btnCancel的clicked()信号与QWDialog的reject()槽函数关联,将btnClose的clicked()信号与QWDialog的close()槽函数关联。
注意
在图2-13的右侧列表框中没有close()槽函数,需要勾选下方的“Show signals and slots inherited from QWidget”才会出现close()函数。
设置完3个按钮的信号与槽关联之后,在窗体下方的Signals和Slots 编辑器里也显示了这3个关联。实际上,可以直接在Signals和Slots 编辑器进行关联设置。现在编译并运行程序,单击这3个按钮都会关闭程序。
那么,这3个按钮的信号与槽函数的关联是在哪里实现的呢?答案在setupUi()函数里,在setupUi()函数里自动增加了以下3行代码:
QObject::connect(btnOK, SIGNAL(clicked()), QWDialog, SLOT(accept()));
QObject::connect(btnCancel, SIGNAL(clicked()), QWDialog, SLOT(reject()));
QObject::connect(btnClose, SIGNAL(clicked()), QWDialog, SLOT(close()));
这个实例程序的功能全部完成了。采用UI设计器设计了窗体界面,采用可视化和程序化的方式设计槽函数,设计信号与槽函数之间的关联。从以上的设计过程可以看到,Qt Creator和UI设计器为设计应用程序提供了强大的可视化设计功能。
UI的可视化设计是对用户而言的,其实底层都是C++的代码实现,只是Qt巧妙地进行了处理,让用户省去了很多繁琐的界面设计工作。
由于界面设计的底层其实都是由C++语言实现的,底层实现的功能比可视化设计更加强大和灵活。某些界面效果是可视化设计无法完成的,或者某些人习惯了用纯代码的方式来设计界面,就可以采用纯代码的方式设计界面,如Qt自带的实例基本都是用纯代码方式实现用户界面的。
所以,本节介绍一个用纯代码方式设计UI的实例,通过实例了解用纯代码设计UI的基本原理。与前面的可视化UI设计相对应,且称之为代码化UI设计。
首先建立一个Widget Appliation项目samp2_3,在创建项目向导中选择基类时,选择基类QDialog,新类的名称命名为QWDlgManual,关键是取消创建窗体,即不勾选“Generate form”复选框。创建后的项目文件目录树下没有qwdlgmanual.ui文件。
该项目通过代码创建一个对话框,实现与samp2_2类似的界面和功能。本例完成后的运行效果如图2-14所示,其界面和功能与samp2_2类似。
图2-14 实例samp2_3运行效果
完成功能后的qwdlgmanual.h文件中QWDlgManual类的完整定义如下。
#include <QDialog>
#include <QCheckBox>
#include <QRadioButton>
#include <QPlainTextEdit>
#include <QPushButton>
class QWDlgManual : public QDialog
{
Q_OBJECT
private:
QCheckBox *chkBoxUnder;
QCheckBox *chkBoxItalic;
QCheckBox *chkBoxBold;
QRadioButton *rBtnBlack;
QRadioButton *rBtnRed;
QRadioButton *rBtnBlue;
QPlainTextEdit *txtEdit;
QPushButton *btnOK;
QPushButton *btnCancel;
QPushButton *btnClose;
void iniUI(); //UI 创建与初始化
void iniSignalSlots(); //初始化信号与槽的链接
private slots:
void on_chkBoxUnder(bool checked); //Underline的槽函数
void on_chkBoxItalic(bool checked);//Italic的槽函数
void on_chkBoxBold(bool checked); //Bold的槽函数
void setTextFontColor(); //设置字体颜色
public:
QWDlgManual(QWidget *parent = 0);
~QWDlgManual();
};
在QWDlgManual类的private部分,声明了界面上的各个组件的指针变量,这些界面组件都需要在QWDlgManual类的构造函数里创建并在窗体上布局。
在private部分自定义了两个函数,iniUI()用来创建所有界面组件,并完成布局和属性设置,iniSignalSlots()用来完成所有的信号与槽函数的关联。
在private slots部分声明了4个槽函数,分别是3个CheckBox的响应槽函数,以及3个颜色设置的RadioButton的共同响应槽函数。
注意
与可视化设计得到的窗体类定义不同,QWDlgManual的类定义里没有指向界面的指针ui。
这几个槽函数的功能与例samp2_2中的类似,只是在访问界面组件时,无需使用ui指针,而是直接访问QWDlgManual类里定义的界面组件的成员变量即可,例如on_chkBoxUnder()的代码:
void QWDlgManual::on_chkBoxUnder(bool checked)
{
QFont font=txtEdit->font();
font.setUnderline(checked);
txtEdit->setFont(font);
}
界面的创建,以及信号与槽函数的关联都在QWDlgManual的构造函数里完成,构造函数代码如下:
QWDlgManual::QWDlgManual(QWidget *parent) : QDialog(parent)
{
iniUI(); //界面创建与布局
iniSignalSlots(); //信号与槽的关联
setWindowTitle("Form created mannually");
}
构造函数调用iniUI()创建界面组件并布局,调用iniSignalSlots()进行信号与槽函数的关联。
iniUI()函数实现界面组件的创建与布局,以及属性设置。下面是iniUI()的完整代码。
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()函数按照顺序完成了如下的功能。
如此创建组件并设置布局后,运行可以得到如图2-14所示的界面效果。这里完全是采用代码来实现组件创建与布局的设置,而这些功能在可视化设计中是由setupUi()函数根据界面的可视化设计结果自动实现的。
采用代码设计实现UI时,需要对组件的布局有个完整的规划,不如可视化设计直观,且编写代码工作量大。
在纯代码设计UI时,信号与槽的关联也需要用代码来完成。函数iniSignalSlots()初始化所有的信号与槽的关联,其完整代码如下。
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()));
}
设计完成后,编译并运行程序,可以得到如图2-14所示的运行效果,且功能与samp2_2相同。很显然,采用纯代码方式实现UI界面是比较复杂的,代码设计的工作量大而繁琐。
可视化UI设计无需人工编写代码去处理大量繁琐的界面组件的创建和布局管理工作,可以直观地进行界面设计,大大提高工作效率。但是可视化UI设计也存在一些缺陷,如某些组件无法可视化地添加到界面上,比如在工具栏上无法可视化添加ComboBox组件,而用代码就可以。
采用纯代码方式进行UI设计虽然无所不能,但是设计效率太低,过程非常繁琐,而可视化UI设计简单高效。所以,能用可视化设计的就尽可能用可视化设计解决,无法解决的再用纯代码方式,将两种方法结合,才是高效设计UI的方法。
本节用一个实例讲解如何用混合方式创建UI,即部分界面设计用UI 设计器可视化实现,部分无法在UI设计器里实现的界面设计用代码实现。同时用这个实例讲解如何使用资源文件、如何使用Actions,如何设计主窗口里的菜单、工具栏和状态栏,这些是一般应用程序主窗口都有的功能。
图2-15是在UI设计器里设计完成的窗口。本项目的窗口类是从QMainWindow继承而来的,具有主菜单、工具栏和状态栏。这个例子实现了主菜单、主工具栏和状态栏,中间工作区里是一个TextEdit组件,用于编辑文本。
图2-15 在UI设计器里完成的窗口界面
我们希望在工具栏上添加一个SpinBox组件,用于设置字体大小,还想在工具栏上添加一个FontComboBox组件,用于选择字体名称。但是在UI设计器里,将这些组件拖放到工具栏上时,会显示不能添加到工具栏上。同样,在窗体下方的状态栏上也不能直接添加Label和ProgressBar组件。这就是UI可视化设计的局限,无法实现某些界面效果。
但是通过编写代码,可以实现期望的界面效果。图2-16就是程序运行时的窗口,可见在工具栏上添加了设置字体大小的SpinBox组件和选择字体名称的FontComboBox组件,在状态栏上添加了显示当前文件名称的标签,还添加了一个ProgressBar。通过设计相应的槽函数并与界面组件的相关信号进行关联,实现了期望的程序功能。
图2-16 程序运行时的窗口界面
这就是混合方式界面设计的效果,先将所有在UI设计器里可设计的静态UI元素都用可视化的方式实现,不能在UI设计器里可视化设计的再用代码实现。
创建一个Widget Application项目,在向导的创建窗口类时,选择基类QMainWindow,新建类的名称设置为QWMainWind,并选择生成窗体。
本项目主窗口有菜单和工具栏,需要使用到图标。在Qt项目中,图标可以存储在资源文件里,为此先创建一个资源文件。在Qt Creator里单击“File”→“New File or Project…”菜单项,在新建文件与项目对话框里选择“Qt Resource File”,然后按照向导的指引设置资源文件的文件名,并添加到当前项目里。
本项目创建的资源文件名为res.qrc。在项目文件目录树里,会自动创建一个与Headers、Sources和Forms并列的Resources文件组,在Resources组里有res.qrc节点。在资源文件名节点上右击,在弹出的快捷菜单中选择“Open in Editor”打开资源文件编辑器(如图2-17所示)。
图2-17 资源文件编辑器
资源文件最主要的一个功能就是存储图标和图片文件,以便在程序里使用。在资源文件里首先建一个前缀(Prefix),例如images,方法是在图2-17显示的窗口右下方的功能区单击“Add”按钮下的“Add Prefix”,设置一个前缀名,前缀就类似于是资源的分组。然后再单击“Add”按钮下的“Add Files”选择图标文件即可。如果所选的图标文件不在本项目的子目录里,会提示复制文件到项目下的子目录。所以,最好将图标等原始文件放在项目的子目录下。
QAction是一个非常有用的类,在界面设计时创建Action,并编写其trigger()信号的槽函数。使用设计的Action可以创建菜单项、工具栏按钮,还可以设置为QToolButton按钮的关联Action。点击这些由Action创建的菜单项、按钮就是执行Action的槽函数。
在项目文件目录树里双击qwmainwind.ui,进入UI设计器,在窗体的下方有一个Action Editor的面板,图2-18是本项目设计好之后的Action列表。根据图标和文字就可以知道每个Action的功能。
图2-18 Action编辑器
在Action编辑器的上方有一个工具栏,可以新建、复制、粘贴、删除Action,还可以设置Action列表的显示方式。若要编辑某个Action,在列表里双击该Action即可。单击工具栏上的“New”按钮可以新建一个Action。新建或编辑Action的对话框如图2-19所示。
图2-19 新建或编辑一个Action
在此对话框里有以下的一些设置。
做好这些设置后,单击“OK”按钮就可以新建或修改Action了。所有用于菜单和工具栏设计的功能都需要用Action来实现。
建立Action之后,就可以在主窗体上设计菜单和工具栏了。本项目的窗体类QWMainWind是从QMainWindow继承的,具有菜单栏、工具栏和状态栏。
双击项目文件目录树里的qwmainwind.ui,在UI设计器里打开此窗口。在窗口最上方显示“Type Here”的地方是菜单栏,菜单栏下方是工具栏,窗口最下方是状态栏。
在菜单栏显示“Type Here”的地方双击,出现一个编辑框,在编辑框里输入所要设计菜单的分组名称,如“文件”,然后回车,这样就创建了一个“文件”菜单分组,同样可以创建“编辑”“格式”分组。
创建主菜单的分组后,从Action编辑器的列表里将一个Action拖放到菜单某个分组下,就可以创建一个菜单项,如同在界面上放置一个组件一样。如果需要在菜单里增加一个分隔条,双击“Add Seperator”就可以创建一个分隔条,然后拖动到需要的位置即可。如果需要删除某个菜单项或分隔条,单击右键,选择“Remove”菜单项。菜单设计的结果如图2-20所示。
图2-20 完成后的菜单栏各分组下的菜单项
设计工具栏的方式也很相似。将一个Action拖放到窗口的工具栏上,就会新建一个工具栏按钮。同样可以在工具栏上添加分隔条,可以移除工具栏按钮。主窗口上初始的只有一个工具栏,如果需要设计多个工具栏,在主窗口上单击右键,单击“Add Tool Bar”即可新建一个工具栏。
工具栏上按钮的显示方式有多种,只需设置工具栏的toolButtonStyle属性,这是Qt::ToolButtonStyle枚举类型,缺省的是Qt::ToolButtonIconOnly,即只显示按钮的图标。还可以设置为:
同时在窗体上放置一个QTextEdit组件,其objectname设置为txtEdit。如此就完成了菜单栏、工具栏的可视化设计。
可视化设计的界面的底层实现是由ui_qwmainwind.h文件实现的。打开ui_qwmainwind.h文件,能看到如下代码(已删除部分设置布局的代码)。
class Ui_QWMainWind
{
public:
QAction *actCut;
QAction *actCopy;
QAction *actPaste;
QAction *actFontBold;
QAction *actFontItalic;
QAction *actFontUnder;
QAction *actClose;
QAction *actOpen;
QAction *actClear;
QAction *actFont;
QAction *actNew;
QAction *actToolbarLab;
QWidget *centralWidget;
QtextEdit *txtEdit;
QSpinBox *spinBox;
QFontComboBox *fontComboBox;
QMenuBar *menuBar;
QMenu *menu;
QMenu *menu_2;
QMenu *menu_3;
QtoolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *QWMainWind)
{
if (QWMainWind->objectName().isEmpty())
QWMainWind->setObjectName(QStringLiteral("QWMainWind"));
QWMainWind->resize(586, 363);
QFont font;
font.setPointSize(11);
QWMainWind->setFont(font);
actCut = new QAction(QWMainWind);
actCut->setObjectName(QStringLiteral("actCut"));
QIcon icon;
icon.addFile(QStringLiteral(":/images/images/cut.bmp"), QSize(), QIcon::Normal, QIcon::Off);
actCut->setIcon(icon);
actCopy = new QAction(QWMainWind);
actCopy->setObjectName(QStringLiteral("actCopy"));
QIcon icon1;
icon1.addFile(QStringLiteral(":/images/images/120.bmp"), QSize(), QIcon::Normal, QIcon::Off);
actCopy->setIcon(icon1);
// 创建其他action,省略...
centralWidget = new QWidget(QWMainWind);
centralWidget->setObjectName(QStringLiteral("centralWidget"));
txtEdit = new QtextEdit(centralWidget);
txtEdit->setObjectName(QStringLiteral("txtEdit"));
txtEdit->setGeometry(QRect(20, 20, 231, 221));
QFont font1;
font1.setPointSize(16);
txtEdit->setFont(font1);
QWMainWind->setCentralWidget(centralWidget);
//主窗体创建菜单栏、工具栏和状态栏
menuBar = new QMenuBar(QWMainWind);
menuBar->setObjectName(QStringLiteral("menuBar"));
menuBar->setGeometry(QRect(0, 0, 586, 23));
menu = new QMenu(menuBar);
menu->setObjectName(QStringLiteral("menu"));
menu_2 = new QMenu(menuBar);
menu_2->setObjectName(QStringLiteral("menu_2"));
menu_3 = new QMenu(menuBar);
menu_3->setObjectName(QStringLiteral("menu_3"));
QWMainWind->setMenuBar(menuBar);
mainToolBar = new QtoolBar(QWMainWind);
mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
QWMainWind->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(QWMainWind);
statusBar->setObjectName(QStringLiteral("statusBar"));
QWMainWind->setStatusBar(statusBar);
//主菜单添加菜单项
menuBar->addAction(menu->menuAction());
menuBar->addAction(menu_2->menuAction());
menuBar->addAction(menu_3->menuAction());
menu->addAction(actNew);
menu->addAction(actOpen);
menu->addSeparator();
menu->addAction(actClose);
menu_2->addAction(actCut);
menu_2->addAction(actCopy);
menu_2->addAction(actPaste);
menu_2->addSeparator();
menu_2->addAction(actClear);
menu_3->addAction(actFontBold);
menu_3->addAction(actFontItalic);
menu_3->addAction(actFontUnder);
menu_3->addSeparator();
menu_3->addAction(actToolbarLab);
//主工具栏添加按钮
mainToolBar->addAction(actNew);
mainToolBar->addAction(actOpen);
mainToolBar->addAction(actClear);
mainToolBar->addSeparator();
mainToolBar->addAction(actCut);
mainToolBar->addAction(actCopy);
mainToolBar->addAction(actPaste);
mainToolBar->addSeparator();
mainToolBar->addAction(actFontItalic);
mainToolBar->addAction(actFontBold);
mainToolBar->addAction(actFontUnder);
mainToolBar->addSeparator();
retranslateUi(QWMainWind);
QMetaObject::connectSlotsByName(QWMainWind);
} // setupUi
};
ui_qwmainwind.h中定义了类Ui_QWMainWind。在public部分定义了界面所有Action和各种组件的指针变量,在setupUi()函数里依次创建所有的Action和界面组件,然后将Action添加到主菜单和工具栏上。
ui_qwmainwind.h的代码很长,上面的清单已省略了若干行。查看ui_qwmainwind.h里的代码有助于了解Action、菜单、工具栏的代码实现原理。若按照实例samp2_3的方法完全通过手工的方式来设计这样一个窗口无疑是一件繁琐的工作。好在采用UI设计器设计界面时,不需要动手去编写这些代码。界面设计的代码实现交给Qt去做就好了,它比手工编写代码效率高。
至此,在UI设计器里已经设计了这个应用程序的主要界面功能。现在想要实现如图2-16所示的界面,在工具栏上增加一个SpinBox用于设置字体大小,增加一个FontComboBox来选择字体。当从组件面板里拖放一个SpinBox到工具栏上时,却发现工具栏“拒收”!同样,在状态栏上放一个Label和一个ProgreeBar也是被“拒收”的。这是UI设计器的局限性,某些界面效果无法用可视化设计方式实现。
为此,需要编写一些代码来实现这些无法可视化设计的界面功能。先在QWMainWind类定义里增加一些变量和函数定义,增加的定义如下:
class QWMainWind : public QMainWindow
{
private:
QLabel *fLabCurFile;//状态栏里显示当前文件的Label
QProgressBar *progressBar1;//状态栏上的进度条
QSpinBox *spinFontSize;// 字体大小spinBox
QFontComboBox *comboFont;//字体名称comboBox
void iniUI(); //代码实现的UI初始化
};
iniUI()函数用于创建这些界面组件,并添加到工具栏或状态栏,其实现代码如下:
void QWMainWind::iniUI()
{//状态栏上添加组件
fLabCurFile=new QLabel;
fLabCurFile->setMinimumWidth(150);
fLabCurFile->setText("当前文件:");
ui->statusBar->addWidget(fLabCurFile);//添加到状态栏
progressBar1=new QProgressBar;
progressBar1->setMaximumWidth(200);
progressBar1->setMinimum(5);
progressBar1->setMaximum(50);
progressBar1->setValue(ui->txtEdit->font().pointSize());
ui->statusBar->addWidget(progressBar1); //添加到状态栏
//工具栏上添加组件
spinFontSize = new QSpinBox;//文字大小 SpinBox
spinFontSize->setMinimum(5);
spinFontSize->setMaximum(50);
spinFontSize->setValue(ui->txtEdit->font().pointSize());
spinFontSize->setMinimumWidth(50);
ui->mainToolBar->addWidget(new QLabel("字体大小 "));
ui->mainToolBar->addWidget(spinFontSize); //SpinBox添加到工具栏
ui->mainToolBar->addSeparator(); //分隔条
ui->mainToolBar->addWidget(new QLabel(" 字体 "));
comboFont = new QFontComboBox;
comboFont->setMinimumWidth(150);
ui->mainToolBar->addWidget(comboFont);//添加到工具栏
setCentralWidget(ui->txtEdit);
}
iniUI()函数实现如下的功能。
实现了iniUI()函数之后,在QWMainWind的构造函数里调用iniUI()函数,现在的构造函数代码实现如下:
QWMainWind::QWMainWind(QWidget *parent) : QMainWindow(parent),
ui(new Ui::QWMainWind)
{
ui->setupUi(this);
iniUI();
}
注意
iniUI()函数一定要在 ui->setupUi(this)之后再调用,两行语句的先后顺序不能调换。因为ui-> setupUi(this)实现了可视化设计的界面的创建,iniUI()是在可视化创建的界面基础之上添加其他组件,所以必须在其后面调用。
现在编译后运行就可以出现如图2-17所示的界面了,界面由可视化设计和代码设计混合实现。
Action是一种不可见的界面元素,主要用于菜单项、工具栏按钮的设计。Action的主要信号是trigger(),为一个Action的trigger()信号编写槽函数之后,菜单和工具栏上由此Action创建的菜单项和工具栏按钮就都关联此槽函数。
用于编辑的Action有剪切、复制、粘贴和清除的功能,以便对txtEdit组件进行相应的操作。而QTextEdit类就有相应的槽函数,无需再编写实现代码,只需将Action的trigger()信号与txtEdit的相应槽函数进行关联即可。在Signals和Slots编辑器里设置信号与槽的关联,设计好后的几个Action的关联如图2-21所示。
图2-21 在信号与槽编辑器里设置关联
Action的主要信号是trigger()和trigger(bool),在单击菜单项或工具栏按钮时发射。
用于设置粗体、斜体和下划线的3个Action具有Checkable属性,选择trigger(bool)信号设计槽函数更合适,该信号会将Action的复选状态作为一个参数传递,在响应代码里可以直接使用。其他Action可选择trigger()信号生成槽函数。在Action的“Go to slot”对话框,选择信号为Action创建槽函数。
下面是用于设置字体粗体的actFontBold槽函数代码,使用了trigger(bool)信号。
void QWMainWind::on_actFontBold_triggered(bool checked)
{
QtextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat();
if (checked)
fmt.setFontWeight(QFont::Bold);
else
fmt.setFontWeight(QFont::Normal);
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
其他Action的槽函数代码略。本实例只是为介绍混合法UI设计,以及应用程序开发的基本流程和方法,程序功能的具体实现不是本例的重点。例如,“打开文件”的代码实现涉及对话框调用、文件读取、数据流等操作,这些会在后续章节里介绍。
为了使界面和程序功能显得更加智能一点,应该根据当前的状态自动更新相关Action的checked和enabled属性,这是软件设计中常见的功能。
在本程序中,“剪切”“复制”“粘贴”的enabled属性应该随文本框内文字选择的状态变化而变化,“粗体”“斜体”“下划线”的checked属性应该随着当前文字的字体状态而自动更新。这可以针对QTextEdit的一些信号编写槽函数来实现。
在主窗体上选择文本编辑框txtEdit,在快捷菜单里调出“Go to slot”对话框。对话框里列出了QTextEdit的所有信号,有两个可以利用的信号。
为txtEdit组件的这两个信号生成槽函数定义和函数体框架,编写代码如下:
void QWMainWind::on_txtEdit_copyAvailable(bool b)
{ //更新cut,copy,paste的enabled属性
ui->actCut->setEnabled(b);
ui->actCopy->setEnabled(b);
ui->actPaste->setEnabled(ui->txtEdit->canPaste());
}
void QWMainWind::on_txtEdit_selectionChanged()
{//更新粗体、斜体和下划线3种action的checked属性
QtextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat(); //获取文字的格式
ui->actFontItalic->setChecked(fmt.fontItalic()); //是否斜体
ui->actFontBold->setChecked(fmt.font().bold()); //是否粗体
ui->actFontUnder->setChecked(fmt.fontUnderline());//是否有下划线
}
在界面上还有两个用代码创建的组件,用于设置字体大小的spinFontSize和用于设置字体的comboFont,这两个组件的信号与槽的功能实现显然不能通过前面所述的可视化的方法来实现。
对于手工创建的组件需要手工编写槽函数,并将信号与槽关联起来。
首先,需要确定利用组件的哪个信号来编写响应代码。例如,用于设置字体大小的SpinBox组件最合适的信号是valueChanged(int),用于字体设置的FontComboBox组件适合使用currentIndex Changed(QString)信号。
为此需要在QWMainWind类中定义两个槽函数,以及用于进行信号与槽关联的函数iniSignal Slots(),该函数在构造函数里调用。下面是QWMainWind类中的相关定义(省略了其他代码)。
class QWMainWind : public QMainWindow
{
private:
void iniSignalSlots(); //关联信号与槽
private slots:
// 自定义槽函数
void on_spinBoxFontSize_valueChanged(int aFontSize);//改变字体大小
void on_comboFont_currentIndexChanged(const QString &arg1);//选择字体
};
下面的代码是以上3个函数的实现。
void QWMainWind::iniSignalSlots()
{ //信号与槽的关联
connect(spinFontSize,SIGNAL(valueChanged(int)),
this,SLOT(on_spinBoxFontSize_valueChanged(int)));
connect(comboFont,SIGNAL(currentIndexChanged(const QString &)),
this,SLOT(on_comboFont_currentIndexChanged(const QString &)));
}
void QWMainWind::on_spinBoxFontSize_valueChanged(int aFontSize)
{//改变字体大小的SpinBox
QtextCharFormat fmt;
fmt.setFontPointSize(aFontSize); //设置字体大小
ui->txtEdit->mergeCurrentCharFormat(fmt);
progressBar1->setValue(aFontSize);
}
void QWMainWind::on_comboFont_currentIndexChanged(const QString &arg1)
{//FontCombobox的响应,选择字体名称
QtextCharFormat fmt;
fmt.setFontFamily(arg1); //设置字体名称
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
然后在QWMainWind的构造函数里调用iniSignalSlots()函数,实现手工创建的组件的信号与槽关联。
用Qt Creator创建的项目编译后的可执行文件具有默认的图标,如果需要为应用设置一个自己的图标,其操作很简单,只需两步。
RC_ICONS = AppIcon.ico
其中,“AppIcon.ico”就是复制到项目源程序目录下的图标文件名称。这样设置后,编译后生成的可执行文件,以及主窗口的图标就换成设置的图标了。
至此,这个例子的全部功能就都实现了。在这个实例里,我们介绍了Action的创建和使用,采用可视化的方式设计了大部分界面和槽函数。又采用手工编写代码的方式添加了其他的组件到界面上,并编写槽函数,进行信号与槽函数的关联。
这种可视化与代码化混合的设计方式与纯代码方式相比,避免了创建界面时大量繁琐的创建与布局工作,可以大大提高设计效率,同时又用代码实现了一些在UI设计器里无法可视化完成的一些界面效果。
Qt Creator在设计界面或编辑代码时,有一些快捷键和使用技巧,熟悉这些快捷键和使用技巧,可以提高工作效率。表2-6是Qt Creator的一些快捷操作的总结。
表2-6 源程序编辑器的快捷操作
功能 |
快捷键 |
解释 |
---|---|---|
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-22所示),也可以使用“开始”菜单Qt程序组里的Assistant单独打开帮助系统。
图2-22 使用Qt的帮助系统查看资料
在帮助文件显示界面上,左上方工具栏中有个下拉列表框,可以选择Bookmarks、Contents、Index和Search 4种模式。
在Qt帮助系统里可以搜索查看每个类的详细资料,如QTextEdit,可以看到这个类的详细资料,包括在这个类定义的公共类型、属性、公共函数、信号、公共槽等。
另外,若要查看类的继承关系,可以访问Qt官网的“Inheritance Hierarchy”页面。