第8章 界面外观
Qt提供了强大的界面外观定制功能,主要包括样式(QStyle)和样式表两种方式。QStyle类封装了GUI外观样式,可通过QStyleFactory创建不同平台风格的样式,如Windows、Fusion等。开发者可以通过setStyle()方法为整个应用或单个部件设置样式,还支持通过命令行参数指定样式。 调色板(QPalette)允许自定义部件的颜色组,支持激活、非激活和失效三种状态。开发者可以修
一个完善的应用程序不仅应该有实用的功能,还要有一个漂亮的外观,这样才能使应用程序更加友善、更加吸引用户。作为一个跨平台的UI开发框架,Qt提供了强大而灵活的界面外观设计机制。
8.1 Qt Widgets外观样式
8.1.1 QStyle
- QStyle类是一个抽象基类,封装了一个GUI的外观样式,Qt的内建部件使用它来执行几乎所有的绘制工作,以确保这些部件看起来可以像各个平台上的本地部件一样。Qt包含一组QStyle的子类,可以模拟Qt支持的不同平台的样式,这些样式被内置于了Qt GUI模块中。另外,也可以通过插件来提供样式。
- QStyleFactory类可以创建一个QStyle对象,首先通过keys()函数获取可用的样式,然后使用create()函数创建一个QStyle对象。一般Windows样式和Fusion样式是默认可用的,而有些样式只在特定的平台上才有效,例如WindowsXP样式、WindowsVista样式、GTK样式和Macintosh样式等。
8.1.2 使用不同样式预览程序
在设计模式,选择“工具→Form Editor→Preview in”菜单项,这里列出了现在可用的几种风格,选择“Fusion”样式进行预览:
- 如果想使用不同的样式来运行程序,那么只需要调用QApplication的setStyle()函数指定要使用的样式即可。
- 在main()函数的
“QApplication a(argc, argv);”代码后添加如下代码:
a.setStyle(QStyleFactory::create("fusion"));
- 如果不想整个应用程序都使用相同的样式,可以调用部件的setStyle()函数来指定该部件的样式:
ui->progressBar->setStyle(QStyleFactory::create("windows"));
- 使用命令行运行程序时通过添加参数来指定,比如要使用Fusion样式,则可以使用“-style fusion”参数。
8.1.3 调色板
- 调色板QPalette类包含了部件各种状态的颜色组。一个调色板包含三种状态:激活(Active)、失效(Disabled)和非激活(Inactive)。Qt中的所有部件都包含一个调色板,并且使用各自的调色板来绘制它们自身,这样可以使用户界面更容易配置,也更容易保持一致。调色板中的颜色组包括:
1)激活颜色组QPalette::Active,用于获得键盘焦点的窗口;
2)非激活颜色组QPalette::Inactive,用于其他的窗口;
3)失效颜色组QPalette::Disabled,用于因为一些原因而不可用的部件(不是窗口)。 - 要改变一个应用程序的调色板,可以先使用QApplication::palette()函数来获取其调色板,然后对其进行更改,最后再使用QApplication::setPalette()函数来使用该调色板。更改了应用程序的调色板,会影响到该程序的所有窗口部件。如果要改变一个部件的调色板,可以调用该部件的palette()和setPalette()函数,这样只会影响该部件及其子部件。
- 示例:

// 通过画刷来使用图片
QBrush brush(QPixmap("../mystyle/bg.png"));
// 获取主窗口的调色板
QPalette palette = this->palette();
// 为主窗口背景设置图片
palette.setBrush(QPalette::Window, brush);
// 主窗口使用设置好的调色板
this->setPalette(palette);
// 获取pushButton的调色板
QPalette palette1 = ui->pushButton->palette();
// 设置按钮文本颜色为红色
palette1.setColor(QPalette::ButtonText, Qt::red);
// 设置按钮背景色为绿色
palette1.setColor(QPalette::Button, Qt::green);
// pushButton使用修改后的调色板
ui->pushButton->setPalette(palette1);
// 设置spinBox不可用
ui->spinBox->setDisabled(true);
QPalette palette2 = ui->spinBox->palette();
// 设置spinBox不可用时的背景颜色为蓝色
palette2.setColor(QPalette::Disabled, QPalette::Base, Qt::blue);
ui->spinBox->setPalette(palette2);
8.2 Qt样式表简介
Qt样式表是一个可以自定义部件外观的十分强大的机制,它的概念、术语和语法都受到了HTML的层叠样式表(Cascading Style Sheets,CSS)的启发,不过与CSS不同的是,Qt样式表应用于部件的世界。
8.2.1 使用代码设置样式表
- 样式表可以使用QApplication::setStyleSheet()函数将其设置到整个应用程序上,也可以使用QWidget::setStyleSheet()函数将其设置到一个指定的部件(还有它的子部件)上。如果在不同的级别都设置了样式表,那么Qt会使用所有有效的样式表,这被称为样式表的层叠。
1)调用指定部件的setStyleSheet()函数对这个部件应用该样式表:
// 设置pushButton的背景为黄色
ui->pushButton->setStyleSheet("background:yellow");
// 设置horizontalSlider的背景为蓝色
ui->horizontalSlider->setStyleSheet("background:blue");

