Dokument-Viewer

Eine Widgets-Anwendung zum Anzeigen und Drucken von JSON-, Text- und PDF-Dateien.

Die Benutzeroberfläche des Dokumentenbetrachters zeigt ein Popup-Fenster "Offenen Modus auswählen" an.

Document Viewer demonstriert die Verwendung einer QMainWindow mit statischen und dynamischen Symbolleisten, Menüs und Aktionen. Außerdem werden die folgenden Funktionen in Widget-basierten Anwendungen demonstriert:

  • Verwendung von QSettings zum Abfragen und Speichern von Benutzereinstellungen und zum Verwalten des Verlaufs zuvor geöffneter Dateien.
  • Steuerung des Cursor-Verhaltens, wenn der Mauszeiger über Widgets bewegt wird.
  • Erstellen von dynamisch geladenen Plugins.
  • Lokalisierung der Benutzeroberfläche für verschiedene Sprachen.

Ausführen des Beispiels

Um das Beispiel auszuführen Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples. Weitere Informationen finden Sie unter Qt Creator: Tutorial: Erstellen und Ausführen.

Erstellen einer Anwendung und des Hauptfensters

Die Anwendung und ihr Hauptfenster werden in main.cpp erstellt. Die Funktion main() verwendet QCommandLineParser, um Befehlszeilenargumente zu verarbeiten - help, version und ein optionales Positionsargument, file. Wenn der Benutzer beim Starten der Anwendung einen Pfad zu einer Datei angegeben hat, wird diese im Hauptfenster geöffnet:

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-Klasse

Die Klasse MainWindow bietet einen Anwendungsbildschirm mit Menüs, Aktionen und einer Symbolleiste. Sie kann eine Datei öffnen und erkennt automatisch ihren Inhaltstyp. Sie verwaltet außerdem eine Liste der zuvor geöffneten Dateien und verwendet QSettings, um die Einstellungen zu speichern und beim Start neu zu laden. Das MainWindow erstellt einen geeigneten Betrachter für die geöffnete Datei, basierend auf ihrem Inhaltstyp, und bietet Unterstützung für das Drucken eines Dokuments.

Der Konstruktor von MainWindow initialisiert die in Qt Designer erstellte Benutzeroberfläche. Die Datei mainwindow.ui bietet auf der linken Seite eine QTabWidget, die Lesezeichen und Miniaturansichten anzeigt. Auf der rechten Seite befindet sich ein QScrollArea zur Anzeige des Dateiinhalts.

ViewerFactory-Klasse

Die Klasse ViewerFactory verwaltet Viewer für bekannte Dateitypen. Diese Betrachter werden als Plugins implementiert. Wenn eine Instanz einer ViewerFactory erstellt wird, werden Zeiger auf den Ansichtsbereich und das Hauptfenster an den Konstruktor übergeben:

m_factory.reset(new ViewerFactory(ui->viewArea, this));

ViewerFactory lädt alle verfügbaren Plugins bei der Erstellung. Sie bietet eine öffentliche API um die geladenen Plugins, ihre Namen und unterstützten MIME Typen abzufragen:

    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;

Die Funktion viewer() gibt einen Zeiger auf das Plugin zurück, das zum Öffnen des als Argument übergebenen QFile geeignet ist:

m_viewer = m_factory->viewer(file);

Wenn die Anwendungseinstellungen einen Abschnitt für den Viewer enthalten, wird dieser an die virtuelle Funktion restoreState() des Viewers übergeben:

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);
}

Dann werden die Standard-UI-Assets an den Viewer übergeben und der Hauptbildlaufbereich wird so eingestellt, dass er das Anzeige-Widget des Viewers anzeigt:

    m_viewer->initViewer(ui->actionBack, ui->actionForward, ui->menuHelp->menuAction(), ui->tabWidget);
    restoreViewerSettings();
    ui->scrollArea->setWidget(m_viewer->widget());
    return true;
}

AbstractViewer-Klasse

AbstractViewer bietet eine verallgemeinerte API zum Anzeigen, Speichern und Drucken eines Dokuments. Eigenschaften sowohl des Dokuments als auch des Viewers können abgefragt werden:

  • Hat das Dokument einen Inhalt?
  • Wurde es geändert?
  • Wird eine Übersicht (Miniaturansichten oder Lesezeichen) unterstützt?

AbstractViewer bietet geschützte Methoden für abgeleitete Klassen, um Aktionen und Menüs im Hauptfenster zu erstellen. Damit diese Elemente im Hauptfenster angezeigt werden können, werden sie diesem übergeordnet. AbstractViewer ist für das Entfernen und Zerstören der von ihm erstellten UI-Assets verantwortlich. Er erbt von QObject, um Signale und Slots zu implementieren.

Signale

void uiInitialized();

