
第16章 应用程序设计辅助功能本章介绍Qt应用程序设计的一些辅助功能包括设计多语言界面、使用样式表定制界面和组件的外观、使用QStyle设置界面外观以及应用程序发布等。这些辅助功能不一定在每个应用程序设计里都会用到但也是非常有用的一些功能如在大型的软件设计中一般要考虑使用多语言界面完成应用程序开发后还要考虑程序发布的问题。16.1 多语言界面16.1.1 多语言界面设计概述有些软件需要开发多语言界面版本如中文版和英文版并且在软件里可以方便地切换界面语言。Qt为多语言界面提供了很好的支持使用Qt的一些规则和工具可以很方便地为应用程序开发提供多语言界面支持。用Qt开发多语言界面应用程序主要包括以下几个步骤。1在程序设计阶段程序代码中每一个用户可见的字符串都用tr()函数封装以便Qt提取界面字符串用于生成翻译资源文件。用UI设计器可视化设计窗体时统一用一种语言如汉语。2在项目配置文件.pro文件中设置需要导出的翻译文件.ts文件名称使用lupdate工具扫描项目文件中需要翻译的字符串并生成翻译文件。3使用Qt的Linguist程序打开生成的翻译文件将程序中的字符串翻译为需要的语言如将所有中文字符串翻译为英文。4使用lrelease工具编译翻译好的翻译文件生成更为紧凑的“.qm”文件。5在应用程序中用QTranslator调用不同的“.qm”文件实现不同的语言界面。16.1.2 tr()函数的使用为了让Qt能自动提取程序中用户可见的字符串对于每个字符串都需要使用tr()函数封装。tr()是QObject的一个静态函数在使用了Q_OBJECT宏定义的类或QObject的子类中都可以直接使用tr()函数否则需要使用QObject::tr()进行调用。或者在类定义中用Q_DECLARE_TR_FUNCTIONS宏把tr()函数添加到类中之后再直接调用tr()函数。tr()函数的定义是QString QObject::tr(const char *sourceText, const char *disambiguation Q_NULLPTR, int n -1)其中sourceText是源字符串disambiguation是为翻译者提供额外信息的字符串用于对一些容易混淆的地方作说明内容如下LabCellPos new QLabel(tr(当前单元格),this); QMessageBox::information(this, tr(信息), tr(信息提示), QMessageBox::Yes); QString str1tr(左右, 大约的意思); QString str2tr(左右, 掌握、控制的意思);使用tr()函数需要注意以下一些事项。尽量使用常量字符串不要使用字符串变量。在tr()函数中应直接传递字符串常量而不是用变量传递字符串如下面的代码使用了字符串变量使用lupdate工具提取项目中的字符串时将不能提取“不能删除记录”这个字符串。char *errorStr不能删除记录; QString str2tr(errorStr);使用字符串变量时需要用Qt_TR_NOOP()宏进行标记。若要在tr()函数中使用字符串变量需要在定义字符串的地方用Qt_TR_NOOP()宏进行标记这在使用字符串数组时比较有用例如const char *cities[4]{Qt_TR_NOOP(Beijing), Qt_TR_NOOP(Shanghai), Qt_TR_NOOP(Qingdao), Qt_TR_NOOP(Wuhan) }; for (int i0; i4; i) comboBox-addItem(tr(cities[i]));tr()不能使用拼接的动态字符串。tr()不能使用拼接的动态字符串例如下面的用法是错误的LabCellPos-setText(tr(第 QString::number(current.row()) 行);正确的方式如下。LabCellPos-setText(tr(第 %1 行).arg(current.row()));翻译的字符串是第 %1 行然后再用QString的arg()去替换占位符“%1”的内容。Qt_NO_CAST_FROM_ASCII 的作用。在一个需要翻译为多语言的应用程序中如果编写程序时忘了对某个字符串使用tr()函数lupdate生成的翻译资源文件就会遗漏这个字符串。为了避免这种疏忽错误可以在项目配置文件.pro文件中添加如下的定义DEFINES Qt_NO_CAST_FROM_ASCII这样在编译时会禁止从const char* 到QString的隐式转换强制每个字符串都必须使用tr()或QLatin1String()封装避免出现遗漏未翻译的字符串。16.1.3 生成语言翻译文件要生成多语言界面相关的翻译文件除了之前所说的在对每个字符串都使用tr()函数封装之外还需要在项目配置文件.pro文件中使用TRANSLATIONS定义语言翻译文件.ts文件并使用lupdate工具生成语言翻译文件。作为实例将第6章的samp6_2作为多语言界面设计实例复制项目samp6_2的全部文件并将项目名称更改为samp16_1。在项目的配置文件中增加如下的设置语句TRANSLATIONS samp16_1_cn.ts\ samp16_1_en.ts这里设置生成两个语言翻译文件“samp16_1_cn.ts”和“samp16_1_en.ts”分别是中文和英文翻译文件。文件名称可以任意设计只要有所区分即可。原始的程序设计采用中文。为了便于进行语言切换在主窗口上设置了两个工具栏按钮分别用于切换中文和英文界面如原书的图16-1所示界面语言切换的代码实现会在下一小节介绍。为了让lupdate工具能提取项目代码内的字符串这里对项目程序里的字符串全部采用tr()函数封装对于不符合tr()函数使用规则的地方进行修改。例如之前在状态栏上显示当前单元格的行号和列号的程序是LabCellPos-setText(QString::asprintf(当前单元格%d行%d列, current.row(),current.column()));现在就修改为如下形式LabCellPos-setText(tr(当前单元格%1 行%2 列 ).arg(current.row()).arg(current.column()));在项目设计期间任何时候都可以使用lupdate工具生成或更新翻译文件方法是单击Qt Creator主菜单的“Tools”→“External”→“Qt 语言家”→“Update Translations(lupdate)”菜单项若项目的源程序目录下没有samp16_1_cn.ts和samp16_1_en.ts这两个文件就会自动生成如果文件已经存在则会更新这两个文件的内容。16.1.4 使用Qt Linguist翻译ts文件生成的samp16_1_cn.ts和samp16_1_en.ts文件内包含了项目源程序和UI界面里的所有字符串使用Qt Linguist可以将这些字符串翻译为需要的语言版本。在Qt安装后的程序组里可以找到Qt Linguist软件。samp16_1_cn.ts是中文界面的翻译文件由于源程序的界面就是用中文设计的所以无需再翻译。samp16_1_en.ts是英文翻译文件需要将提取的所有中文字符串翻译为英文。在Linguist软件中打开文件samp16_1_en.ts当第一次打开一个ts文件时Linguist会出现如原书的图16-2所示的语言设置对话框用于设置目标语言和所在国家和地区。这个对话框也可以通过Linguist主菜单的“编辑”→“翻译文件设置”菜单项调出。samp16_1_en.ts是用于英文界面的翻译文件所以选择语言“English”国家/地区可选择“UnitedStates”。打开samp16_1_en.ts文件后的Linguist软件界面如原书的图16-3所示。左侧“上下文”列表里列出了项目中的所有窗口或类这个项目有4个窗口。“字符串”列表里列出了从项目的UI窗口和代码文件中提取的字符串右侧“短语和表单”会显示窗口界面的预览或字符串在源程序中出现的代码段。在“字符串”列表中选择一个源文后在下方会出现译文编辑框在此填写字符串对应的英文译文。Linguist可以同时打开项目的多个ts文件在选中一个源文后在下方会出现对应的多个语言的译文编辑框可以同时翻译为多个语言版本。16.1.5 调用翻译文件改变界面语言1生成qm文件使用Linguist软件编辑翻译文件将所有字符串都翻译后在Qt Creator中单击主菜单项“Tools”→“External”→“Qt 语言家”→“Release Translations(lrelease)”会在项目源程序目录下生成与ts文件对应的qm文件这是更为紧凑的翻译文件。本实例生成的是samp16_1_cn.qm和samp16_1_en.qm。2项目启动时设置界面语言使用QTranslator类设置界面的不同语言版本需在应用程序启动时设置界面语言翻译文件即在main()函数中进行处理。项目samp16_1的main.cpp的代码如下#include mainwindow.h #include QApplication #include QTranslator #include QSettings QTranslator *transNULL; QString readSetting(); int main(int argc, char *argv[]) { QApplication app(argc, argv); transnew QTranslator; QString curLangreadSetting(); //读取语言设置 if (curLangEN) trans-load(samp16_1_en.qm); else trans-load(samp16_1_cn.qm); app.installTranslator(trans); MainWindow w; w.show(); return app.exec(); } QString readSetting() {//从注册表读取上次设置的语言 QString organizationWWB-Qt; QString appNamesamp16_1; QSettings settings(organization,appName); QString Languagesettings.value(Language,EN).toString(); return Language; }这里定义了一个全局变量trans在main.cpp里定义了一个函数readSetting()用于从注册表里读取上次设置的界面语言版本。注册表里数据的读取和写入使用到QSettings类在6.5节有详细介绍。在main()函数中创建QTranslator类的对象trans后调用readSetting()函数从注册表里读取上次的语言版本若为“EN”就用load()函数载入编译后的英文翻译文件samp16_1_en.qm否则就载入中文翻译文件samp16_1_cn.qm。然后再执行app.installTranslator(trans)就可以给应用程序安装翻译器实现需要的界面版本。samp16_1_en.qm和samp16_1_cn.qm文件是被编译到项目的可执行文件中的无需将这两个文件复制到可执行文件目录下。在main()函数里加载翻译器是相比之下一劳永逸的方法这样一来随后应用程序的任何窗口都会自动应用开始设置的语言。所以一些大型的软件在重新设置了语言版本后通常都会要求重新启动软件才生效。3动态切换语言在软件运行时可以动态切换语言即无需重启软件就可以切换界面语言。在samp16_1的主窗口上有“中文”和“English”两个工具栏按钮用于实现中文和英文界面的切换。下面是这两个按钮的响应代码void MainWindow::on_actLang_CN_triggered() {//中文界面 qApp-removeTranslator(trans); delete trans; transnew Qtranslator; trans-load(samp16_1_cn.qm); qApp-installTranslator(trans); ui-retranslateUi(this); //刷新界面字符串 QSettings settings(WWB-Qt,samp16_1); settings.setValue(Language,CN); } void MainWindow::on_actLang_EN_triggered() {//英文界面 qApp-removeTranslator(trans); delete trans; transnew Qtranslator; trans-load(samp16_1_en.qm); qApp-installTranslator(trans); ui-retranslateUi(this); //刷新界面字符串 QSettings settings(WWB-Qt,samp16_1); settings.setValue(Language,EN); }一个应用程序只能加载一个翻译器因为在main()函数里已经加载了一个翻译器所以需要先移除当前的翻译器才能重新创建新的翻译器加载翻译文件并为应用程序重新加载新翻译器。完成这些后还必须调用UI的retranslateUi()函数来刷新界面。retranslateUi()函数是在窗口的“ui_”头文件中自动生成的如mainwindow.ui主窗口对应的头文件ui_mainwindow.h里就有函数retranslateUi()这个函数使用QApplication::translate()函数将所有界面字符串进行翻译类似于tr()函数的功能。窗口在被创建时会自动调用此retranslateUi()函数在程序运行中动态切换界面语言时必须手工调用此retranslateUi()函数才可以立即更新界面语言。若不是用UI Designer设计的窗口而是完全由代码实现的窗口界面需要专门设计一个retranslateUi()函数将所有界面字符串用tr()函数进行翻译并在动态切换语言时调用此函数。很显然这样做比较麻烦特别是当软件比较大窗口非常多时。所以大型的软件在重新设置语言版本后一般要求重新启动软件才生效在程序启动时根据上次的设置加载一次翻译器即可。按钮响应代码的最后是将设置的语言版本写入注册表以便下次程序启动时自动加载相应的语言。16.2 使用样式表自定义界面16.2.1 Qt样式表Qt样式表style sheet是用于定制用户界面的强有力的机制其概念、术语是受到HTML中的级联样式表Cascading Style SheetsCSS启发而来的只是Qt 样式表是应用于窗体界面的。与HTML的CSS类似Qt的样式表是纯文本的格式定义在应用程序运行时可以载入和解析这些样式定义。使用样式表可以定义各种界面组件QWidget类及其子类的样式从而使应用程序的界面呈现不同的效果。很多软件具有换肤功能使用Qt的样式表就可以容易地实现这样的功能。在UI设计器中集成了Qt样式表的编辑功能。在设计窗时选择窗体或某个界面组件单击鼠标右键在弹出的快捷菜单中选择“Change styleSheet…”菜单项就可以出现样式表编辑对话框。原书的图16-4所示是某个窗体的样式表编辑对话框已经对窗体和一些类设置了样式定义例如QWidget{ background-color: rgb(255, 255, 0); color: rgb(255, 0, 0); font: 12pt 宋体; }这定义了QWidget类的背景颜色、字体大小和名称、前景颜色。这个样式定义会应用于QWidget类及其子类。QLineEdit{ border: 2px groove gray; border-radius: 5px; padding: 2px 4px; border-color: rgb(12,45,68); }这定义了QLineEdit类的显示效果包括边框宽度、圆角边框的半径和边框颜色等。在原书的图16-4中的对话框内上方有几个具有下拉菜单的按钮可以添加一些常用的样式属性如前景色color、背景色background-color、选中后颜色selection-color以及背景图片background- image等。16.2.2 Qt样式表句法1一般句法格式Qt样式表的句法syntax与HTML的CSS句法几乎完全相同。Qt样式表包含一系列的样式法则一个样式法则由一个选择器selector和一些声明declaration组成。例如QPlainTextEdit{ font: 12pt 仿宋; color: rgb(255, 255, 0); background-color: rgb(0, 0, 0); }其中QPlainTextEdit就是选择器表明后面花括号里的样式声明应用于QPlainTextEdit类及其子类。样式声明部分是样式法则列表每个样式法则由属性和值组成每条法则用分号结束。每条样式法则由“属性值”构成例如font: 12pt 仿宋;表示font属性字体大小为12pt字体名称为“仿宋”当一个属性有多个值时多个值用空格隔开。2选择器selectorQt样式表支持CSS2中定义的所有选择器表16-1显示的是一些常用的选择器。表16-1 Qt样式表中的选择器类型选择器例子用途通用选择器*所有组件类型选择器QPushButton所有QPushButton类及其子类的组件属性选择器QPushButton[flat”false”]所有flat 属性为false的QPushButton类及其子类的组件。如果样式表应用后组件的属性再发生变化需要重新应用样式表才能刷新显示效果非子类选择器.QPushButton所有QPushButton类的组件但是不包括QPushButton的子类ID选择器QPushButton#btnOKObjectName为btnOK的QPushButton实例从属对象选择器QDialog QPushButton所有从属于QDialog的QPushButton类的实例即QDialog对话框里的所有QPushButton子对象选择器QDialog QPushButton所有直接从属于QDialog的QPushButton类的实例这些选择器的定义为选择界面组件提供了灵活性。选择器可以组合使用一个样式声明可以应用于多个选择器例如QPlainTextEdit,QLineEdit,QPushButton,QCheckBox{ color: rgb(255, 255, 0); background-color: rgb(0, 0, 0); }这个样式声明将同时应用于QPlainTextEdit、QLineEdit、QPushButton和QCheckBox的实例。QLineEdit[readOnlytrue], QCheckBox[checkedtrue] { background-color: rgb(255, 0, 0) }上面的这个样式应用于readOnly属性为true的QLineEdit和checked属性为true的QCheckBox实例功能是使其背景颜色为红色。在Qt中可以为一个界面组件使用QObject::setProperty()设置一个动态属性例如在数据表编辑界面上一些字段是必填字段就可以为这些字段的关联组件设置一个required属性为true如editName-setProperty(required, true); comboSex-setProperty(required, true); checkAgree-setProperty(required, true);这样设置了三个界面组件的动态属性required为true。那么可以应用下面的样式将这种必填字段的背景颜色设置为亮绿色。*[requiredtrue] {background-color: lime}3子控件sub-controls对于一些组合的界面组件需要对其子控件进行选择如QComboBox的下拉按钮或QSpinBox的上、下按钮。通过选择器的子控件可以对这些界面元素进行显示效果控制。例如QComboBox::drop-down{ image: url(:/images/images/down.bmp); }选择器QComboBox::drop-down选择了QComboBox的drop-down子控件定义的样式是设置其image属性为资源文件中的图片down.bmp。QSpinBox::up-button{ image: url(:/images/images/up.bmp); } QSpinBox::down-button{ image: url(:/images/images/down.bmp); }这两条样式定义语句分别定义了QSpinBox的上、下按钮的图片用资源文件中的图片替代了缺省的图片。这样定义的QComboBox和QSpinBox具有如原书的图16-5所示的显示效果。Qt中常用的子控件见表16-2所有子控件的详细描述见Qt的帮助文档。表16-2 Qt样式表中常见的子控件列表子控件名称说明::branchQTreeView的分支指示器::chunkQProgressBar 的进度显示块::close-buttonQDockWidget或QTabBar页面的关闭按钮::down-arrowQComboBoxQHeaderView (排序指示器)QScrollBar 或QSpinBox的下拉箭头::down-buttonQScrollBar或 QSpinBox的向下按钮::drop-downQComboBox的下拉按钮::float-buttonQDockWidget的浮动按钮::grooveQSlider的凹槽::indicatorQAbstractItemViewQCheckBoxQRadioButton可勾选的QMenu 菜单项或可勾选的QGroupBox的指示器::handleQScrollBarQSplitter或QSlider的滑块::iconQAbstractItemView或 QMenu的图标::itemQAbstractItemViewQMenuBarQMenu或QStatusBar的一个项::left-arrowQScrollBar的向左箭头::menu-arrow具有下拉菜单的QToolButton的下拉箭头::menu-buttonQToolButton的菜单按钮::menu-indicatorQPushButton的菜单指示器::right-arrowQMenu 或QScrollBar的右侧箭头::paneQTabWidget的面板::scrollerQMenu 或 QTabBar的卷轴::sectionQHeaderView的分段::separatorQMenu 或QMainWindow的分隔器::tabQTabBar 或QToolBox的分页::tab-barQTabWidget的分页条。这个子控件只用于控制QTabBar在QTabWidget中的位置定义分页的样式使用::tab子控件::textQAbstractItemView的文字::titleQGroupBox或QDockWidget的标题::up-arrowQHeaderView (排序指示器)QScrollBar或 QSpinBox的向上箭头::up-buttonQSpinBox的向上按钮4伪状态pseudo-states选择器可以包含伪状态使得样式法则只能应用于界面组件的某个状态也就是一种条件应用法则。伪状态出现在选择器的后面用一个冒号:隔开。如下面的样式法则QLineEdit:hover{ background-color: black; color: yellow; }定义了当鼠标移动到QLineEdit上方时hover改变QLineEdit的背景色和前景色。可以对伪状态取反方法是在伪状态前面加一个感叹号!如QLineEdit:!read-only{ background-color: rgb(235, 255, 251); }这定义了readonly属性为false的QLineEdit的背景色。伪状态可以串联使用相当于逻辑与的计算例如QCheckBox:hover:checked{ color: red; }这定义了当鼠标移动到一个被勾选了的QCheckBox组件上方时其字体颜色变为红色。伪状态可以并联使用相当于逻辑或的计算例如QCheckBox:hover, QCheckBox:checked{ color: red; }这表示鼠标移动到QCheckBox组件上方或QCheckBox组件被勾选时字体颜色变为红色。子控件也可以使用伪状态如QCheckBox::indicator:checked{ image: url(:/images/images/checked.bmp);} QCheckBox::indicator:unchecked{image: url(:/images/images/unchecked.bmp);}这里定义了QCheckBox的indicator在checked和unchecked两种状态下的显示图片可以得到如原书的图16-6所示的效果。Qt样式定义中常见的一些伪状态见表16-3熟悉这些伪状态并灵活应用可以定义自己想要的界面效果。表16-3 Qt样式表中常见的伪状态伪状态描述:active当组件处于一个活动的窗体时此状态为真:adjoins-itemQTreeView::branch与一个条目相邻时此状态为真:alternate当QAbstractItemView的alternatingRowColors()属性为true时绘制交替的行时此状态为真:bottom组件处于底部如QTabBar的表头位于底部:checked组件被勾选如QAbstractButton的checked属性为true:closable组件可以被关闭例如当QDockWidget 的DockWidgetClosable属性为true时:closed条目处于关闭状态例如QTreeView的一个没有展开的条目:default条目是缺省的如一个缺省的QPushButton按钮或QMenu中一个缺省的action:disabled条目被禁用:editableQComboBox是可编辑的:edit-focus条目有编辑焦点:enabled条目被使能:exclusive条目是一个排他性组的一部分例如一个排他性QActionGroup的一个菜单项:first第一个项例如QTabBar中的第一个页:flat条目是flat的例如QPushButton的flat属性设置为true时:focus条目具有输入焦点:has-children条目有子条目例如QTreeView的一个节点具有子节点:horizontal条目具有水平方向:hover鼠标移动到条目上方时:last最后一个项例如QTabBar中的最后一页:left条目位于左侧例如QTabBar的页头位于左侧:maximized条目处于最大化例如最大化的QMdiSubWindow窗口:minimized条目处于最小化例如最小化的QMdiSubWindow窗口:movable条目是可移动的:off对于可以切换状态的条目其状态处于”off”:on对于可以切换状态的条目其状态处于”on”:open条目处于打开状态例如QTreeView的一个展开的条目:pressed条目上按下了鼠标:read-only条目是只读或不可编辑的:right条目位于右侧例如QTabBar的页头位于右侧:selected条目被选中例如QTabBar中一个被选中的页或QMenu中一个被选中的菜单项:top条目位于顶端例如QTabBar的页头位于顶端:unchecked条目处于被被选中状态:vertical条目处于垂直方向5属性Qt样式表内对每一个选择器可定义多条样式规则每个规则是一个“属性值”对Qt样式表中可定义的属性很多可以在Qt的帮助文件中查找“Qt Style Sheets Reference”查看所有属性的详细说明。在原书的图16-4所示的样式表编辑对话框中从上方的几个按钮的下拉菜单中可以设计常用的一些属性如“Add Resource”按钮下三个菜单项可以从项目的资源文件中选择图片作为background- image、border-image或image属性的值“Add Color”按钮的下拉菜单用于设置组件的各种颜色包括前景色、背景色、边框颜色等颜色的值可以用rgb()、rgba()函数表示或Qt能识别的颜色常量。使用样式表可以定义组件复杂的显示效果。每个界面组件都可以用如原书的图16-7所示的盒子模型Box Model来表示模型由四个同心矩形表示。1content是显示内容矩形区域如QLineEdit用于显示文字的区域min-width、max-width、min-height和max-height属性定义最大/最小宽度或高度就是定义这个矩形区例如QLineEdit{ min-width:50px; max-height:40px; }这定义QLineEdit最小宽度为50px最大高度是40px其中px是单位表示像素。2padding是包围content的矩形区域通过padding属性可以定义padding的宽度或padding-top、padding-bottom、padding-left和padding-right分别定义padding的上、下、左、右宽度例如QLineEdit{ padding: 0px 10px 0px 10px;}这设定padding的上、右、下、左的宽度它等效于QLineEdit{ padding-top:0px; padding-right:10px; padding-bottom: 0px; padding-left:10px; }3border是包围padding的边框通过border属性或border-width、border-style、border-color可以定义边框的线宽、线型和颜色也可以分别定义border的上、下、左、右的线宽和颜色。使用border-radius可以定义边框转角的圆弧半径从而构造具有圆角矩形的编辑或按钮等组件例如QLineEdit{ border-width: 2px; border-style: solid; border-color: gray; border-radius: 10px; padding: 0px 10px; }这使得QLineEdit具有灰色边框线条、圆角矩形的效果。通过border-radius、min-width和min-height等属性可以设计圆形的按钮如QPushButton { border: 2px groove red; border-radius: 30px; min-width:60px; min-height:60px; }使得边框转角半径等于content宽度或长度的一半宽度和长度相等就可以得到一个圆形的按钮。使用border-image属性还可以为组件设置背景图片图片会填充border矩形框之内的区域一般使用材质图片设置背景以使界面具有统一的特色例如QLineEdit, QPushButton{border-image: url(:/images/images/border.jpg);}4margin是border之外与父组件之间的空白边距可以分别定义上、下、左、右的边距大小。缺省的情况下margin、border-width和 padding 属性缺省值为零这种情况下四个同心矩形就是重合的一个矩形。使用Qt样式表可以为界面组件设计各种美观的显示效果美观而特殊的界面不仅需要编程的能力更重要的是美工设计能力。16.2.3 样式表的使用1程序中使用样式表有多种方法可以应用样式表。第一种是在使用Qt Designer设计窗体时直接用样式表编辑器为窗体或窗体上的某个部件设计样式表则设计的样式保存在窗体的ui文件里窗体创建时会自动应用所设计的样式表。这样设计的样式表对应用程序是固定的无法取得换肤的效果而且需要为每个窗体都设计样式表重复性工作量太大。第二种是使用setStyleSheet函数应用样式使用qApp的setStyleSheet()函数可以为应用程序全局设置样式使用QWidget::setStyleSheet()可以为一个窗口、一个对话框或一个界面组件设置样式。例如qApp-setStyleSheet(QLineEdit { background-color: gray });这里使用应用程序全局变量qApp为QLineEdit设置样式如果应用程序内的某些QLineEdit组件没有再被设置样式则QLineEdit组件的背景色为灰色。MainWindow-setStyleSheet(QLineEdit { background-color: lime });这是为主窗口MainWindow内的QLineEdit组件设置样式即背景色为亮绿色。editName-setStyleSheet(color: blue; background-color: yellow; selection-color: yellow; selection-background-color: blue;);这是设置一个ObjectName为editName的QLineEdit组件的样式注意这时在样式表中无需设置selector名称所设置的样式是应用于editName这个QLineEdit组件的。这样将样式表固定在程序中很显然也是无法实现切换界面效果的。为了实现切换界面效果换肤的目的一般将样式定义表保存为qss后缀的纯文本文件然后在程序中打开文件读取文本内容再调用setStyleSheet()函数应用样式。示例代码如下QFile file(:/qss/mystyle.qss ); file.open(QFile::ReadOnly); QString styleSheet QString::fromLatin1(file.readAll()); qApp-setStyleSheet(styleSheet);2样式定义的明确性当多条样式法则对一个属性定义了不同值时就会出现冲突例如QPushButton#btnSave { color: gray } QPushButton { color: red }这两条法则都可以应用于ObjectName为btnSave的QPushButton组件都定义了其前景色这就会出现冲突。这时选择器的明确性specificity决定组件适用的样式法则即法则应用于更明确的组件。在上面的例子中QPushButton#btnSave被认为是比QPushButton更明确的选择器因为它指向一个对象而不是QPushButton的所有实例。所以如果是在一个窗口上应用以上两条法则则btnSave按钮的前景色为gray而其他按钮的前景色为red。同样具有伪状态的选择器被认为比没有伪状态的选择器明确性更强如QPushButton:hover { color: white } QPushButton { color: red }这样当鼠标在按钮上停留时颜色为white否则颜色为red。如果两个选择器具有相同的明确性则以法则出现的先后顺序为准后出现的法则起作用例如QPushButton:hover { color: white } QPushButton:enabled { color: red }这里的两个选择器具有相同的明确性所以当鼠标停留在一个使能的按钮上时只有第二条法则起作用。这种情况下如果希望不出现冲突应该修改法则以使其更明确如下面这两条法则是不冲突的。QPushButton:hover:enabled { color: white } QPushButton:enabled { color: red }父子关系的两个类作为选择器时具有相同的明确性例如QPushButton { color: red } QAbstractButton { color: gray }这两个选择器的明确性相同所以只依赖于语句的先后顺序。确定法则的明确性Qt样式表遵循CSS2的规定在设计样式表时应尽量明确并避免冲突情况。3样式定义的级联性样式定义可以在qApp、窗口或一个具体组件中定义任何一个组件的样式是其父组件、父窗口和qApp的样式的融合。当出现冲突时组件会使用离自己最近的样式定义即按顺序使用组件自己的样式、或父组件的样式定义、或父窗口的样式定义或qApp的样式定义而不考虑样式选择器的确定性。例如在QApplication中设置全局样式qApp-setStyleSheet(QPushButton { color: red });那么应用程序中所有未再定义样式的QPushButton的前景颜色为red。如果在MainWindow中再定义样式MainWindow-setStyleSheet(QPushButton { color: blue });则MainWindow上的按钮的前景色为blue而不是red。如果MainWindow上有一个名称为btnSave的QPushButton按钮其定义样式如下btnSave-setStyleSheet(color: yellow; background-color: black;);则按钮btnSave按照自己的样式显示前景和背景色。Qt样式表功能强大可以设计自己想要的界面效果但是这需要有较好的美工设计基础需要在使用样式表的过程中不断尝试和总结经验。16.3 使用QStyle设置界面外观16.3.1 QStyle的作用Qt是一个跨平台的类库相同的界面组件在不同的操作系统上显示效果是不一样的。QStyle是封装了GUI界面组件外观的抽象类Qt定义了QStyle类的一些子类应用于不同的操作系统如QWindowsStyle和QMacStyle等。这些样式是Qt GUI模块自带的在不同的平台上编译运行的程序具有缺省的样式QApplication::style()可以返回应用程序缺省的样式。Qt内置的界面组件都使用QStyle进行绘制以保证它们与运行平台的界面效果一致如原书的图16-8所示是QComboBox在不同操作系统上的九种不同的样式。QStyleFactory类管理着Qt的内置样式QStyleFactory有两个静态函数keys()和create()其函数原型如下QStringList QStyleFactory::keys() QStyle *QStyleFactory::create(const QString key)keys()函数返回一个字符串列表是所在平台支持的QStyle的名称列表create()函数根据样式名称字符串创建一个QStyle对象。QApplication有两个静态函数用于操作样式其函数原型为QStyle *QApplication::style() void QApplication::setStyle(QStyle *style)style()函数返回应用程序当前的样式任何一个GUI应用程序在创建时就有一个缺省的样式通过下面的语句QApplication::style()-metaObject()-className()可以获得这个缺省样式的名称。setStyle()为应用程序设置一个样式设置样式后界面元素都具有这个样式所定义的外观。除了这些Qt内置的样式用户也可以从QStyle类继承定义自己的样式一般是从QStyle的子类QProxyStyle继承。16.3.2 Qt内置样式的使用使用Qt内置的样式可以通过QStyleFactory::keys()获取运行平台支持的样式列表然后用QStyleFactory::create()创建样式再用QApplication::setStyle()设置样式即可。创建一个基于QMainWindow的Widget应用程序samp16_2并设计界面。如原书的图16-9所示是设置为QWindowsStyle样式时的运行界面具有老式的 Windows界面效果。下面是主窗口构造函数的代码QMainWindow(parent), ui(new Ui::MainWindow) { ui-setupUi(this); QRegExp regExp(.(.*)\\?Style); QString defaultStyle QApplication::style()-metaObject()-className(); ui-statusBar-showMessage(defaultStyle); if (regExp.exactMatch(defaultStyle)) defaultStyle regExp.cap(1); ui-comboSysStyle-addItems(QStyleFactory::keys()); ui-comboSysStyle-setCurrentIndex(ui-comboSysStyle-findText( defaultStyle, Qt::MatchContains)); }在构造函数中通过QStyleFactory::keys()获取当前平台上的样式名称列表并添加到下拉列表框comboSysStyle里。程序运行后在“系统样式”下拉列表框里会显示当前平台支持的样式列表例如在Windows平台上列表里会有Widnows、WidnowsXP、WidnowsVista和Fusion。在“系统样式”下拉列表框中选择一个样式可以为应用程序设置样式下拉列表框comboSysStyle的currentIndexChanged()信号响应槽函数代码如下void MainWindow::on_comboSysStyle_currentIndexChanged(const QString arg1) { // qApp-setStyle(arg1); QStyle *styleQStyleFactory::create(arg1); qApp-setStyle(style); ui-statusBar-showMessage(style-metaObject()-className()); }这里首先使用QStyleFactory::create(arg1)根据选择的样式名称字符串arg1创建样式style然后使用qApp-setStyle(style)为应用程序设置样式。如果不是需要显示样式的类名称直接使用qApp-setStyle(arg1)也可以为应用程序设置样式。窗口上有“取消样式表”和“应用样式表”两个按钮代码如下void MainWindow::on_btnNormal_clicked() {//取消所有样式表 this-setStyleSheet(); } void MainWindow::on_btnStyleSheet_clicked() {//设置样式表 this-setStyleSheet(QPlainTextEdit{color: blue; font: 13pt 宋体;} QPushButton:hover{background-color:lime;} QLineEdit{ border: 2px groove red; background-color: rgb(170, 255, 127); border-radius: 6px;} QCheckBox:checked{color: red;} QRadioButton:checked{color:red;} ); }“应用样式表”按钮的代码为界面上的几个显示组件类设置了样式表在设置样式表后即使修改窗口样式这些样式表定义的显示效果依然存在。16.4 Qt应用程序的发布16.4.1 应用程序发布方式用Qt开发一个应用程序后将应用程序提供给用户在其他计算机上使用就是应用程序的发布。应用程序发布一般会提供一个安装程序将应用程序的可执行文件及需要的运行库安装到用户计算机上即使用户计算机上没有安装Qt也能正常运行安装的程序。Qt的应用程序发布有两种方式静态链接和共享库方式。静态链接Static linking是指用Qt编译应用程序时将Qt的运行库等所需的支持文件全部静态编译到应用程序里生成一个独立的可执行文件应用程序发布只需很少的几个文件。这种方式的缺点是应用程序可执行文件很大缺少灵活性。例如当应用程序需要更新或Qt有更新时需要重新编译应用程序后再发布。而且静态链接方式不能部署插件。共享库Shared Libraries方式是指按正常方式编译生成应用程序将应用程序运行所需的各种共享库与应用程序一同发布给用户。这样当Qt的运行库更新时可以单独更新Qt运行库应用程序如果使用了插件插件是以共享库形式存在的也可以单独更新插件这为应用程序更新提供了方便。如果要使用静态链接发布应用程序还需要将Qt以静态方式重新编译生成静态版本的Qt然后用静态版本的Qt编译和链接应用程序才可以生成静态链接的应用程序。这个过程显然很花时间也很复杂。所以一般应用程序发布都采用共享库的形式。16.4.2 Windows平台上的应用程序发布Qt是跨平台的开发工具开发的应用程序可以在各种平台上发布本书只以常用的Windows平台为例介绍Qt应用程序的发布。1Windows发布工具windeployqt.exe是Qt自带的Windows平台发布工具它可以自动为一个应用程序复制其运行所需的各种库文件、插件和翻译文件生成可发布的目录。windeployqt.exe文件在Qt的bin目录下Qt的每一个编译器版本均有独立的目录如计算机上的Qt安装有minGW32、MSVC2015 32bit和MSVC2015 62bit 3个编译器版本Qt安装在D:\Qt目录下则3个版本的bin目录是D:\Qt\Qt5.9.1\5.9.1\mingw53_32\bin D:\Qt\Qt5.9.1\5.9.1\msvc2015\bin D:\Qt\Qt5.9.1\5.9.1\msvc2015_64\bin注意应用程序由哪个编译器生成的就应该用哪个版本的windeployqt生成发布文件。在Windows的Command窗口使用windeployqt程序其句法如下windeployqt [options] [files]其中options是一些选项设置可以查看Qt帮助文件查看具体设置一般使用缺省设置即可files是需要生成发布文件的应用程序文件名。要在Command窗口使用windeployqt最好的方法是将windeployqt版本所在的bin路径添加到系统的PATH环境变量里。在Windows平台上windeployqt会将编译器的运行时文件复制到发布目录下如果是用MSVC编译的就包含Visual C的运行库。2MinGW编译的应用程序发布实例将15.2节的音频播放器实例程序samp15_1用MinGW编译器在release模式下编译生成可执行文件samp15_1.exe。将此可执行文件复制到一个目录下例如“F:\Setup\minGW”并将samp15_1.exe更名为AudioPlayer.exe。将MinGW版本的Qt的bin路径添加到系统的PATH环境变量里然后在Windows的“开始”菜单的文本框里键入“cmd”回车后进入Command窗口在Command窗口里依次键入并执行以下指令F: cd setup\minGW windeployqt AudioPlayer.exe执行完成后windeployqt将AudioPlayer.exe运行时需要的各种库文件都复制到目录“F:\Setup\minGW”下如原书的图16-10所示。为测试生成的发布目录下的文件是否齐全将环境变量PATH中的Qt的bin目录去除然后双击图16-10中的AudioPlayer.exe文件运行程序。这时会出现一个对话框提示丢失文件“libgcc_s_dw2-1.dll”发现目录“D:\Qt\Qt5.9.0\5.9\mingw53_32\bin”下有这个文件将此文件复制到“F:\Setup\minGW”目录下。再次运行AudioPlayer.exe程序还会出现丢失文件对话框将这些文件复制到“F:\Setup\minGW”下依赖的库文件复制完全后AudioPlayer.exe才可以正常运行。所以windeployqt并不能保证一次将所有依赖文件复制完全需要测试运行应用程序以检验依赖库的完整性。注意运行应用程序测试部署文件完整性时必须删除系统环境变量里Qt的bin路径否则系统从Qt的bin路径里总是可以找到依赖的库文件就达不到测试的目的了。查看一个可执行文件的依赖文件也可以使用工具软件Dependency Walker它能可视化地查看可执行文件的依赖项。可以从其官网下载这个软件的最新版本。即便在开发应用程序的计算机上测试发布程序没有问题也应该将发布文件目录复制到一个没有安装Qt的计算机上测试应用程序能否正常运行。3MSVC编译的应用程序发布实例将15.2节的音频播放器实例程序samp15_1用MSVC2015 32bit编译器在release模式下编译生成可执行文件samp15_1.exe。将此可执行文件复制到一个目录下如“F:\Setup\msvc32”并将samp15_1.exe更名为AudioPlayer.exe。然后就需要使用MSVC2015 32bit版本的Qt的bin目录下的windeployqt程序来为程序AudioPlayer.exe生成发布文件其使用方法与MinGW版本的类似。生成的发布目录下的文件和目录与原书的图16-10所示内容一样只不过来源版本不同。测试时双击AudioPlayer.exe文件发现它可以直接运行未提示丢失文件。同样也应该将发布目录复制到一个没有安装Qt和MSVC2015的计算机上测试看其能否正常运行。4安装程序制作复制了应用程序所需的依赖文件并测试没有问题后就可以制作安装文件了。有很多制作安装文件的软件用户自行选择并下载一个即可制作自己的安装文件这里不再详述。