2)对所有相同部件都使用相同的样式表,那么可以在它们的父部件上设置样式表。例如两个部件都在MainWindow上,可以为MainWindow设置样式表,以后向主窗口上添加的所有QPushButton部件和QSlider部件的背景色都会改为这里指定的颜色:
setStyleSheet("QPushButton{background:yellow}QSlider{background:blue}");
8.2.2 在设计模式中设置样式表
- 进入设计模式。在界面上右击,在弹出的级联菜单中选择“改变样式表”,这时会出现编辑样式表对话框,在其中输入如下代码:
`QPushButton{
}`
- 注意光标留在第一个大括号后。然后单击上面“添加颜色”选项后面的下拉箭头,在弹出的列表中选择background-color一项。这时会弹出选择颜色对话框,可以随便选择一个颜色,然后单击“确定”按钮,则自动添加代码:
QPushButton{
background-color: rgb(85, 170, 127);
}

根据选择的颜色不同,rgb()中的参数的数值也会不同。可以看到,在这里设置样式表不仅很便捷而且很直观,不仅可以设置颜色,还可以使用图片,使用渐变颜色,或者更改字体。
- 这里是在MainWindow界面上设置了样式表,当然,也可以按照这种方法在指定的部件上添加样式表。

8.2.3 Qt样式表语法
Qt样式表的术语和语法规则与HTML CSS基本相同,下面从样式规则、选择器类型、子控件(Sub-Controls)、伪状态(Pseudo-States)、冲突解决、层叠、继承、设置QObject属性等方面来进行讲解。
8.2.3.1 样式规则
- 样式表包含了一系列的样式规则,每个样式规则由选择器(selector)和声明(declaration)组成。选择器指定了受该规则影响的部件;声明指定了这个部件上要设置的属性。例如:
QPushButton{color:red}
在这个样式规则中,QPushButton是选择器,{color:red}是声明,其中color是属性,red是值。这个规则指定了QPushButton和它的子类应该使用红色作为前景色。
- Qt样式表中一般不区分大小写,例如color、Color、COLOR和COloR表示相同的属性。只有类名、对象名和Qt属性名是区分大小写的。
- 一些选择器可以指定相同的声明,使用逗号隔开,例如:
QPushButton,QLineEdit,QComboBox{color:red}
- 样式规则的声明部分是一些“属性:值”对组成的列表,它们包含在大括号中,使用分号隔开。例如:
QPushButton{color:red;background-color:white}
- 可以在Qt Style Sheets Reference关键字对应的文档中查看Qt样式表所支持的所有部件及其属性。
8.2.3.2 选择器类型
Qt样式表支持在CSS2中定义的所有选择器,下表列出了最常用的选择器类型:
8.2.3.3 子控件(Sub-Controls)
- 对一些复杂的部件修改样式,可能需要访问它们的子控件,例如QComboBox的下拉按钮,还有QSpinBox的向上和向下箭头等。选择器可以包含子控件来对部件的特定子控件应用规则,例如:
QComboBox::drop-down{image:url(dropdown.png)}
这样的规则可以改变所有的QComboBox部件的下拉按钮的样式。
- 在Qt Style Sheets Reference关键字对应帮助文档的List of Sub-Controls一项中列出了所有可用的子控件。

8.2.3.4 伪状态(Pseudo-States)
- 选择器可以包含伪状态来限制规则只能应用在部件的指定状态上。伪状态出现在选择器之后,用冒号隔开,例如:
QPushButton:hover{color:white}
这个规则表明当鼠标悬停在一个QPushButton部件上时才被应用。
- 伪状态可以使用感叹号来表示否定,例如要当鼠标没有悬停在一个QRadioButton上时才应用规则:
QRadioButton:!hover{color:red}
- 伪状态还可以多个连用,达到逻辑与效果,例如当鼠标悬停在一个被选中的QCheckBox部件上时才应用规则:
QCheckBox:hover:checked{color:white}
- 也可以使用逗号来表示逻辑或操作,例如:
QCheckBox:hover,QCheckBox:checked{color:white}
- 伪状态也可以和子控件联合使用,例如:
QComboBox::drop-down:hover { image: url(dropdown_bright.png) }
- 在Qt Style Sheets Reference关键字对应的帮助文档的List of Pseudo-States一项中列出了Qt支持的所有的伪状态。
8.2.3.5 冲突解决
- 当几个样式规则对相同的属性指定了不同的值时就会产生冲突。例如:
QPushButton#okButton { color: gray }
QPushButton { color: red }
这样,okButton的color属性便产生了冲突。解决这个冲突的原则是:特殊的选择器优先。因为QPushButton#okButton一般代表一个单一的对象,而不是一个类所有的实例,所以它比QPushButton更特殊,那么这时便会使用第一个规则,okButton的文本颜色为灰色。
- 相似的,有伪状态比没有伪状态优先。如果两个选择器的特殊性相同,则后面出现的比前面的优先。Qt样式表使用CSS2规范来确定规则的特殊性。
8.2.3.6 层叠
样式表可以被设置在QApplication上、父部件上或子部件上。部件有效的样式表是通过部件祖先的样式表和QApplication上的样式表合并得到的。当发生冲突时,部件自己的样式表优先于任何继承的样式表,同样,父部件的样式表优先于祖先的样式表。
8.2.3.7 继承
- 当使用Qt样式表时,部件并不会自动从父部件继承字体和颜色设置。例如,一个QPushButton包含在一个QGroupBox中,这里对QGroupBox设置样式表:
qApp->setStyleSheet("QGroupBox { color: red; } ");
- 但没有对QPushButton设置样式表。这时,QPushButton会使用系统颜色,而不会继承QGroupBox的颜色。如果想要QGroupBox的颜色设置到其子部件上,可以这样设置样式表:
qApp->setStyleSheet("QGroupBox, QGroupBox * { color: red; }");
8.2.3.8 继承设置QObject属性
任何可设计的Q_PROPERTY都可以使用“qproperty-属性名称”语法来设置样式表。例如:
MyLabel { qproperty-pixmap: url(pixmap.png); }
QPushButton { qproperty-iconSize: 20px 20px; }
8.2.4 自定义部件外观
8.2.4.1 盒子模型(The Box Model)
- 当使用样式表时,每一个部件都被看做是拥有4个同心矩形的盒子。这4个矩形分别是:内容(content)、填衬(padding)、边框(border)、边距(margin)。边距、边框宽度和填衬等属性的默认值都是0,这样4个矩形恰好重合。

