En esta página

Visor de documentos

Una aplicación Widgets para mostrar e imprimir archivos JSON, de texto y PDF.

La interfaz de usuario del visor de documentos muestra una ventana emergente "Seleccionar modo de apertura".

Document Viewer demuestra cómo utilizar un QMainWindow con barras de herramientas estáticas y dinámicas, menús y acciones. Además, demuestra las siguientes características en aplicaciones basadas en widgets:

  • Uso de QSettings para consultar y guardar las preferencias del usuario y gestionar el historial de archivos abiertos anteriormente.
  • Control del comportamiento del cursor al pasar por encima de los widgets.
  • Creación de plugins de carga dinámica.
  • Localización de la interfaz de usuario a diferentes idiomas.

Ejecución del ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.

Crear una aplicación y la ventana principal

La aplicación y su ventana principal se construyen en main.cpp. La función main() utiliza QCommandLineParser para procesar los argumentos de la línea de comandos - help, version, y un argumento posicional opcional, file. Si el usuario proporcionó una ruta a un archivo al iniciar la aplicación, la ventana principal lo abre:

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

Clase MainWindow

La clase MainWindow proporciona una pantalla de aplicación con menús, acciones y una barra de herramientas. Puede abrir un fichero, detectando automáticamente su tipo de contenido. También mantiene una lista de archivos abiertos anteriormente, utilizando QSettings para almacenar y recargar la configuración cuando se lanza. MainWindow crea un visor adecuado para el archivo abierto, basado en su tipo de contenido, y proporciona soporte para imprimir un documento.

El constructor de MainWindow inicializa la interfaz de usuario creada en Qt Designer. El archivo mainwindow.ui proporciona un QTabWidget a la izquierda, mostrando marcadores y miniaturas. A la derecha, hay un QScrollArea para ver el contenido del archivo.

Clase ViewerFactory

La clase ViewerFactory gestiona visores para tipos de archivo conocidos. Estos visores se implementan como plugins. Cuando se crea una instancia de ViewerFactory, los punteros al área de visualización y a la ventana principal se pasan al constructor:

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

ViewerFactory carga todos los plugins disponibles en la construcción. Proporciona una API pública para consultar los plugins cargados, sus nombres y los tipos MIME soportados:

    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;

La función viewer() devuelve un puntero al plugin adecuado para abrir el QFile pasado como argumento:

m_viewer = m_factory->viewer(file);

Si la configuración de la aplicación contiene una sección para el visor, se pasa a la función virtual restoreState() del visor:

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

A continuación, los activos de interfaz de usuario estándar se pasan al visor y el área de desplazamiento principal se configura para mostrar el widget de visualización del visor:

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

La clase AbstractViewer

AbstractViewer proporciona una API generalizada para ver, guardar e imprimir un documento. Se pueden consultar las propiedades tanto del documento como del visor:

  • ¿Tiene contenido el documento?
  • ¿Ha sido modificado?
  • ¿Se admite una vista general (miniaturas o marcadores)?

AbstractViewer proporciona métodos protegidos para que las clases derivadas creen acciones y menús en la ventana principal. Para poder mostrar estos activos en la ventana principal, deben estar vinculados a ella. AbstractViewer es responsable de eliminar y destruir los activos de interfaz de usuario que crea. Hereda de QObject para implementar señales y ranuras.

Señales

void uiInitialized();

Esta señal se emite después de que un visor reciba toda la información necesaria sobre los activos de interfaz en la ventana principal.

void printingEnabledChanged(bool enabled);

Esta señal se emite cuando se activa o desactiva la impresión de documentos. Esto ocurre después de que se haya cargado correctamente un nuevo documento o, por ejemplo, se haya eliminado todo el contenido.

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

Esta señal se emite para mostrar un mensaje de estado al usuario.

void documentLoaded(const QString &fileName);

Esta señal notifica a la aplicación que un documento se ha cargado correctamente.

La clase TxtViewer

TxtViewer es un visor de texto simple, que hereda de AbstractViewer. Permite editar ficheros de texto, copiar/cortar y pegar, imprimir y guardar cambios.

Definición de la clase
class TxtViewer : public ViewerInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "txtviewer.json")
    Q_INTERFACES(ViewerInterface)

La definición de la clase comienza con la macro Q_OBJECT, que maneja señales y ranuras. Le siguen las macros Q_PLUGIN_METADATA y Q_INTERFACES, necesarias para registrar el complemento.

La clase hereda de ViewerInterface, que a su vez hereda de AbstractViewer. La clase ViewerInterface se utiliza para proporcionar una interfaz entre la aplicación de ventana principal y el plugin.

QPluginLoader también requiere el archivo txtviewer.json, que debe contener la clave del plugin:

{ "Keys": [ "txtviewer" ] }

La clase no define ningún constructor, lo que significa que sólo está disponible un constructor estándar sin argumentos. Todas las demás funciones, incluido el destructor, reimplementan funciones virtuales de ViewerInterface. Se utilizan para intercambiar datos, información e instrucciones con la aplicación principal.

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; }
    void retranslate() override;

#ifdef DOCUMENTVIEWER_PRINTSUPPORT
protected:
    void printDocument(QPrinter *printer) const override;
#endif // DOCUMENTVIEWER_PRINTSUPPORT

private slots:
    void setupTxtUi();

private:
    void openFile();
    bool saveFile (QFile *file);

    QPlainTextEdit *m_textEdit;
    QMenu *m_editMenu = nullptr;
    QToolBar *m_editToolBar = nullptr;
    QAction *m_cutAct = nullptr;
    QAction *m_copyAct = nullptr;
    QAction *m_pasteAct = nullptr;
};
Implementación de la clase 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()
{
    connect(this, &AbstractViewer::uiInitialized, this, &TxtViewer::setupTxtUi);
}

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()
{
    m_editMenu = addMenu(tr("&Edit"));
    m_editToolBar = addToolBar(tr("Edit"));
#if QT_CONFIG(clipboard)
    const QIcon cutIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCut,
                                           QIcon(":/demos/documentviewer/images/cut.png"_L1));
    m_cutAct = new QAction(cutIcon, tr("Cu&t"), this);
    m_cutAct->setShortcuts(QKeySequence::Cut);
    m_cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                            "clipboard"));
    connect(m_cutAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
    m_editMenu->addAction(m_cutAct);
    m_editToolBar->addAction(m_cutAct);

    const QIcon copyIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditCopy,
                                            QIcon(":/demos/documentviewer/images/copy.png"_L1));
    m_copyAct = new QAction(copyIcon, tr("&Copy"), this);
    m_copyAct->setShortcuts(QKeySequence::Copy);
    m_copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                             "clipboard"));
    connect(m_copyAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::copy);
    m_editMenu->addAction(m_copyAct);
    m_editToolBar->addAction(m_copyAct);

    const QIcon pasteIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditPaste,
                                             QIcon(":/demos/documentviewer/images/paste.png"_L1));
    m_pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
    m_pasteAct->setShortcuts(QKeySequence::Paste);
    m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                              "selection"));
    connect(m_pasteAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
    m_editMenu->addAction(m_pasteAct);
    m_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);
    });
}

Comenzamos incluyendo los archivos de cabecera necesarios para acceder a todas las clases utilizadas por TxtViewer. También incluimos txtviewer.h.

QPrinter y QPrintDialog sólo se incluyen si el soporte de impresión está activado en el sistema de compilación.

Tenga en cuenta que estas cabeceras no se incluyen directamente en mainwindow.h. Incluir cabeceras grandes en otros archivos de cabecera puede afectar al rendimiento de la compilación. En este caso, no causaría problemas, pero es una buena práctica incluir sólo las cabeceras necesarias para mantener las dependencias al mínimo.

La implementación comienza con un destructor vacío. Podría omitirse por completo. Es una buena práctica implementarlo vacío para indicar a los lectores de código que no es necesario hacer nada en el destructor.

El destructor es seguido por una función de inicialización, tomando tres argumentos:

  • file, el puntero al archivo que se abrirá y mostrará.
  • parent, apuntando al QWidget dentro del cual se colocará el editor.
  • mainWindow, que apunta a la ventana principal de la aplicación, donde se manejan los menús y las barras de menú.

La función llama a la función base init de AbstractViewer. Se crea un nuevo widget QPlainTextEdit, que mostrará el contenido del fichero. A continuación, la función setup de TxtViewer se conecta a la señal uiInitialized de la clase base.

La siguiente función devuelve la lista de tipos mime que soporta el visor de texto. Sólo admite texto plano.

La última función de inicialización añade componentes de interfaz de usuario específicos del visor, como menús, iconos, botones e información sobre herramientas. Utiliza la funcionalidad proporcionada por AbstractViewer para asegurarse de que estos componentes se eliminan de la ventana principal de la aplicación, una vez que se muestra otro archivo con otro plugin de visor.

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 abre un archivo, transfiere su contenido a QPlainTextEdit, e imprime un mensaje de estado para el usuario, dependiendo de si la apertura ha tenido éxito o no.

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 (m_editMenu)
        m_editMenu->setTitle(tr("&Edit"));
    if (m_editToolBar)
        m_editToolBar->setWindowTitle(tr("Edit"));
    if (m_cutAct) {
        m_cutAct->setText(tr("Cu&t"));
        m_cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                                "clipboard"));
    }
    if (m_copyAct) {
        m_copyAct->setText(tr("&Copy"));
        m_copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                                 "clipboard"));
    }
    if (m_pasteAct) {
        m_pasteAct->setText(tr("&Paste"));
        m_pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                                  "selection"));
    }
}

