Qt Widgets - 文本查看器插件示例

包含菜单、工具栏和状态栏的小部件示例。

文本查看器示例是一个以QPlainTextEdit为核心的文本编辑器,以一般用途的文档查看器插件的形式存在。

文本查看器示例的所有代码都在继承自AbstractViewerTxtViewer类中。 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_METADATAQ_INTERFACES

类继承自ViewerInterface,它继承自AbstractViewerViewerInterface类用于在主窗口应用程序和插件之间提供接口。

QPluginLoader还要求包含包含插件密钥的txtviewer.json文件

{ "Keys": [ "txtviewer" ] }
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 QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
    void printDocument(QPrinter *printer) const override;
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT

private slots:
    void setupTxtUi();

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

    QPlainTextEdit *m_textEdit;
};

该类没有定义构造函数,这意味着只有不带参数的标准构造函数可用。其他所有函数,包括析构函数,都重新实现了ViewerInterface的虚拟函数。它们用于与主应用程序交换数据、信息和指令。

未实现保存和恢复设置的函数。函数supportsOverview始终返回false,这告诉主应用程序不需要显示用于缩略图导航的窗口。

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

QStringList TxtViewer::supportedMimeTypes() const
{
    return {"text/plain"_L1};
}

void TxtViewer::setupTxtUi()
{
    QMenu *editMenu = addMenu(tr("&Edit"));
    QToolBar *editToolBar = addToolBar(tr("Edit"));
#ifndef QT_NO_CLIPBOARD
    const QIcon cutIcon = QIcon::fromTheme("edit-cut"_L1,
                                           QIcon(":/demos/documentviewer/images/cut.png"_L1));
    QAction *cutAct = new QAction(cutIcon, tr("Cu&t"), this);
    cutAct->setShortcuts(QKeySequence::Cut);
    cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                            "clipboard"));
    connect(cutAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
    editMenu->addAction(cutAct);
    editToolBar->addAction(cutAct);

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

    const QIcon pasteIcon = QIcon::fromTheme("edit-paste"_L1,
                                             QIcon(":/demos/documentviewer/images/paste.png"_L1));
    QAction *pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
    pasteAct->setShortcuts(QKeySequence::Paste);
    pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                              "selection"));
    connect(pasteAct, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
    editMenu->addAction(pasteAct);
    editToolBar->addAction(pasteAct);

    menuBar()->addSeparator();

    cutAct->setEnabled(false);
    copyAct->setEnabled(false);
    connect(m_textEdit, &QPlainTextEdit::copyAvailable, cutAct, &QAction::setEnabled);
    connect(m_textEdit, &QPlainTextEdit::copyAvailable, copyAct, &QAction::setEnabled);
#endif // !QT_NO_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);
    });
}

我们首先包含访问所有使用TxtViewer的类的必需头文件。同时也包含txtviewer.h

如果编译系统启用了打印支持,则包含QPrinterQPrintDialog

您可能会想知道为什么我们不在mainwindow.h中包含这些头文件,直接完成它。原因是因为从一个头文件中包含多个大型头文件可能会迅速降低性能。在这里,这并没有造成任何伤害,但通常是一个好主意,只从另一个头文件中包含绝对必要的头文件。

实现从空析构函数开始。它可以完全省略。实现为一个空的析构函数是一种良好的实践,以便向代码读者指出在析构函数中无需执行任何操作。

析构函数之后是一个初始化函数,它接受三个参数

  • file,指向要打开和显示的文件的指针。
  • parent,指向编辑器将放置在内的QWidget
  • mainWindow,指向应用程序的主窗口,其中处理菜单和菜单栏。

该函数调用AbstractViwer的基类初始化函数。创建了一个新的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());
#ifndef QT_NO_CURSOR
    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
    if (!m_textEdit->toPlainText().isEmpty()) {
        m_textEdit->clear();
        disablePrinting();
    }
    m_textEdit->setPlainText(in.readAll());
#ifndef QT_NO_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 QT_DOCUMENTVIEWER_PRINTSUPPORT
void TxtViewer::printDocument(QPrinter *printer) const
{
    if (!hasContent())
        return;

    m_textEdit->print(printer);
}
#endif // QT_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();
}

下一个重新实现的函数向主应用程序报告查看器插件是否正在显示内容。

如果编译系统支持打印,下一个部分会实现它。

最后两个重新实现提供了保存当前文件或将文件以新名称保存的功能。

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。本文档的贡献是各自所有者版权的一部分。提供的文档是根据自由软件基金会发布的、版本1.3的GNU自由文档许可证条款授予的。Qt及其相关标志是芬兰及全世界其他国家的Qt公司有限公司的商标。所有其他商标均为其各自所有者的财产。