- 可以使用background-image属性来为部件指定一个背景。默认的,background-image只在边框以内的区域进行绘制,这个可以使用background-clip属性来进行更改。还可以使用background-repeat和background-origin来控制背景图片的重复方式以及原点。
- 如果想要背景随着部件的大小变化,那就必须使用border-image。如果同时指定了background-image和border-image,那么border-image会绘制在background-image之上。此外,image属性可以用来在border-image之上绘制一个图片。
8.2.4.2 样式示例
/****************主界面背景*******************/
QMainWindow {
/*背景图片*/
background-image: url(:/images/bg.png);
}
/****************按钮部件*******************/
QPushButton {
/*背景色*/
background-color: rgba(100, 225, 100, 30);
/*边框样式*/
border-style: outset;
/*边框宽度为4像素*/
border-width: 4px;
/*边框圆角半径*/
border-radius: 10px;
/*边框颜色*/
border-color: rgba(255, 225, 255, 30);
/*字体*/
font: bold 14px;
/*字体颜色*/
color:rgba(0, 0, 0, 100);
/*填衬*/
padding: 6px;
}
/*鼠标悬停在按钮上时*/
QPushButton:hover {
background-color:rgba(100,255,100, 100);
border-color: rgba(255, 225, 255, 200);
color:rgba(0, 0, 0, 200);
}
/*按钮被按下时*/
QPushButton:pressed {
background-color:rgba(100,255,100, 200);
border-color: rgba(255, 225, 255, 30);
border-style: inset;
color:rgba(0, 0, 0, 100);
}
/****************滑块部件*******************/
/*水平滑块的手柄*/
QSlider::handle:horizontal {
image: url(:/image/sliderHandle.png);
}
/*水平滑块手柄以前的部分*/
QSlider::sub-page:horizontal {
/*边框图片*/
border-image: url(:/image/slider.png);
}

8.2.4.3 实现换肤功能
- Qt样式表可以存放在一个以.qss为后缀的文件中,这样就可以在程序中通过调用不同的qss文件来实现不同的界面外观。
- 在程序中添加新文件,模板选择概要分类中的Empty File,名称为my.qss。建立完成后,将前面示例中的内容全部剪切到这个文件中。然后创建资源文件,将my.qss添加进去。可以通过如下代码来使用样式表文件:
qssFile = new QFile(":/qss/my.qss", this);
// 只读方式打开该文件
qssFile->open(QFile::ReadOnly);
// 读取文件全部内容
QString styleSheet = QString(qssFile->readAll());
// 为QApplication设置样式表
qApp->setStyleSheet(styleSheet);
qssFile->close();
这里读取了Qt样式表文件中的内容,然后为应用程序设置了样式表。
- 在换肤按钮中这样设置:
void MainWindow::on_pushButton_clicked()
{
if(qssFile->fileName() == ":/qss/my.qss")
qssFile->setFileName(":/qss/my1.qss");
else qssFile->setFileName(":/qss/my.qss");
qssFile->open(QFile::ReadOnly);
QString styleSheet = QString(qssFile->readAll());
qApp->setStyleSheet(styleSheet);
qssFile->close();
}
运行程序,按下按钮后便会更改界面的外观,这样就实现了换肤功能。
8.2.5 特殊效果窗体
8.2.5.1 不规则窗体
Qt中提供了部件遮罩(mask)来实现不规则窗体。例如:
1)先在构造函数中添加如下代码:
QPixmap pix;
pix.load(":/image/yafeilinux.png"); // 加载图片
resize(pix.size()); // 设置窗口大小为图片大小
setMask(pix.mask()); // 为窗口设置遮罩
2)然后在paintEvent()函数中将图片绘制在窗口上:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// 从窗口左上角开始绘制图片
painter.drawPixmap(0,0,QPixmap(":/image/yafeilinux.png"));
}

8.2.5.2 透明窗体
- 方式一:使用setWindowOpacity()函数,它的参数取值范围为0.0-1.0,当取值为0.0时完全透明,取值为1.0时完全不透明。
setWindowOpacity(0.3);

