欢迎光临
我们一直在努力

【Qt】《Qt 5.9 C++开发指南》笔记-Chapter16-应用程序设计辅助功能

第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 str1=tr("左右", "大约的意思");
QString str2=tr("左右", "掌握、控制的意思");

使用tr()函数,需要注意以下一些事项。

  • 尽量使用常量字符串,不要使用字符串变量。在tr()函数中应直接传递字符串常量,而不是用变量传递字符串,如下面的代码使用了字符串变量,使用lupdate工具提取项目中的字符串时,将不能提取“不能删除记录”这个字符串。

char *errorStr="不能删除记录";
QString str2=tr(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 i=0; i<4; 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 *trans=NULL;
QString readSetting();

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
trans=new QTranslator;
QString curLang=readSetting(); //读取语言设置
if (curLang=="EN")
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 organization="WWB-Qt";
QString appName="samp16_1";
QSettings settings(organization,appName);
QString Language=settings.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;
trans=new 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;
trans=new 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 Sheets,CSS)启发而来的,只是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.选择器(selector)

Qt样式表支持CSS2中定义的所有选择器,表16-1显示的是一些常用的选择器。

表16-1 Qt样式表中的选择器类型

选择器例子用途
通用选择器 * 所有组件
类型选择器 QPushButton 所有QPushButton类及其子类的组件
属性选择器 QPushButton[flat=”false”] 所有flat 属性为false的QPushButton类及其子类的组件。如果样式表应用后组件的属性再发生变化,需要重新应用样式表才能刷新显示效果
非子类选择器 .QPushButton 所有QPushButton类的组件,但是不包括QPushButton的子类
ID选择器 QPushButton#btnOK ObjectName为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[readOnly="true"], QCheckBox[checked="true"]
{ 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。

那么,可以应用下面的样式将这种必填字段的背景颜色设置为亮绿色。

*[required="true"] {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样式表中常见的子控件列表

子控件名称说明
::branch QTreeView的分支指示器
::chunk QProgressBar 的进度显示块
::close-button QDockWidget或QTabBar页面的关闭按钮
::down-arrow QComboBox,QHeaderView (排序指示器),QScrollBar 或QSpinBox的下拉箭头
::down-button QScrollBar或 QSpinBox的向下按钮
::drop-down QComboBox的下拉按钮
::float-button QDockWidget的浮动按钮
::groove QSlider的凹槽
::indicator QAbstractItemView,QCheckBox,QRadioButton,可勾选的QMenu 菜单项,或可勾选的QGroupBox的指示器
::handle QScrollBar,QSplitter或QSlider的滑块
::icon QAbstractItemView或 QMenu的图标
::item QAbstractItemView,QMenuBar,QMenu,或QStatusBar的一个项
::left-arrow QScrollBar的向左箭头
::menu-arrow 具有下拉菜单的QToolButton的下拉箭头
::menu-button QToolButton的菜单按钮
::menu-indicator QPushButton的菜单指示器
::right-arrow QMenu 或QScrollBar的右侧箭头
::pane QTabWidget的面板
::scroller QMenu 或 QTabBar的卷轴
::section QHeaderView的分段
::separator QMenu 或QMainWindow的分隔器
::tab QTabBar 或QToolBox的分页
::tab-bar QTabWidget的分页条。这个子控件只用于控制QTabBar在QTabWidget中的位置,定义分页的样式使用::tab子控件
::text QAbstractItemView的文字
::title QGroupBox或QDockWidget的标题
::up-arrow QHeaderView (排序指示器),QScrollBar或 QSpinBox的向上箭头
::up-button QSpinBox的向上按钮
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-item QTreeView::branch与一个条目相邻时,此状态为真
:alternate 当QAbstractItemView的alternatingRowColors()属性为true时,绘制交替的行时此状态为真
:bottom 组件处于底部,如QTabBar的表头位于底部
:checked 组件被勾选,如QAbstractButton的checked属性为true
:closable 组件可以被关闭,例如当QDockWidget 的DockWidgetClosable属性为true时
:closed 条目处于关闭状态,例如QTreeView的一个没有展开的条目
:default 条目是缺省的,如一个缺省的QPushButton按钮,或QMenu中一个缺省的action
:disabled 条目被禁用
:editable QComboBox是可编辑的
: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)来表示,模型由四个同心矩形表示。

(1)content是显示内容矩形区域,如QLineEdit用于显示文字的区域,min-width、max-width、min-height和max-height属性定义最大/最小宽度或高度,就是定义这个矩形区,例如:

QLineEdit{
min-width:50px;
max-height:40px;
}

这定义QLineEdit最小宽度为50px,最大高度是40px,其中px是单位,表示像素。

(2)padding是包围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;
}

(3)border是包围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);}

(4)margin是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 *style=QStyleFactory::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应用程序的发布。

1.Windows发布工具

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++的运行库。

2.MinGW编译的应用程序发布实例

将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的计算机上测试应用程序能否正常运行。

3.MSVC编译的应用程序发布实例

将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.安装程序制作

复制了应用程序所需的依赖文件,并测试没有问题后就可以制作安装文件了。有很多制作安装文件的软件,用户自行选择并下载一个即可制作自己的安装文件,这里不再详述。

赞(0)
未经允许不得转载:171主机测评 » 【Qt】《Qt 5.9 C++开发指南》笔记-Chapter16-应用程序设计辅助功能
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址