文档查看器
一款用于显示和打印 JSON、文本及 PDF 文件的 Widgets 应用程序。

“文档查看器”演示了如何使用QMainWindow ,其中包含静态和动态工具栏、菜单以及操作。此外,它还演示了基于小部件的应用程序中的以下功能:
- 使用QSettings 查询和保存用户偏好设置,并管理先前打开的文件历史记录。
- 控制光标在小部件上悬停时的行为。
- 创建动态加载的插件。
- 将用户界面本地化为不同语言。
运行示例
您可以通过以下方式运行示例:
- Qt Creator
打开“Welcome ”模式,并从Examples 中选择该示例。有关更多信息,请参阅Qt Creator :教程:构建和运行。
- Qt Extension for Visual Studio Code
在Command Palette 中运行Qt: Open Qt examples 命令,然后从列表中选择该示例。有关详细信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
创建应用程序及其主窗口
应用程序及其主窗口是在main.cpp 中构建的。main()函数使用QCommandLineParser 来处理命令行参数——help、version以及一个可选的位置参数file。如果用户在启动应用程序时提供了文件路径,主窗口将打开该文件:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("QtProject"_L1);
QCoreApplication::setApplicationName("DocumentViewer"_L1);
QCoreApplication::setApplicationVersion("1.0"_L1);
Translator mainTranslator;
mainTranslator.setBaseName("docviewer"_L1);
mainTranslator.install();
QCommandLineParser parser;
parser.setApplicationDescription(Tr::tr("A viewer for JSON, PDF and text files"));
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("File"_L1, Tr::tr("JSON, PDF or text file to open"));
parser.process(app);
const QStringList &positionalArguments = parser.positionalArguments();
const QString &fileName = (positionalArguments.count() > 0) ? positionalArguments.at(0)
: QString();
MainWindow w(mainTranslator);
// Start application only if plugins are available
if (!w.hasPlugins()) {
QMessageBox::critical(nullptr,
Tr::tr("No viewer plugins found"),
Tr::tr("Unable to load viewer plugins. Exiting application."));
return 1;
}
w.show();
if (!fileName.isEmpty())
w.openFile(fileName);
return app.exec();
}MainWindow 类
MainWindow 类提供了一个包含菜单、操作和工具栏的应用程序界面。它能够打开文件,并自动检测其内容类型。此外,它还维护着之前打开过的文件列表,并在启动时使用QSettings 来存储和重新加载设置。MainWindow会根据打开文件的内容类型创建相应的查看器,并提供文档打印支持。
MainWindow 的构造函数会初始化在 `Qt Designer` 中创建的用户界面。`mainwindow.ui ` 文件在左侧提供了一个 `QTabWidget `,用于显示书签和缩略图;右侧则有一个 `QScrollArea `,用于查看文件内容。
ViewerFactory 类
ViewerFactory 类负责管理已知文件类型的查看器。这些查看器以插件的形式实现。当创建 ViewerFactory 的实例时,会将视图区域和主窗口的指针传递给构造函数:
m_factory.reset(new ViewerFactory(ui->viewArea, this));ViewerFactory 在初始化时会加载所有可用的插件。它提供了一个公共 API,用于查询已加载的插件、其名称以及支持的 MIME 类型:
using ViewerList = QList<AbstractViewer *>;
QStringList viewerNames(bool showDefault = false) const;
ViewerList viewers() const;
AbstractViewer *findViewer(const QString &viewerName) const;
AbstractViewer *defaultViewer() const;
QStringList supportedMimeTypes() const;viewer() 函数返回一个指向适合打开作为参数传递的 `QFile ` 的插件的指针:
m_viewer = m_factory->viewer(file);如果应用程序设置中包含针对查看器的部分,则将其传递给查看器的虚拟 `restoreState() ` 函数:
void MainWindow::restoreViewerSettings()
{
if (!m_viewer)
return;
QSettings settings;
settings.beginGroup(settingsViewers);
QByteArray viewerSettings = settings.value(m_viewer->viewerName(), QByteArray()).toByteArray();
settings.endGroup();
if (!viewerSettings.isEmpty())
m_viewer->restoreState(viewerSettings);
}随后,标准 UI 资源将传递给查看器,并将主滚动区域设置为显示查看器的显示控件:
m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
restoreViewerSettings();
ui->scrollArea->setWidget(m_viewer->widget());
return true;
}AbstractViewer 类
AbstractViewer 提供了一个用于查看、保存和打印文档的通用 API。可以查询文档和查看器的属性:
- 文档是否有内容?
- 文档是否已被修改?
- 是否支持概览(缩略图或书签)?
AbstractViewer 为派生类提供了受保护的方法,用于在主窗口上创建操作和菜单。为了在主窗口上显示这些元素,它们会被设置为主窗口的子窗口。AbstractViewer 负责移除并销毁其创建的用户界面元素。它继承自QObject 以实现信号与插槽机制。
信号
void uiInitialized();
当查看器接收完主窗口上所有 UI 资源的必要信息后,会发出此信号。
void printingEnabledChanged(bool enabled);
当文档打印被启用或禁用时,会发出此信号。这通常发生在新文档成功加载之后,或者例如当所有内容被移除时。
void showMessage(const QString &message, int timeout = 8000);
此信号的发出是为了向用户显示状态消息。
void documentLoaded(const QString &fileName);
该信号通知应用程序文档已成功加载。
TxtViewer 类
TxtViewer 是一个简单的文本查看器,继承自 AbstractViewer。它支持编辑文本文件、复制/剪切和粘贴、打印以及保存更改。
类定义
class TxtViewer : public ViewerInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "txtviewer.json")
Q_INTERFACES(ViewerInterface)类定义以Q_OBJECT 宏开头,该宏负责处理信号和槽。随后是Q_PLUGIN_METADATA 和Q_INTERFACES 宏,它们是注册插件所必需的。
该类继承自ViewerInterface ,而 又继承自AbstractViewer 。ViewerInterface 类用于在主窗口应用程序与插件之间提供接口。
QPluginLoader 此外还需要文件 txtviewer.json,其中必须包含插件的密钥:
{ "Keys": [ "txtviewer" ] }该类未定义构造函数,这意味着仅提供一个无参数的标准构造函数。所有其他函数(包括析构函数)均重新实现了ViewerInterface 的虚函数。这些函数用于与主应用程序交换数据、信息和指令。
public:
TxtViewer();
~TxtViewer() override;
void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
QString viewerName() const override { return QLatin1StringView(staticMetaObject.className()); };
QStringList supportedMimeTypes() const override;
bool saveDocument() override { return saveFile(m_file.get()); };
bool saveDocumentAs() override;
bool hasContent() const override;
QByteArray saveState() const override { return {}; }
bool restoreState(QByteArray &) override { return true; }
bool supportsOverview() const override { return false; }
#ifdef DOCUMENTVIEWER_PRINTSUPPORT
protected:
void printDocument(QPrinter *printer) const override;
#endif // DOCUMENTVIEWER_PRINTSUPPORT
private slots:
void setupTxtUi();
private:
void retranslate() override;
void openFile();
bool saveFile (QFile *file);
QPlainTextEdit *m_textEdit;
QAction *m_cutAct = nullptr;
QAction *m_copyAct = nullptr;
QAction *m_pasteAct = nullptr;
};TxtViewer 类的实现
#include "txtviewer.h"
#include <QFileDialog>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QToolBar>
#include <QGuiApplication>
#include <QPainter>
#include <QTextDocument>
#include <QDir>
#ifdef DOCUMENTVIEWER_PRINTSUPPORT
#include <QPrinter>
#include <QPrintDialog>
#endif
using namespace Qt::StringLiterals;
TxtViewer::TxtViewer()
: m_cutAct(new QAction(this)),
m_copyAct(new QAction(this)),
m_pasteAct(new QAction(this))
{
connect(this, &AbstractViewer::uiInitialized, this, &TxtViewer::setupTxtUi);
const QIcon cutIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCut,
QIcon(":/demos/documentviewer/images/cut.png"_L1));
m_cutAct->setIcon(cutIcon);
m_cutAct->setShortcuts(QKeySequence::Cut);
const QIcon copyIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCopy,
QIcon(":/demos/documentviewer/images/copy.png"_L1));
m_copyAct->setIcon(copyIcon);
m_copyAct->setShortcuts(QKeySequence::Copy);
const QIcon pasteIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditPaste,
QIcon(":/demos/documentviewer/images/paste.png"_L1));
m_pasteAct->setIcon(pasteIcon);
m_pasteAct->setShortcuts(QKeySequence::Paste);
}
TxtViewer::~TxtViewer() = default;
void TxtViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
AbstractViewer::init(file, new QPlainTextEdit(parent), mainWindow);
m_textEdit = qobject_cast<QPlainTextEdit *>(widget());
setTranslationBaseName("txtviewer"_L1);
}
QStringList TxtViewer::supportedMimeTypes() const
{
return {"text/plain"_L1};
}
void TxtViewer::setupTxtUi()
{
QMenu *editMenu = addMenu();
QToolBar *editToolBar = addToolBar();
#if QT_CONFIG(clipboard)
connect(m_cutAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
editMenu->addAction(m_cutAct);
editToolBar->addAction(m_cutAct);
connect(m_copyAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::copy);
editMenu->addAction(m_copyAct);
editToolBar->addAction(m_copyAct);
connect(m_pasteAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
editMenu->addAction(m_pasteAct);
editToolBar->addAction(m_pasteAct);
menuBar()->addSeparator();
m_cutAct->setEnabled(false);
m_copyAct->setEnabled(false);
connect(m_textEdit, &QPlainTextEdit::copyAvailable, m_cutAct, &QAction::setEnabled);
connect(m_textEdit, &QPlainTextEdit::copyAvailable, m_copyAct, &QAction::setEnabled);
#endif // QT_CONFIG(clipboard)
openFile();
connect(m_textEdit, &QPlainTextEdit::textChanged, this, [&](){
maybeSetPrintingEnabled(hasContent());
});
connect(m_uiAssets.back, &QAction::triggered, m_textEdit, [&](){
auto *bar = m_textEdit->verticalScrollBar();
if (bar->value() > bar->minimum())
bar->setValue(bar->value() - 1);
});
connect(m_uiAssets.forward, &QAction::triggered, m_textEdit, [&](){
auto *bar = m_textEdit->verticalScrollBar();
if (bar->value() < bar->maximum())
bar->setValue(bar->value() + 1);
});
retranslate();
}首先,我们包含访问TxtViewer 所用所有类所需的头文件。此外,我们还包含txtviewer.h 。
QPrinter 只有当编译系统启用了打印支持时,才会包含 `QPrintDialog `。
请注意,这些头文件并未直接包含在mainwindow.h 中。在其他头文件中包含大型头文件可能会影响构建性能。虽然在此情况下不会引发问题,但仅包含必要的头文件以将依赖关系降至最低是最佳实践。
实现从一个空的析构函数开始。它可以完全省略。将其实现为空是一个良好的编程习惯,旨在向代码阅读者表明析构函数中无需执行任何操作。
析构函数之后是一个初始化函数,该函数接受三个参数:
file,即待打开并显示的文件的指针。parent,指向将放置编辑器的QWidget。mainWindow,指向应用程序的主窗口,该窗口负责处理菜单和菜单栏。
该函数会调用AbstractViewer 的基类初始化函数。随后创建一个新的QPlainTextEdit 控件,用于显示文件内容。接着,将TxtViewer 的setup函数连接到基类的uiInitialized 信号上。
接下来的函数返回文本查看器支持的 MIME 类型列表。目前仅支持纯文本。
最后一个初始化函数添加了查看器特有的 UI 组件,如菜单、图标、按钮和工具提示。它利用AbstractViewer 提供的方法,确保当使用其他查看器插件显示新文件时,这些组件会从应用程序的主窗口中移除。
void TxtViewer::openFile()
{
const QString type = tr("open");
if (!m_file->open(QFile::ReadOnly | QFile::Text)) {
statusMessage(tr("Cannot read file %1:\n%2.")
.arg(QDir::toNativeSeparators(m_file->fileName()),
m_file->errorString()), type);
return;
}
QTextStream in(m_file.get());
#if QT_CONFIG(cursor)
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
if (!m_textEdit->toPlainText().isEmpty()) {
m_textEdit->clear();
disablePrinting();
}
m_textEdit->setPlainText(in.readAll());
#if QT_CONFIG(cursor)
QGuiApplication::restoreOverrideCursor();
#endif
statusMessage(tr("File %1 loaded.")
.arg(QDir::toNativeSeparators(m_file->fileName())), type);
maybeEnablePrinting();
}openFile 打开文件,将其内容传输到QPlainTextEdit 中,并根据打开操作是否成功向用户显示状态消息。
bool TxtViewer::hasContent() const
{
return (!m_textEdit->toPlainText().isEmpty());
}
#ifdef DOCUMENTVIEWER_PRINTSUPPORT
void TxtViewer::printDocument(QPrinter *printer) const
{
if (!hasContent())
return;
m_textEdit->print(printer);
}
#endif // DOCUMENTVIEWER_PRINTSUPPORT
bool TxtViewer::saveFile(QFile *file)
{
QString errorMessage;
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
if (file->open(QFile::WriteOnly | QFile::Text)) {
QTextStream out(file);
out << m_textEdit->toPlainText();
} else {
errorMessage = tr("Cannot open file %1 for writing:\n%2.")
.arg(QDir::toNativeSeparators(file->fileName())),
file->errorString();
}
QGuiApplication::restoreOverrideCursor();
if (!errorMessage.isEmpty()) {
statusMessage(errorMessage);
return false;
}
statusMessage(tr("File %1 saved")
.arg(QDir::toNativeSeparators(file->fileName())));
return true;
}
bool TxtViewer::saveDocumentAs()
{
QFileDialog dialog(mainWindow());
dialog.setWindowModality(Qt::WindowModal);
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec() != QDialog::Accepted)
return false;
const QStringList &files = dialog.selectedFiles();
if (files.isEmpty())
return false;
//newFile();
m_file->setFileName(files.first());
return saveDocument();
}
void TxtViewer::retranslate()
{
if (toolBars().isEmpty())
return;
menus().at(0)->setTitle(tr("&Edit"));
toolBars().at(0)->setWindowTitle(tr("Edit"));
m_cutAct->setText(tr("Cu&t"));
m_cutAct->setStatusTip(tr("Cut the current selection's contents to the clipboard"));
m_copyAct->setText(tr("&Copy"));
m_copyAct->setStatusTip(tr("Copy the current selection's contents to the clipboard"));
m_pasteAct->setText(tr("&Paste"));
m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current selection"));
}接下来重新实现的函数用于告知主应用程序,查看器插件是否正在实际显示内容。
如果编译系统支持打印,下一节将实现该功能。
最后两个重写实现了保存当前文件或以新名称保存文件的功能。
ImageViewer 类
ImageViewer 使用QLabel ,以QImageReader 支持的方式显示图像。
在构造函数中,我们提高了QImageReader 的分配限制,以便支持更大的照片:
ImageViewer::ImageViewer()
: m_zoomInAct(new QAction(this)),
m_zoomOutAct(new QAction(this)),
m_resetZoomAct(new QAction(this)),
m_formats(imageFormats())
{
connect(this, &AbstractViewer::uiInitialized, this, &ImageViewer::setupImageUi);
QImageReader::setAllocationLimit(1024); // MB
m_zoomInAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomIn));
m_zoomInAct->setShortcut(QKeySequence::ZoomIn);
connect(m_zoomInAct, &QAction::triggered, this, &ImageViewer::zoomIn);
m_zoomOutAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomOut));
m_zoomOutAct->setShortcut(QKeySequence::ZoomOut);
connect(m_zoomOutAct, &QAction::triggered, this, &ImageViewer::zoomOut);
m_resetZoomAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomFitBest));
m_resetZoomAct->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_0));
connect(m_resetZoomAct, &QAction::triggered, this, &ImageViewer::resetZoom);
}在 openFile() 函数中,我们加载图像并确定其尺寸。如果图像大于屏幕尺寸,我们会将其缩放至屏幕尺寸,同时保持宽高比。该计算必须以原生像素为单位进行,并且需要为生成的 pixmap 设置设备像素比率,以确保显示清晰:
void ImageViewer::openFile()
{
#if QT_CONFIG(cursor)
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
const QString name = m_file->fileName();
QImageReader reader(name);
const QImage origImage = reader.read();
if (origImage.isNull()) {
statusMessage(tr("Cannot read file %1:\n%2.")
.arg(QDir::toNativeSeparators(name),
reader.errorString()), tr("open"));
disablePrinting();
#if QT_CONFIG(cursor)
QGuiApplication::restoreOverrideCursor();
#endif
return;
}
clear();
QImage image = origImage.colorSpace().isValid()
? origImage.convertedToColorSpace(QColorSpace::SRgb)
: origImage;
const auto devicePixelRatio = m_imageLabel->devicePixelRatioF();
m_imageSize = QSizeF(image.size()) / devicePixelRatio;
QPixmap pixmap = QPixmap::fromImage(image);
pixmap.setDevicePixelRatio(devicePixelRatio);
m_imageLabel->setPixmap(pixmap);
const QSizeF targetSize = m_imageLabel->parentWidget()->size();
if (m_imageSize.width() > targetSize.width()
|| m_imageSize.height() > targetSize.height()) {
m_initialScaleFactor = qMin(targetSize.width() / m_imageSize.width(),
targetSize.height() / m_imageSize.height());
}
m_maxScaleFactor = 3 * m_initialScaleFactor;
m_minScaleFactor = m_initialScaleFactor / 3;
doSetScaleFactor(m_initialScaleFactor);
statusMessage(msgOpen(name, origImage));
#if QT_CONFIG(cursor)
QGuiApplication::restoreOverrideCursor();
#endif
maybeEnablePrinting();
}JsonViewer 类
JsonViewer 通过QTreeView 显示JSON文件。在内部,它将文件内容加载到QJsonDocument 中,并利用JsonItemModel 将其填充到自定义树模型中。
该 JSON 查看器插件演示了如何实现一个从 `QAbstractItemModel` 继承的自定义项模型。`JsonTreeItem ` 类提供了一套用于操作 JSON 数据并将其回传至底层 `QJsonDocument` 的基本 API。
JsonViewer 将文档的顶级对象用作导航书签。其他节点(键和值)可以作为额外书签添加,也可以从书签列表中移除。
PdfViewer 类
PdfViewer 类(及其插件)是PDF Viewer Widget示例的分支。它演示了如何使用QScroller 流畅地翻阅文档。
其他相关类
HoverWatcher 类
HoverWatcher 类在鼠标悬停于小部件上时设置一个覆盖光标,并在鼠标移开时恢复原光标。为防止为同一个小部件创建多个HoverWatcher实例,它被实现为每个小部件的单例。
HoverWatcher 继承自QObject ,并将它所监视的QWidget 作为实例的父对象。它会安装一个事件过滤器,用于拦截悬停事件而不消耗这些事件:
HoverWatcher::HoverWatcher(QWidget *watched)
: QObject(watched), m_watched(watched)
{
Q_ASSERT(watched);
m_cursorShapes[Entered].emplace(Qt::OpenHandCursor);
m_cursorShapes[MousePress].emplace(Qt::ClosedHandCursor);
m_cursorShapes[MouseRelease].emplace(Qt::OpenHandCursor);
// no default for Left => restore override cursor
m_watched->installEventFilter(this);
}HoverAction 枚举列出了 HoverWatcher 响应的操作:
enum HoverAction {
Entered,
MousePress,
MouseRelease,
Left,
Ignore
};静态函数用于创建监听器、检查特定 `QWidget` 是否存在,或移除监听器:
static HoverWatcher *watcher(QWidget *watched);
static const HoverWatcher *watcher(const QWidget *watched);
static bool hasWatcher(QWidget *widget);
static void dismiss(QWidget *watched);可以为每个 HoverAction 设置或取消设置光标形状。如果没有关联的光标形状,则在触发操作时将恢复应用程序的覆盖光标。
public slots:
void setCursorShape(HoverAction type, Qt::CursorShape shape);
void unSetCursorShape(HoverAction type);mouseButtons 属性用于指定MousePress 操作中需要考虑的鼠标按钮:
void setMouseButtons(Qt::MouseButtons buttons);
void setMouseButton(Qt::MouseButton button, bool enable);处理完操作后,会发出特定于该操作的信号:
signals:
void entered();
void mousePressed();
void mouseReleased();
void left();会发出一个通用信号,并将已处理的操作作为参数传递:
void hoverAction(HoverAction action);RecentFiles 类
RecentFiles 是一个QStringList ,专门用于管理最近打开的文件列表。
RecentFiles 具有槽,可用于一次添加单个文件或多个文件。如果路径指向一个存在且可打开的文件,则该条目将被添加到最近文件列表中。如果文件已存在于列表中,则将其从原位置移除并添加到列表顶部。
public slots:
void addFile(const QString &fileName) { addFile(fileName, EmitPolicy::EmitWhenChanged); }
void addFiles(const QStringList &fileNames);文件可通过名称或索引从列表中移除:
void removeFile(const QString &fileName) { removeFile(m_files.indexOf(fileName)); }
void removeFile(qsizetype index) {removeFile(index, RemoveReason::Other); }实现从QSettings 保存和恢复功能的插槽:
void saveSettings(QSettings &settings, const QString &key) const;
bool restoreFromSettings(QSettings &settings, const QString &key);恢复设置时,不存在的文件将被忽略。maxFiles 属性用于指定要存储的最近文件的最大数量(默认值为 10)。
qsizetype maxFiles();
void setMaxFiles(qsizetype maxFiles);RecentFiles 在接受文件之前,先验证该文件是否可读。
RecentFileMenu 类
RecentFileMenu 是一个QMenu ,专门用于将RecentFiles对象显示为子菜单。
其构造函数接受一个指向父级QObject 的指针以及一个指向RecentFiles对象的指针,并将该对象的内容可视化显示。当用户从列表中选择一个最近文件时,其fileOpened() 信号会被触发,并将该文件的绝对路径作为参数传递。
注意: RecentFileMenu 由其父控件或传递给其构造函数的RecentFiles 对象销毁。
class RecentFileMenu : public QMenu
{
Q_OBJECT
public:
explicit RecentFileMenu(QWidget *parent, RecentFiles *recent);
signals:
void fileOpened(const QString &fileName);
...
};翻译
应用程序的用户界面提供英语和德语版本。默认语言由 Qt 自动选择:如果系统语言为德语,则为德语;否则为英语。此外,用户还可以在Help >Language 菜单中切换语言。每个插件以及主应用程序都负责在运行时独立加载各自的翻译。
CMake 集成
顶级 CMakeLists.txt 文件声明了随附的语言。
qt_standard_project_setup(REQUIRES 6.8
I18N_SOURCE_LANGUAGE en
I18N_TRANSLATED_LANGUAGES de
)documentviewer 目标定义了主应用程序。它将该目标的本地化字符串存储并加载到docviewer_de.ts和docviewer_en.ts文件中。此外,它还将Qt提供的相应qtbase translations 合并到生成的翻译文件中,从而确保包括打印对话框在内的Qt对话框也能得到正确的翻译:
qt_add_translations(documentviewer
SOURCE_TARGETS documentviewer abstractviewer
TS_FILE_BASE docviewer
MERGE_QT_TRANSLATIONS
QT_TRANSLATION_CATALOGS qtbase
)每个插件级别的CMakeLists.txt 仅对该插件的源文件(SOURCE_TARGETS )调用qt_add_translations 。将翻译文件的作用域限定在插件目标范围内,可避免对主应用程序和其他插件的源文件进行重复扫描和重新翻译:
qt_add_translations(txtviewer
SOURCE_TARGETS txtviewer
TS_FILE_BASE txtviewer
)Translator 类
Translator 类是 QtQTranslator 的封装类,负责管理主应用程序和每个插件的国际化。每个组件(主应用程序和插件)都有自己的 Translator 实例,从而实现整个应用程序范围内协调一致的语言切换。 在启动时或用户选择新语言时,会调用Translator::install() 方法。该方法使用QTranslator::load()根据QLocale::uiLanguages()以及Qt资源系统中的基名来加载翻译文件。如果未找到匹配的翻译,则回退到英语。
voidTranslator::install()
{
if(m_baseName.isEmpty()) {
qWarning() << "The basename of the translation is not set. Ignoring.";
return;
}
if(!m_translator.isEmpty())
qApp->removeTranslator(&m_translator);
if(m_translator.load(m_trLocale,m_baseName, "_"_L1, ":/i18n/"_L1)
&& qApp->installTranslator(&m_translator)) {
qInfo() << "Loaded translation" << m_translator.filePath();
}else{
if(m_trLocale.language()!=QLocale::English) {
qWarning() << "Failed to load translation" << m_baseName <<
"针对" <<m_trLocale.name()<< "。若不适用,则回退到英文翻译";
setLanguage(QLocale::English);
}
}
}插件支持
AbstractViewer 基类提供了翻译支持,因此每个插件都可以自主管理其自身的翻译:
AbstractViewer::setTranslationBaseName(): 初始化一个Translator对象,设置其基名,并将其安装以加载默认翻译。void AbstractViewer::setTranslationBaseName(const QString &baseName) { m_translator.reset(new Translator); m_translator->setBaseName(baseName); m_translator->install(); }AbstractViewer::eventFilter():该方法在init()的主窗口中安装,用于监听QEvent::LanguageChange 事件。当区域设置发生变化时,它会针对新语言重新安装插件的翻译器,并调用retranslate()来刷新所有文本。bool AbstractViewer::eventFilter(QObject *, QEvent *event) { if (event->type() != QEvent::LanguageChange) return false; const QLocale locale; if (locale != m_currentLocale) { m_currentLocale = locale; if (m_translator) { m_translator->setLanguage(locale.language()); m_translator->install(); } retranslate(); } return false; }AbstractViewer::retranslate(): 每个插件都需要实现的虚拟方法,用于重新翻译其自身的 UI 文本。例如,在 ImageViewer 中重写如下:retranslate(); }
由插件拥有且包含可翻译字符串的控件可以直接处理 `QEvent::LanguageChange `。例如,`ZoomSelector ` 重写了 `changeEvent() ` 方法,以重新翻译其下拉列表项:
void ZoomSelector::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange)
retranslate();
QComboBox::changeEvent(event);
}应用程序启动
- 主应用程序:在
main.cpp中,我们在显示窗口之前加载应用程序的翻译:Translator mainTranslator; mainTranslator.setBaseName("docviewer"_L1); mainTranslator.install(); - 插件:每个插件都在其
init()函数中调用AbstractViewer::setTranslationBaseName(),以使用其翻译文件名初始化一个Translator,并安装当前语言的翻译。void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow) ... setTranslationBaseName("imgviewer"_L1); ...
运行时语言切换
运行时语言切换可通过以下两种方式触发:
- 在运行时切换整个系统的语言:操作系统会向应用程序发送一个“QEvent::LocaleChange ”事件。
- 使用菜单“Help >Language ”:点击“
QMenu”选项将触发MainWindow::onActionSwitchLanguage(),该方法会设置默认区域设置并发布QEvent::LocaleChange 事件,从而进入与系统区域设置变更相同的处理流程:
在这两种情况下,MainWindow::changeEvent() 都会处理这些事件。QEvent::LocaleChange 会安装新的翻译器,随后触发的QEvent::LanguageChange 事件会重新翻译主窗口的用户界面。插件会通过其事件过滤器做出响应。
void MainWindow::changeEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
statusBar()->clearMessage();
break;
case QEvent::LocaleChange:
m_translator.setLanguage(QLocale().language());
m_translator.install();
break;
default:
break;
}
QMainWindow::changeEvent(event);
}源文件
另请参阅 所有 Qt 示例。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.