- 方式二:使用setAttribute()函数。例如:
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
这里使用了setAttribute()函数指定窗口的Qt::WA_TranslucentBackground属性,它可以使窗体背景透明,而其中的部件不受影响。不过在Windows下,还要使用setWindowFlags()函数指定Qt::FramelessWindowHint标志,这样才能实现透明效果。
- 方式三:在方式二的基础上修改重绘事件。例如:
// 进行paintEvent()函数的定义:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(),QColor(255,255,255,100));
}
这里先使用rect()函数获取窗口的内部矩形,它不包含任何边框。然后使用半透明的白色对这个矩形进行填充。
8.3 Qt Quick控件样式
Qt Quick Controls中也为控件提供了多种样式,主要包括:
1)Basic Style:这是一种简单而轻便的样式,为Qt Quick Controls提供了最好的性能,将动画和过渡的数量保持在最小。如果其他样式未实现某个控件,则默认选择该控件的Basic Style来实现。
2)Fusion Style:这是一种与平台无关的样式。它实现了与Qt Widgets的Fusion样式相同的设计语言。
3)Imagine Style:该样式基于图片资源,附带了一组默认的图片。通过预定义的命名约定提供一个包含图片的目录,可以轻松更改使用的图片。
4)Material Style:该样式基于Google Material Design Guidelines,但它并不是原生Android样式,而是一种100%跨平台的Qt Quick Controls样式。
5)Universal Style:这是一种基于Microsoft Universal Design Guidelines的设备无关的样式,是为了能在手机、平板电脑和个人电脑等所有设备上都具有良好效果而设计的。
6)除了这里列举的几种样式,还有一些特定系统的样式,例如macOS系统上的macOS Style、iOS系统上的iOS Style、Windows系统上的Windows Style等。如果没有指定特定的样式,那么在不同系统会使用不同的默认样式,其他操作系统会默认使用Basic Style。
8.3.1 使用控件样式
在Qt Quick程序中选择样式有两种情况,一种是在编译时选择,另一种是在运行时选择。
8.3.1.1 编译时选择样式
在编译时选择样式,只需要使用import导入要使用的样式即可,例如:
import QtQuick.Controls.Material
ApplicationWindow { ...}
使用这种方式的好处是不再需要导入QtQuick.Controls模块,所以部署程序时也不需要包含该模块。另外,如果应用程序是静态构建的,则必须使用这种方式导入。
8.3.1.2 运行时选择样式
在运行时选择样式,在程序中必须导入QtQuick.Controls模块,然后通过如下几种方式来选择样式:
1)使用QQuickStyle::setStyle()。
2)使用-style命令行参数。
3)使用QT_QUICK_CONTROLS_STYLE环境变量。
4)使用qtquickcontrols2.conf配置文件。
这些方式的优先级从高到低,也就是说,使用QQuickStyle设置样式总是优先于使用命令行参数。在运行时选择样式的好处是,单个应用程序二进制文件可以支持多种样式。
8.3.1.3 QQuickStyle的使用
- 注意,必须在加载导入了Qt Quick Controls模块的QML文件之前配置样式,也就是说setStyle()必须在QQmlApplicationEngine::load()之前进行调用,当注册QML类型后,将无法再更改样式。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Fusion"); // 设置样式
QQmlApplicationEngine engine;
engine.load(QUrl::fromLocalFile("../mystyle/main.qml"));
return app.exec();
}
- Qt Quick Controls支持一个特殊的配置文件qtquickcontrols2.conf,它内置在应用程序的资源中。配置文件可以指定首选样式和某些特定于样式的属性。
1)首先按下Ctrl+N新建文件,模板选择概要分类中的Empty File,文件名称设置为qtquickcontrols2.conf,完成后在其中添加如下代码:
[Controls]
Style=Material
[Material]
Theme=Light
Accent=Teal
Primary=BlueGrey
这里指定首选样式为Material样式,该样式的主题为浅色,强调色和基色分别为青色和蓝灰色。qtquickcontrols2.conf文件必须添加到资源文件中,且前缀为“/”才能自动启用,所以下面需要添加资源文件。
2)再次按下Ctrl+N新建文件,模板选择Qt分类中的Qt Resource File,文件名设置为file.qrc。添加完成后在资源文件编辑界面,先添加前缀“/”,然后单击“添加文件”按钮将qtquickcontrols2.conf文件添加进来,完成后按下Ctrl+S保存更改。注意在main.cpp中将前面添加的setStyle(“Fusion”)那行代码删除或者注释掉。
8.3.2 自定义控件
- 虽然在Qt Quick Controls中提供了多个样式可供使用,但是有时还是想实现自定义的外观。如果只是自定义一个特定的控件对象,那么可以直接在其定义处使用代码设置外观,例如:
import QtQuick
import QtQuick.Controls
Button {
id: control
text: qsTr("Button")
contentItem: Text {
text: control.text
font: control.font
... ...
}
background: Rectangle {
implicitWidth: 100
implicitHeight: 40
opacity: enabled ? 1 : 0.3
... ...
}
}
Button控件由两个视觉项目组成:background和contentItem,所以可以直接自定义这两个项目,从而产生想要的效果。
- 另外,如果想在某个现成样式的基础上进行修改也是可以的,例如:
import QtQuick
import QtQuick.Controls.Basic as Basic
Basic.SpinBox {
background: Rectangle { color: "lightblue" }
}
8.4 国际化
- 国际化的英文表述为Internationalization,通常简写为I18N(首尾字母加中间的字符数),一个应用程序的国际化就是使该应用程序可以让其他国家的用户使用的过程。
- Qt支持现在使用的大多数语言,所有的输入部件和文本绘制方式对Qt所支持的所有语言都提供了内置的支持。
- Qt内置的字体引擎可以在同一时间正确而且精细地绘制不同的文本,这些文本可以包含来自众多不同书写系统的字符。
8.4.1 使用Qt Linguist翻译应用程序
8.4.1.1 Qt Linguist工具
Qt对应用程序进行本地语言翻译提供了很好的支持,可以使用Qt Linguist工具完成应用程序的翻译工作。在编写Qt Widgets代码时对需要在界面上显示的字符串一般会调用tr()函数,然后通过下面3步完成应用程序的翻译工作:
1)运行lupdate工具,从C++源代码中提取要翻译的文本,这时会生成一个.ts文件,这个文件是XML格式的;
2)在Qt Linguist中打开.ts文件,并完成翻译工作;
3)运行lrelease工具从.ts文件中获得.qm文件,它是一个二进制文件。这里的.ts文件是供翻译人员使用的,而在程序运行时只需要使用.qm文件,这两个文件都是与平台无关的。
8.4.1.2 翻译应用程序过程
- 第一步,编写源码。新建Qt Widgets应用,项目名称为myI18N,类名为MainWindow,基类保持QMainWindow不变。建立完项目后,单击mainwindow.ui文件进入设计模式,先添加一个“&File”菜单,再为其添加一个“&New”子菜单并设置快捷键为Ctrl+N,然后往界面上拖入一个Push Button。最后按下Ctrl+S保存该文件。下面再使用代码添加三个标签,打开mainwindow.cpp文件,在构造函数中添加代码:
QLabel *label = new QLabel(this);
label->setText(tr("hello Qt!"));
label->move(100, 50);
QLabel *label2 = new QLabel(this);
label2->setText(tr("password", "mainwindow"));
label2->move(100, 80);
QLabel *label3 = new QLabel(this);
int id = 123;
QString name = "yafei";
label3->setText(tr("ID is %1,Name is %2").arg(id).arg(name));
label3->resize(150, 12);
label3->move(100, 120);