La siguiente función reimplementada indica a la aplicación principal si el complemento de visualización está o no mostrando el contenido.

Si la impresión está soportada en el sistema de compilación, la siguiente sección la implementa.

Las dos últimas reimplementaciones proporcionan funcionalidad para guardar el archivo actual o guardarlo con un nuevo nombre.

Clase ImageViewer

ImageViewer muestra imágenes tal y como soporta QImageReader, utilizando un QLabel.

En el constructor, aumentamos el límite de asignación de QImageReader para permitir fotos más grandes:

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

En la función openFile(), cargamos la imagen y determinamos su tamaño. Si es mayor que la pantalla, la reducimos al tamaño de la pantalla, manteniendo la relación de aspecto. Este cálculo tiene que hacerse en píxeles nativos, y la relación de píxeles del dispositivo tiene que establecerse en el mapa de píxeles resultante para que aparezca nítido:

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

Clase JsonViewer

JsonViewer muestra un archivo JSON en QTreeView. Internamente, carga el contenido de un archivo en QJsonDocument y lo utiliza para rellenar un modelo de árbol personalizado con JsonItemModel.

El complemento del visor JSON muestra cómo implementar un modelo de elementos personalizado heredado de QAbstractItemModel. La clase JsonTreeItem proporciona una API básica para manipular datos JSON y propagarlos de vuelta al QJsonDocument subyacente.

JsonViewer utiliza los objetos de nivel superior del documento como marcadores para la navegación. Otros nodos (claves y valores) pueden ser añadidos como marcadores adicionales, o eliminados de la lista de marcadores.

Clase PdfViewer

La clase PdfViewer (y el plugin) es un fork del PDF Viewer Widget Example. Demuestra el uso de QScroller para desplazarse suavemente por un documento.

Otras clases relevantes

Clase HoverWatcher

La clase HoverWatcher establece un cursor de anulación cuando se pasa el ratón por encima de un widget, restaurándolo al salir. Para evitar que se creen múltiples instancias de HoverWatcher para el mismo widget, se implementa como un singleton por widget.

HoverWatcher hereda de QObject y toma el QWidget que vigila como padre de la instancia. Instala un filtro de eventos para interceptar los eventos hover sin consumirlos:

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

El enum HoverAction lista las acciones a las que HoverWatcher reacciona:

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

Las funciones estáticas crean watchers, comprueban su existencia para un QWidget específico, o descartan un watcher:

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

Para cada HoverAction puede establecerse o desestablecerse una forma de cursor. Si no hay una forma de cursor asociada, el cursor de la aplicación se restaura cuando se activa la acción.

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

La propiedad mouseButtons contiene los botones del ratón a considerar para una acción MousePress:

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

Las señales específicas de la acción se emiten después de procesar una acción:

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

Se emite una señal general que pasa la acción procesada como argumento:

void hoverAction(HoverAction action);
La clase RecentFiles

RecentFiles es una QStringList especializada en gestionar una lista de archivos abiertos recientemente.

RecentFiles tiene ranuras para añadir un único archivo o varios archivos de una sola vez. Se añade una entrada a la lista de archivos recientes si la ruta apunta a un archivo que existe y puede abrirse. Si un archivo ya está en la lista, se elimina de su posición original y se añade al principio.

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

Los ficheros se eliminan de la lista por nombre o por índice:

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

Ranuras que permiten guardar y restaurar desde QSettings:

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

Al restaurar la configuración, se ignoran los archivos inexistentes. La propiedad maxFiles contiene la cantidad máxima de archivos recientes a almacenar (por defecto es 10).

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

RecentFiles verifica que un archivo puede ser leído antes de aceptarlo.

La clase RecentFileMenu

RecentFileMenu es un QMenu, especializado para mostrar un objeto RecentFiles como un submenú.

Su constructor toma un puntero a un padre QObject y un puntero a un objeto RecentFiles, cuyo contenido visualizará. Su señal fileOpened(), que se activa cuando el usuario selecciona un archivo reciente de la lista, pasa la ruta absoluta al archivo como argumento.

Nota: RecentFileMenu es destruido por su widget padre, o por el objeto RecentFiles pasado a su constructor.

