ドキュメントビューア
JSON、テキスト、およびPDFファイルを表示・印刷するためのウィジェットアプリケーションです。

Document Viewerは、静的および動的なツールバー、メニュー、アクションを備えたQMainWindow の使用方法を示しています。さらに、ウィジェットベースのアプリケーションにおける以下の機能も紹介しています:
- QSettings を使用してユーザー設定の取得および保存を行うこと、および過去に開いたファイルの履歴を管理すること。
- ウィジェットにカーソルを合わせた際の挙動の制御。
- 動的に読み込まれるプラグインの作成。
- UIをさまざまな言語にローカライズすること。
例の動作確認
サンプルは次の手順で実行できます。
- Qt Creator
Welcome モードを開き、Examples からこのサンプルを選択してください。詳細については、Qt Creator: Tutorial: Build and run を参照してください。
- 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は、生成時に利用可能なすべてのプラグインをロードします。また、ロードされたプラグイン、その名前、およびサポートされているMIMEタイプを照会するためのパブリックAPIを提供します:
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は、派生クラスがメインウィンドウ上にアクションやメニューを作成するためのprotectedメソッドを提供します。これらのアセットをメインウィンドウに表示するには、それらをメインウィンドウの子として配置します。AbstractViewerは、自身が作成したUIアセットの削除および破棄を担当します。また、シグナルとスロットを実装するために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 内に直接インクルードされていない点に注意してください。他のヘッダーファイルに大規模なヘッダーをインクルードすると、ビルドパフォーマンスに影響を与える可能性があります。このケースでは問題にはなりませんが、依存関係を最小限に抑えるために必要なヘッダーのみをインクルードするのがベストプラクティスです。
実装は、空のデストラクタから始まります。これは完全に省略することも可能です。デストラクタ内で実行すべき処理がないことをコードの読者に明確に示すため、空のデストラクタを実装しておくのが良い慣習です。
デストラクタの後には、3つの引数を取る初期化関数が続きます:
file、開いて表示するファイルへのポインタ。parent、エディタが配置されるQWidgetへのポインタ。mainWindow、メニューやメニューバーが処理されるアプリケーションのメインウィンドウへのポインタ。
この関数は、AbstractViewer の基底初期化関数を呼び出します。ファイルの内容を表示するための新しいQPlainTextEdit ウィジェットが作成されます。その後、TxtViewer のセットアップ関数が、基底クラスの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"));
}次に再実装された関数は、ビューアプラグインが実際にコンテンツを表示しているかどうかをメインアプリケーションに通知します。
コンパイルシステムで印刷がサポートされている場合、次のセクションでそれを実装します。
最後の 2 つの再実装は、現在のファイルを保存するか、新しい名前で保存する機能を提供します。
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() 関数では、画像を読み込み、そのサイズを判定します。画面サイズより大きい場合は、アスペクト比を維持したまま画面サイズに合わせて縮小します。この計算はネイティブピクセル単位で行う必要があり、鮮明に表示されるためには、結果のピクマップに対してデバイスピクセル比を設定する必要があります:
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 Example」のフォークです。これは、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 クラスは、QtのQTranslator をラップしたもので、メインアプリケーションと各プラグインの両方の国際化を管理します。各コンポーネント(メインアプリケーションおよびプラグイン)は独自の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); ...
実行時の言語切り替え
実行時の言語切り替えは、次の2つの方法でトリガーできます:
- 実行時にシステム全体の言語を切り替える場合:オペレーティングシステムがアプリケーションに「QEvent::LocaleChange 」イベントを送信します。
- メニューの「Help 」>「Language 」を使用する: 「
QMenu」項目をクリックすると、MainWindow::onActionSwitchLanguage()がトリガーされ、デフォルトのロケールが設定されるとともにQEvent::LocaleChange イベントが送信され、システムロケールの変更と同じ処理フローに入ります:
どちらの場合も、MainWindow::changeEvent() がイベントを処理します。QEvent::LocaleChange は新しい翻訳機能をインストールし、その結果として生成されたQEvent::LanguageChange がメインウィンドウのUIを再翻訳します。プラグインは、それぞれのイベントフィルターを通じてこれに対応します。
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.