因为这三个标签中的内容都是用户可见的,所以需要调用tr()函数。tr()函数一共有3个参数,它的原型如下:
QString QObject::tr ( const char * sourceText, const char * disambiguation = nullptr, int n = -1 ) [static]
1)第一个参数sourceText就是要显示的字符串,tr()函数会返回sourceText的译文。
2)第二个参数disambiguation是消除歧义字符串,比如这里的password,如果一个程序中需要输入多个不同的密码,那么在没有上下文的情况下,就很难确定这个password到底指哪个密码。这个参数一般使用类名或者部件名,比如这里使用了mainwindow,就说明这个password是在mainwindow上的。
3)第三个参数n表明是否使用了复数,因为英文单词中复数一般要在单词末尾加“s”,比如“1 message”,复数时为“2 messages”。遇到这种情况,就可以使用这个参数,它可以根据数值来判断是否需要添加“s”,例如:
int n = messages.count();
showMessage(tr("%n message(s) saved", "", n));
- 第二步,更改项目文件。在项目文件中指定生成的.ts文件。每一种翻译语言对应一个.ts文件,打开myI18N.pro文件,在最后面添加如下一行代码:
TRANSLATIONS = myI18N_zh_CN.ts
这表明后面生成的.ts文件的文件名为myI18N_zh_CN.ts。这里.ts的名称可以随意编写,不过一般是以区域代码来结尾,这样可以更好的区分,如这里使用了zh_CN来表示简体中文。最后按下Ctrl+S保存该文件(这个很重要,不然无法进行下面的操作)。
- 第三步,使用lupdate工具生成.ts文件。当要进行翻译工作时,先要使用lupdate工具来提取源代码中的翻译文本,生成.ts文件。单击“工具→外部→Qt语言家→Update Translations(lupdate)”菜单项(在操作之前确保已经保存了所有文件)。从概要信息输出栏中可以看到,更新了“myI18N_zh_CN.ts”文件,发现了8个源文本,其中有8条新的和0条已经存在的。这表明可以对程序代码进行更改,然后多次运行lupdate,而只需要翻译新添加的内容。可以在项目目录中使用写字板打开这个.ts文件,可以看到它是XML格式的,其中记录了字符串的位置和是否已经被翻译等信息。

- 第四步,使用Qt Linguist完成翻译。这一步一般是翻译人员来做的,就是在Qt Linguist中打开.ts文件,然后对字符串逐个进行翻译。在系统的开始菜单中启动Linguist,然后点击界面左上角的“打开”图标,在弹出的文件对话框中进入项目目录,打开“myI18N_zh_CN”文件。