class RecentFileMenu : public QMenu
{
    Q_OBJECT

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

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

Traducciones

La interfaz de usuario de la aplicación está disponible en inglés y alemán. El idioma por defecto es auto-seleccionado por Qt: Alemán si el idioma del sistema es el alemán; en caso contrario, Inglés. Además, el usuario puede cambiar el idioma en el menú Help > Language. Cada plugin, así como la aplicación principal, es responsable de cargar sus propias traducciones durante el tiempo de ejecución.

Integración con CMake

El nivel superior CMakeLists.txt declara los idiomas distribuidos.

qt_standard_project_setup(REQUIRES 6.8
    I18N_SOURCE_LANGUAGE en
    I18N_TRANSLATED_LANGUAGES de
)

El objetivo documentviewer define la aplicación principal. Almacena y carga las cadenas localizadas para el objetivo en los archivos docviewer_de.ts y docviewer_en.ts. Además, fusiona los respectivos qtbase translations proporcionados por Qt en los archivos de traducción generados, de modo que también los cuadros de diálogo de Qt, como el cuadro de diálogo de impresión, se traducen correctamente:

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

Cada nivel de plugin CMakeLists.txt invoca qt_add_translations sólo en los archivos fuente de ese plugin (SOURCE_TARGETS). Destinando los archivos de traducción al objetivo del plugin se evita volver a escanear y retraducir los fuentes de la aplicación principal y otros plugins:

qt_add_translations(txtviewer
    SOURCE_TARGETS txtviewer
    TS_FILE_BASE txtviewer
)
Clase traductor

La clase Translator es una envoltura alrededor de QTranslator de Qt que gestiona la internacionalización tanto para la aplicación principal como para cada plugin. Cada componente (aplicación principal y plugins) tiene su propia instancia de Translator, lo que permite el cambio coordinado de idioma en toda la aplicación. Al inicio o cuando el usuario selecciona un nuevo idioma, se llama a Translator::install(). Este método utiliza QTranslator::load() para cargar archivos de traducción basados en QLocale::uiLanguages() y el nombre base en el sistema de recursos Qt. Si no se encuentra ninguna traducción que coincida, se vuelve al inglés.

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::Español) {            qWarning() << "Failed to load translation" << m_baseName <<
                   "para la configuración regional"<< m_trLocale.name()<< ". Falling back to English translation"; setLanguage(QLocale::Español); } } }
Soporte de plugins

La clase base AbstractViewer proporciona tres métodos para que cada plugin pueda gestionar su propia traducción:

  • AbstractViewer::setTranslationBaseName(): inicializa un objeto Translator, establece su nombre base, y lo instala para cargar las traducciones por defecto.
    void AbstractViewer::setTranslationBaseName(const QString &baseName)
    {
        m_translator.reset(new Translator);
        m_translator->setBaseName(baseName);
        m_translator->install();
    }
  • AbstractViewer::updateTranslation(): llama a install() en el Traductor existente para instalar las nuevas traducciones, y luego llama a retranslate() para refrescar todo el texto.
    void AbstractViewer::updateTranslation(QLocale::Language lang)
    {
        if (m_translator) {
            m_translator->setLanguage(lang);
            m_translator->install();
            retranslate();
        }
    }
  • AbstractViewer::retranslate()Un método virtual: un método virtual que cada plugin implementa para retraducir sus propios textos de interfaz de usuario. Por ejemplo, como se reimplementa en ImageViewer:
    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"));
    }
Inicio de la aplicación
  • Aplicación principal: en main.cpp cargamos la traducción de la aplicación antes de mostrar la ventana:
        Translator mainTranslator;
        mainTranslator.setBaseName("docviewer"_L1);
        mainTranslator.install();
  • Plugins: cada plugin llama a AbstractViewer::setTranslationBaseName() en su función init() para inicializar un Translator con su nombre de archivo de traducción e instalar las traducciones del idioma actual.
    void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
        ...
        setTranslationBaseName("imgviewer"_L1);
        ...
Cambio de idioma en tiempo de ejecución

El cambio de idioma en tiempo de ejecución puede realizarse de dos formas:

  1. Utilizando el menú Help > Language: Al hacer clic en el elemento QMenu se activa MainWindow::onActionSwitchLanguage(), que instalará el nuevo idioma y volverá a traducir la aplicación principal y los plugins:
    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. Cambiando el idioma de todo el sistema en tiempo de ejecución: La aplicación puede reaccionar a esto escuchando los eventos y llamando a MainWindow::onActionSwitchLanguage() en el evento QEvent::LocaleChange.
    void MainWindow::changeEvent(QEvent *event)
    {
        if (event->type() == QEvent::LocaleChange)
            onActionSwitchLanguage(QLocale::system().language());
    
        QMainWindow::changeEvent(event);
    }

Archivos fuente

Proyecto de ejemplo @ code.qt.io

Ver también Todos los ejemplos de 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.