Dieses Signal wird ausgegeben, nachdem ein Viewer alle notwendigen Informationen über UI-Assets im Hauptfenster erhalten hat.

void printingEnabledChanged(bool enabled);

Dieses Signal wird ausgegeben, wenn der Dokumentendruck entweder aktiviert oder deaktiviert ist. Dies geschieht, nachdem ein neues Dokument erfolgreich geladen wurde oder z. B. der gesamte Inhalt entfernt wurde.

void showMessage(const QString &message, int timeout = 8000);

Dieses Signal wird ausgegeben, um dem Benutzer eine Statusmeldung anzuzeigen.

void documentLoaded(const QString &fileName);

Dieses Signal teilt der Anwendung mit, dass ein Dokument erfolgreich geladen wurde.

TxtViewer-Klasse

TxtViewer ist ein einfacher Textbetrachter, der von AbstractViewer erbt. Sie unterstützt das Bearbeiten von Textdateien, Kopieren/Ausschneiden und Einfügen, Drucken und Speichern von Änderungen.

ImageViewer-Klasse

ImageViewer zeigt Bilder an, wie sie von QImageReader unterstützt werden, unter Verwendung einer QLabel.

Im Konstruktor erhöhen wir die Zuweisungsgrenze von QImageReader, um größere Fotos zu ermöglichen:

ImageViewer::ImageViewer() : m_formats(imageFormats())
{
    connect(this, &AbstractViewer::uiInitialized, this, &ImageViewer::setupImageUi);
    QImageReader::setAllocationLimit(1024); // MB
}

Mit der Funktion openFile() laden wir das Bild und bestimmen seine Größe. Wenn es größer als der Bildschirm ist, wird es unter Beibehaltung des Seitenverhältnisses auf die Bildschirmgröße herunterskaliert. Diese Berechnung muss in nativen Pixeln erfolgen, und das Pixelverhältnis des Geräts muss auf die resultierende Pixmap eingestellt werden, damit sie scharf erscheint:

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-Klasse

JsonViewer zeigt eine JSON-Datei in einer QTreeView an. Intern lädt sie den Inhalt einer Datei in eine QJsonDocument und verwendet ihn, um ein benutzerdefiniertes Baummodell mit JsonItemModel zu füllen.

Das JSON-Viewer-Plugin demonstriert, wie ein von QAbstractItemModel geerbtes benutzerdefiniertes Elementmodell implementiert wird. Die Klasse JsonTreeItem bietet eine grundlegende API für die Bearbeitung von JSON-Daten und deren Rückübertragung an die zugrunde liegende QJsonDocument.

JsonViewer verwendet die Top-Level-Objekte des Dokuments als Lesezeichen für die Navigation. Andere Knoten (Schlüssel und Werte) können als zusätzliche Lesezeichen hinzugefügt oder aus der Lesezeichenliste entfernt werden.

PdfViewer-Klasse

Die Klasse PdfViewer (und das Plugin) ist ein Fork des PDF Viewer Widget Example. Sie demonstriert die Verwendung von QScroller zum reibungslosen Blättern in einem Dokument.

Andere relevante Klassen

HoverWatcher-Klasse

Die Klasse HoverWatcher setzt einen Override-Cursor, wenn die Maus über einem Widget schwebt, und stellt ihn beim Verlassen wieder her. Um zu verhindern, dass mehrere HoverWatcher-Instanzen für dasselbe Widget erstellt werden, ist sie als Singleton pro Widget implementiert.

HoverWatcher erbt von QObject und nimmt das QWidget, das er beobachtet, als Elternteil der Instanz. Er installiert einen Ereignisfilter, um Hover-Ereignisse abzufangen, ohne sie zu konsumieren:

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);
}

Das HoverAction enum listet die Aktionen auf, auf die HoverWatcher reagiert:

    enum HoverAction {
        Entered,
        MousePress,
        MouseRelease,
        Left,
        Ignore
    };

Statische Funktionen erstellen Beobachter, prüfen deren Existenz für eine bestimmte QWidget, oder beenden einen Beobachter:

    static HoverWatcher *watcher(QWidget *watched);
    static const HoverWatcher *watcher(const QWidget *watched);
    static bool hasWatcher(QWidget *widget);
    static void dismiss(QWidget *watched);

Für jede HoverAction kann eine Cursorform gesetzt oder nicht gesetzt werden. Wenn es keine zugehörige Cursorform gibt, wird der Override-Cursor der Anwendung wiederhergestellt, wenn die Aktion ausgelöst wird.

public slots:
    void setCursorShape(HoverAction type, Qt::CursorShape shape);
    void unSetCursorShape(HoverAction type);