①菜单栏和工具栏。菜单栏中列出了Qt Linguist的所有功能选项,而工具栏中列出了常用的一些功能,其中后面11个图标的功能为:
1)在字符串列表中移动到前一个条目;
2)在字符串列表中移动到下一个条目;
3)在字符串列表中移动到前一个没有完成翻译的条目;
4)在字符串列表中移动到下一个没有完成翻译的条目;
5)标记当前条目为完成翻译状态;
6)标记当前条目为完成翻译状态,然后移动到下一个没有完成翻译的条目;
7)打开或关闭加速键(accelerator)验证(validation):打开加速键验证可以验证加速键是否被翻译,例如字符串中包含“&”符号,但是翻译中没有包含“&”符号,则验证失败;
8)打开或关闭空格围绕验证:如果源字符串的开头或者结尾没有空格,当打开空格围绕验证后,如果翻译中在开头或者结尾包含空格就会给出警告,反之亦然;
9)打开或关闭短语结束标点符号验证:打开短语标点符号验证可以验证翻译中是否使用了和字符串中相同的标点来结尾;
10)打开或关闭短语书(phrase book)验证:打开短语书验证可以验证翻译是否和短语书中的翻译相同。在翻译相似的程序时,若希望将常用的翻译记录下来,以便以后使用,就可以使用短语书。可以通过“短语→新建短语书”来创建一个新的短语书,然后翻译字符串时使用Ctrl+T将这个字符串及其翻译放入短语书中;
11)打开或关闭占位符(place marker)验证:打开占位符验证可以验证翻译中是否使用了和字符串中相同的占位符,例如%1,%2等。
②上下文(Context)窗口。这里是一个上下文列表,罗列了要翻译的字符串所在位置的上下文。其中的“上下文”列使用字母表顺序罗列了上下文的名字,它一般是QObject子类的名字;而“项目”列显示的是字符串数目,例如0/8表明有8个要翻译的字符串,已经翻译了0个。在每个上下文的最左端用图标表明了翻译的状态,它们的含义是:
1)绿色对号:上下文中的所有字符串都已经被翻译,而且所有的翻译都通过了验证测试(validation test);
2)黄色对号:上下文中的所有字符串或者都已经被翻译,或者都已经标记为已翻译,但是至少有一个翻译验证测试失败;
3)黄色问号:上下文中至少有一个字符串没有被翻译或者没有被标记为已翻译;
4)灰色对号:上下文中没有再出现要翻译的字符串,这通常意味着这个上下文已经不在应用程序中了。
③字符串(String)窗口。这里罗列了在当前上下文中找到的所有要翻译的字符串。这里选择一个字符串,可以使这个字符串在翻译区域进行翻译。在字符串左边使用图标表明了字符串的状态,它们的含义是:
1)绿色对号:源字符串已经翻译(可能为空),或者用户已经接受翻译,而且翻译通过了所有验证测试;
2)黄色对号:用户已经接受了翻译,但是翻译没有通过所有的验证测试;
3)黄色问号:字符串已经拥有了一个通过了所有验证测试的非空翻译,但是用户还没有接受该翻译;
4)绿色问号:字符串还没有翻译;
5)红色叹号:字符串拥有一个翻译,但是这个翻译没有通过所有的验证测试;
6)灰色对号:字符串已经过时,它已经不在该上下文中。
④源文和窗体(Sources and Forms)窗口。如果包含有要翻译字符串的源文件在Qt Linguist中可用,那么这个窗口会显示当前字符串在源文件中的上下文。
⑤翻译区域(The Translation Area)。在字符串列表中选择的字符串会出现在翻译区域的最顶端的“源文”下面;如果使用tr()函数时设置了第二个参数消除歧义注释,那么这里还会在“开发人员注释”下出现该注释;而在“翻译为”中可以输入翻译文本,如果文本中包含空格,会使用“.”显示;最后面的“译文注释”中可以填写翻译注释文本。
⑥短语和猜测(Phrases and Guesses)窗口。如果字符串列表中的当前字符串出现在了已经加载的短语书中,那么当前字符串和它在短语书中的翻译会被罗列在这个窗口。在这里可以双击翻译文本,这样翻译文本就会复制到翻译区域。
⑦警告信息(Warnings)窗口。如果输入的当前字符串的翻译没有通过开启的验证测试,那么在这里会显示失败信息。
下面来翻译程序。在翻译区域可以看到现在已经是要翻译成简体中文(中国),这是因为.ts文件名中包含了中文的区域代码。如果这里没有正确显示要翻译成的语言,那么可以使用“编辑→翻译文件设置”菜单项来更改。下面首先对MainWindow进行翻译,这里在翻译为简体中文(中国)处翻译为“应用程序主窗口”,然后按下Ctrl+Return(即回车键)完成翻译并开始翻译第二个字符串。按照这种方法完成所有字符串的翻译工作:
- 第五步,使用lrelease生成.qm文件。可以在Qt Linguist中使用“文件→发布”或“文件→发布为”这两个菜单项来生成当前已打开的.ts文件对应的.qm文件,文件默认会生成在.ts所在目录下。也可以通过Qt Creator的“工具→外部→Qt语言家→Release Translations(lrelease)”菜单项来完成。
- 第六步,使用.qm文件。下面在项目中添加代码使用.qm文件来更改界面的语言。进入main.cpp文件,添加头文件#include ,然后在QApplication a(argc, argv);代码下添加如下代码:
QTranslator translator;
if(translator.load("../myI18N/myI18N_zh_CN.qm"))
a.installTranslator(&translator);

这里先加载了.qm文件(使用了相对路径),然后为QApplication对象安装了翻译。注意,这几行代码一定要放到创建部件的代码之前,比如这里放到了“MainWindow w;”一行代码之前,这样才能对该部件进行翻译。
8.4.2 使用Qt Creator自动生成翻译文件
- 现在使用Qt Creator创建应用程序并完成翻译是非常简单的,很多步骤可以省略,如果有专业的翻译人员,那么对编程人员而言,只需要注意代码的一些事项即可。
- 通过Qt Widgets Application模板新建项目时,例如项目名称为myLinguist,在Translation File翻译文件页面选择语言为Chinese(China),这时翻译文件Translation file会自动生成为myLinguist_zh_CN。项目创建完成后,可以发现项目中多了一个myLinguist_zh_CN.ts文件,而在项目文件myLinguist.pro中多了如下代码:
TRANSLATIONS += \
myLinguist_zh_CN.ts
CONFIG += lrelease
CONFIG += embed_translations
这里添加的配置信息可以在编译时自动使用lrelease生成.qm文件,并将.qm文件通过Qt资源系统内嵌到程序中。
另外,在main.cpp文件中的main()函数中多了如下代码:
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages) {
const QString baseName = "myLinguist_" + QLocale(locale).name();
if (translator.load(":/i18n/" + baseName)) {
a.installTranslator(&translator);
break;
}
}
可以使用QLocale::system().name()来获取本地的语言环境,它会返回QString类型的“语言_国家”格式的字符串,其中的语言用两个小写字母表示,符合ISO 639编码;国家使用两个大写字母表示,符合ISO 3166国家编码。例如中国简体中文的表示为“zh_CN”。可以使用这个返回值来调用不同的文件,使应用程序自动使用相应的语言。这里就是使用了这种方法自动加载.qm文件,而需要的.qm文件存放在默认的资源文件中。
- 下面双击mainwindow.ui文件进入设计模式,向界面上拖入一个Push Button部件,然后按下Ctrl + S快捷键进行保存(翻译前一定要先保存)。
- 接着单击“工具→外部→Qt语言家→Update Translations(lupdate)”菜单项来更新.ts翻译文件。
- 下面启动linguist.exe,在其中打开myLinguist_zh_CN.ts完成翻译并进行保存。
- 最后,直接在Qt Creator中运行程序即可,会发现界面已经完成了翻译。
- 后面程序中一旦有新的内容需要进行翻译,直接在菜单中使用lupdate进行更新,然后打开Qt Linguist完成翻译即可,Qt Creator已经做好了其他所有工作。
8.4.3 程序翻译中的相关问题
- 对所有用户可见的文本使用QString。因为QString内部使用了Unicode编码,世界上所有的语言都可以使用熟悉的文本处理操作来进行处理。而且,因为所有的Qt函数都使用QString作为参数来向用户呈现文本内容,所以没有char *到QString的转换开销。
- 对所有字符串文本使用tr()函数。无论什么时候使用要呈现给用户的文本,都要使用tr()函数进行处理。例如:
QLabel *label = new QLabel(tr("Password:"));
如果引用的文本没有在QObject子类的成员函数中,那么可以使用一个合适的类的tr()函数,或者直接使用QCoreApplication::translate()函数。
- 对加速键的值使用QKeySequence()函数。类似于Ctrl+Q或者Alt+F等加速键的值也需要被翻译。当使用了硬编码的Qt::CTRL + Qt::Key_Q等作为退出操作的快捷键,那么翻译将无法覆盖它,正确的习惯用法为:
exitAct = new QAction(tr("E&xit"), this);
exitAct->setShortcuts(QKeySequence::Quit);
- 对动态文本使用QString::arg()函数。对于字符串中使用arg()函数添加的变量,其中的%1、%2等参数的顺序在翻译时可以改变,它们对应的值不会改变。
- 翻译非Qt类。如果要使一个类中的字符串支持国际化,那么该类或者继承自QObject类或者使用Q_OBJECT宏。而对于非Qt类,如果要支持翻译,需要在类定义的开始使用Q_DECLARE_TR_FUNCTIONS()宏,这样就可以在该类中使用tr()函数了。例如:
class MyClass
{
Q_DECLARE_TR_FUNCTIONS(MyClass)
public:
MyClass();
...
};
- 为翻译添加注释。开发人员可以通过为每个可翻译字符串添加注释来帮助翻译人员完成翻译,建议的方式是使用//: 或者/*: … */等格式为tr()函数添加注释,例如:
//: This name refers to a host name.
hostNameLabel->setText(tr("Name:"));
/*: This text refers to a C++ code example. */
QString example = tr("Example");
8.5 Qt Quick的国际化
与Qt Widgets类似,Qt Quick同样支持国际化。其国际化的操作步骤与C++中是一样的。
8.5.1 简单示例
- 新建空项目,项目名称为myLinguist2。项目创建完成后打开mystyle.pro文件,添加如下代码并保存该文件:
QT += quick quickcontrols2
TRANSLATIONS = myLinguist2_zh_CN.ts
lupdate_only{
SOURCES += \*.qml
}
- 然后添加新的main.qml文件,将其内容修改为:
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Window {
width: 640; height: 480; visible: true
ColumnLayout {
spacing: 20
Label { text: qsTr("value is: %1").arg(dial.value) }
Button {text: qsTr("Button") }
Dial { id: dial; value: 0.5 }
}
}
- 接着添加main.cpp文件,修改其内容如下:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QTranslator>
int main(int argc, char *argv[])
{
QGuiApplication a(argc, argv);
QTranslator translator;
QLocale locale;
if( locale.language() == QLocale::Chinese)
{
if (translator.load("../myLinguist2/myLinguist2_zh_CN.qm")) {
a.installTranslator(&translator);
}
}
QQmlApplicationEngine engine;
engine.load(QUrl::fromLocalFile("../myLinguist2/main.qml"));
return a.exec();
}
- 下面选择“工具→外部→Qt语言家→Update Translations(lupdate)”菜单项(在操作之前确保已经保存了所有文件),从概要信息输出栏中可以看到,已经更新了myLinguist2_zh_CN.ts文件。
- 启动linguist.exe,在其中打开myLinguist2_zh_CN.ts完成翻译并进行保存,最后单击“文件→发布”菜单项生成对应的.qm文件。
- 下面回到Qt Creator中运行程序,会发现界面已经完成了翻译。
8.5.2 需要注意的问题
- 对所有需要在界面上显示的字符串使用qsTr()。QML中可以使用qsTr()、qsTranslate()、qsTrld()、QT_TR_NOOP()、QT_TRANSLATE_NOOP()和QT_TRID_NOOP()等函数将字符串标记为可翻译的。标记字符串最普通的方式是使用qsTr()函数,例如:
Text {
id: txt1;
text: qsTr("Back");
}
这样会在翻译文件中将“Back”标记为关键项。运行时,翻译系统会查找关键字“Back”,然后获取与当前系统语言环境对应的翻译值,结果会返回给text属性,用户界面将会根据当前语言环境显示“Back”合适的翻译。
- 为翻译添加上下文。用户界面上的字符串一般较短,所以需要给翻译人员一些提示来帮助其了解该字符串的上下文。源代码中要被翻译的字符串之前可以利用描述性文本来添加一些上下文信息。这些额外的描述性文本会包含到.ts翻译文件中。在下面的代码片段中,在“//:”一行中的文本是给翻译的主要注释信息,在“//~”一行中的文本是可选的额外信息。文本中的第一个单词作为.ts文件中XML元素的附加标识符,所以要确保该单词不是句子的一部分。例如,在.ts文件中会将注释“Context Not related to that”转换为“Not related to that”。
Text {
id: txt1;
// This user interface string is only used here
//: The back of the object, not the front
//~ Context Not related to back-stepping
text: qsTr("Back");
}
- 为相同的文本消除歧义。翻译系统会整合用户界面文本字符串为一些独立的项目,通过整合可以避免多次翻译相同的文本。然而,有时候相同文本却包含不同的意思。例如,在英语中,“back”既意味着向后退一步,也意味着一个对象与前相反的那一面。所以要在翻译时告诉翻译系统这里应该使用哪种翻译。通过qsTr()函数的第二个参数添加一些文本,可以消除相同文本的歧义。例如:
Text {
id: txt1;
text: qsTr("Back", "not front");
}
- 使用%x来为字符串插入参数。不同的语言会将单词以不同的顺序排放,所以通过串联一些单词和数据来构建句子不是理想的方式。通过使用%符号向句子中插入参数可以解决这一问题。例如,下面的代码片段中在句子里面包含了%1和%2两个数字参数,会使用.arg()函数来插入这些参数:
Text {
text: qsTr("File %1 of %2")
.arg(counter).arg(total)
}
- 本地化数字使用%Lx。如果指定一个参数时包含了%L修饰符,该数字便是根据当前区域设置的本地化数字。例如%L1 表示根据当前所选语言环境的数字格式约定来格式化第一个参数。如果total是数字“4321.56”,在英语区域设置中,输出是“4,321.56”;而在德语区域设置中,输出是“4.321,56”。
Text {
text: qsTr("%L1").arg(total)
}
- 日期、时间和货币国际化。QML中并没有特殊的字符串修饰符来格式化日期和时间,需要自己查询当前的语言环境,并使用Date的方法来格式化字符串。Qt.locale()会返回一个Locale对象,其中包含了关于语言环境的所有信息,特别是Locale.name属性包含了当前语言环境的语言和国家信息,可以通过解析这些值来为当前语言环境设置合适的翻译。例如使用Date()获得当前的日期,并为当前语言环境转换成了相应的字符串,然后使用%1参数将日期字符串插入到了翻译中:
Text {
text: qsTr("Date %1")
.arg(Date().toLocaleString(Qt.locale()))
}
- 强制刷新显示。如果用户改变了系统语言,但是没有重启,根据系统不同,在数组、列表模型或其它数据结构中的字符串可能不会自动刷新。当文本在用户界面显示时,如果要强制刷新它们,需要使用QT_TR_NOOP()宏来声明字符串。当要填充用于显示的对象时(例如,为ListModel设置项),需要显式地为每一个文本设置翻译。例如:
ListModel {
id: myListModel;
ListElement {
//: Capital city of Finland
name: QT_TR_NOOP("Helsinki");
}
}
...
Text {
text: qsTr(myListModel.get(0).name);
}
- 使用语言环境来扩展本地化功能。如果要在不同的地理区域使用不同的图形和声音,可以使用Qt.locale()获取当前的语言环境,然后为该语言环境选择合适的图形和声音。下面的代码片段中展示了怎样选择合适的图标来代表当前语言环境的语言:
Component.onCompleted: {
switch (Qt.locale().name.substring(0,2)) {
case "en": // 显示英文图标
languageIcon = "../images/language-icon_en.png";
break;
case "fi": // 显示芬兰语图标
languageIcon = "../images/language-icon_fi.png";
break;
default: // 显示默认图标
languageIcon = "../images/language-icon_default.png";
}
}
- 本地化应用程序。QML程序和C++程序使用了相同的底层本地化系统(lupdate、lrelease和.ts文件),可以在相同的程序中同时包含C++和QML用户界面字符串。系统会创建一个组合的翻译文件,QML和C++都可以访问其中的字符串。
- 包含QML文件。国际化时,lupdate工具会从程序中提取用户界面字符串,该工具会读取程序的.pro项目文件来确定哪些源文件包含需要翻译的文本,但是,SOURCES变量只适用于C++源文件,如果将QML或者JavaScript源文件罗列在这里,编译器会将它们作为C++文件来处理。作为一种变通的方法,可以使用lupdate_only{…}条件语句,这样lupdate工具可以发现.qml文件,但是C++编译器会忽略它们。例如,下面的.pro代码片段中指定了两个.qml文件:
lupdate_only{
SOURCES = main.qml \
MainPage.qml
}
还可以使用通配符来匹配.qml源文件,不过搜索不是递归的,所以需要指定每一个目录,例如:
lupdate_only{
SOURCES = *.qml \
*.js \
content/*.qml \
content/*.js
}
更多推荐
所有评论(0)