Die Eigenschaft mouseButtons enthält die zu berücksichtigenden Maustasten für eine MousePress Aktion:

    void setMouseButtons(Qt::MouseButtons buttons);
    void setMouseButton(Qt::MouseButton button, bool enable);

Aktionsspezifische Signale werden nach der Verarbeitung einer Aktion ausgegeben:

signals:
    void entered();
    void mousePressed();
    void mouseReleased();
    void left();

Es wird ein allgemeines Signal ausgegeben, das die verarbeitete Aktion als Argument übergibt:

void hoverAction(HoverAction action);
Die Klasse RecentFiles

RecentFiles ist eine QStringList, die darauf spezialisiert ist, eine Liste von kürzlich geöffneten Dateien zu verwalten.

RecentFiles hat Slots, um entweder eine einzelne Datei oder mehrere Dateien auf einmal hinzuzufügen. Ein Eintrag wird in die Liste der zuletzt geöffneten Dateien aufgenommen, wenn der Pfad auf eine Datei zeigt, die existiert und geöffnet werden kann. Befindet sich eine Datei bereits in der Liste, wird sie von ihrer ursprünglichen Position entfernt und an den Anfang gesetzt.

public slots:
    void addFile(const QString &fileName) { addFile(fileName, EmitPolicy::EmitWhenChanged); }
    void addFiles(const QStringList &fileNames);

Dateien werden entweder nach Namen oder nach Index aus der Liste entfernt:

    void removeFile(const QString &fileName) { removeFile(m_files.indexOf(fileName)); }
    void removeFile(qsizetype index) {removeFile(index, RemoveReason::Other); }

Slots, die das Speichern und Wiederherstellen von QSettings ermöglichen:

    void saveSettings(QSettings &settings, const QString &key) const;
    bool restoreFromSettings(QSettings &settings, const QString &key);

Bei der Wiederherstellung von Einstellungen werden nicht vorhandene Dateien ignoriert. Die Eigenschaft maxFiles enthält die maximale Anzahl der zu speichernden aktuellen Dateien (Standard ist 10).

qsizetype maxFiles();
void setMaxFiles(qsizetype maxFiles);

RecentFiles prüft, ob eine Datei gelesen werden kann, bevor sie akzeptiert wird.

RecentFileMenu-Klasse

RecentFileMenu ist eine QMenu, die darauf spezialisiert ist, ein RecentFiles-Objekt als Untermenü anzuzeigen.

Ihr Konstruktor nimmt einen Zeiger auf ein übergeordnetes QObject und einen Zeiger auf ein RecentFiles-Objekt entgegen, dessen Inhalt sie visualisieren wird. Das Signal fileOpened(), das ausgelöst wird, wenn der Benutzer eine neue Datei aus der Liste auswählt, übergibt den absoluten Pfad zu der Datei als Argument.

Hinweis: RecentFileMenu wird entweder von seinem übergeordneten Widget oder von dem RecentFiles Objekt, das an seinen Konstruktor übergeben wird, zerstört.

class RecentFileMenu : public QMenu
{
    Q_OBJECT

public:
    explicit RecentFileMenu(QWidget *parent, RecentFiles *recent);

signals:
    void fileOpened(const QString &fileName);
    ...
};

Übersetzungen

Die Benutzeroberfläche der Anwendung ist in Englisch und Deutsch verfügbar. Die Standardsprache wird von Qt automatisch ausgewählt: Deutsch, wenn die Systemsprache Deutsch ist, ansonsten Englisch. Der Benutzer kann die Sprache auch über das Menü Help > Language umschalten. Jedes Plugin und auch die Hauptanwendung sind unabhängig voneinander für das Laden ihrer eigenen Übersetzungen während der Laufzeit verantwortlich.

CMake-Integration

Die CMakeLists.txt auf oberster Ebene deklariert die ausgelieferten Sprachen.

qt_standard_project_setup(REQUIRES 6.8
    I18N_SOURCE_LANGUAGE en
    I18N_TRANSLATED_LANGUAGES de
)

Das Ziel documentviewer definiert die Hauptanwendung. Es speichert und lädt die lokalisierten Strings für das Target in den Dateien docviewer_de.ts und docviewer_en.ts. Außerdem fügt es die von Qt bereitgestellten qtbase translations in die generierten Übersetzungsdateien ein, so dass auch Qt-Dialoge wie der Druckdialog korrekt übersetzt werden:

qt_add_translations(documentviewer
    SOURCE_TARGETS documentviewer abstractviewer
    TS_FILE_BASE docviewer
    MERGE_QT_TRANSLATIONS
    QT_TRANSLATION_CATALOGS qtbase
)

Jede Plugin-Ebene CMakeLists.txt ruft qt_add_translations nur auf den Quelldateien des jeweiligen Plugins auf (SOURCE_TARGETS). Das Scoping der Übersetzungsdateien auf das Plugin-Ziel verhindert ein erneutes Scannen und Übersetzen der Quellen der Hauptanwendung und anderer Plugins:

qt_add_translations(txtviewer
    SOURCE_TARGETS txtviewer
    TS_FILE_BASE txtviewer
)
Translator-Klasse

Die Klasse Translator ist ein Wrapper um Qt's QTranslator, der die Internationalisierung sowohl für die Hauptanwendung als auch für jedes Plugin verwaltet. Jede Komponente (Hauptanwendung und Plugins) hat ihre eigene Translator-Instanz, die eine koordinierte Sprachumschaltung für die gesamte Anwendung ermöglicht. Beim Starten oder wenn der Benutzer eine neue Sprache auswählt, wird Translator::install() aufgerufen. Diese Methode verwendet QTranslator::load(), um Übersetzungsdateien auf der Grundlage von QLocale::uiLanguages() und dem Basisnamen im Qt-Ressourcensystem zu laden. Wenn keine passende Übersetzung gefunden wird, wird auf Englisch zurückgegriffen.

void Translator::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::Englisch) {            qWarning() << "Failed to load translation" << m_baseName <<
                   "für Gebietsschema"<< m_trLocale.name()<< ". Falling back to English translation"; setLanguage(QLocale::Englisch); } } }
Plugin-Unterstützung

Die Basisklasse AbstractViewer bietet drei Methoden, damit jedes Plugin seine eigene Übersetzung verwalten kann:

  • AbstractViewer::setTranslationBaseName(): initialisiert ein Translator Objekt, setzt seinen Basisnamen und installiert es, um die Standardübersetzungen zu laden.
    void AbstractViewer::setTranslationBaseName(const QString &baseName)
    {
        m_translator.reset(new Translator);
        m_translator->setBaseName(baseName);
        m_translator->install();
    }
  • AbstractViewer::updateTranslation(): ruft install() auf dem bestehenden Translator auf, um die neuen Übersetzungen zu installieren, und ruft dann retranslate() auf, um den gesamten Text zu aktualisieren.
    void AbstractViewer::updateTranslation(QLocale::Language lang)
    {
        if (m_translator) {
            m_translator->setLanguage(lang);
            m_translator->install();
            retranslate();
        }
    }
  • AbstractViewer::retranslate()Translator: eine virtuelle Methode, die jedes Plugin implementiert, um seine eigenen UI-Texte neu zu übersetzen. Zum Beispiel, wie in ImageViewer neu implementiert:
    void ImageViewer::retranslate()
    {
        m_toolBar->setWindowTitle(tr("Images"));
        m_zoomInAct->setText(tr("Zoom &In"));
        m_zoomOutAct->setText(tr("Zoom &Out"));
        m_resetZoomAct->setText(tr("Reset Zoom"));
    }
Start der Anwendung
  • Hauptanwendung: in main.cpp laden wir die Übersetzung der Anwendung, bevor wir das Fenster anzeigen:
        Translator mainTranslator;
        mainTranslator.setBaseName("docviewer"_L1);
        mainTranslator.install();
  • Plugins: jedes Plugin ruft AbstractViewer::setTranslationBaseName() in seiner Funktion init() auf, um einen Translator mit dem Namen seiner Übersetzungsdatei zu initialisieren und die Übersetzungen der aktuellen Sprache zu installieren.
    void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
        ...
        setTranslationBaseName("imgviewer"_L1);
        ...
Sprachumschaltung zur Laufzeit

Der Wechsel der Laufzeitsprache kann auf zwei Arten erfolgen:

  1. Über das Menü Help > Language: Ein Klick auf den Eintrag QMenu löst MainWindow::onActionSwitchLanguage() aus, wodurch die neue Sprache installiert und die Hauptanwendung sowie die Plugins neu übersetzt werden:
    void MainWindow::onActionSwitchLanguage(QLocale::Language lang)
    {
        m_translator.setLanguage(lang);
        m_translator.install();
        ui->retranslateUi(this);
        const auto viewerList = m_factory->viewers();
        for (AbstractViewer *viewer : viewerList)
            viewer->updateTranslation(lang);
        statusBar()->clearMessage();
    }
  2. Umschaltung der Sprache des gesamten Systems zur Laufzeit: Die Anwendung kann darauf reagieren, indem sie auf die Ereignisse hört und MainWindow::onActionSwitchLanguage() bei dem Ereignis QEvent::LocaleChange aufruft.
    void MainWindow::changeEvent(QEvent *event)
    {
        if (event->type() == QEvent::LocaleChange)
            onActionSwitchLanguage(QLocale::system().language());
    
        QMainWindow::changeEvent(event);
    }

Quelldateien

Beispielprojekt @ code.qt.io

Siehe auch Alle Qt-Beispiele.

© 2025 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.