Qt例子学习笔记 - Examples/Qt-6.2.0/assistant/simpletextviewer

71 篇文章 4 订阅

main.cpp

#include "mainwindow.h"

#include <QApplication>
//QApplication 专门为 QGuiApplication 提供了一些基于 QWidget 的应用程序所需的功能。
// 它处理特定于小部件的初始化、完成。
//对于任何使用 Qt 的 GUI 应用程序,无论应用程序在任何给定时间是否有 0、1、2 或更多窗口,
//都恰好有一个 QApplication 对象。
//对于非基于 QWidget 的 Qt 应用程序,请改用 QGuiApplication,因为它不依赖于 QtWidgets 库。
//一些 GUI 应用程序提供了一种特殊的批处理模式,即。
//提供命令行参数,无需人工干预即可执行任务。
//在这种非 GUI 模式下,实例化一个普通的 QCoreApplication 通常就足够了,
//以避免不必要地初始化图形用户界面所需的资源。
//以下示例显示了如何动态创建适当类型的应用程序实例:
/*
QCoreApplication* createApplication(int &argc,char *argv[])
{
    for(int i = 1;i < argc; ++i)
    {
        if(!qstrcmp(argv[i],"-no-gui"))
            return new QCoreApplication(argc,argv);
    }
    return new QApplication(argc,argv);
}

int main(int argc,char *argv[])
{
    QScopedPointer<QCoreApplication> app(createApplication(argc,argv));

    if(qobject_cast<QApplication*>(app.data()))
    {
        //start GUI version
    }
    else
    {
        //start non-GUI version
    }
    return app->exec();
}
*/
//QApplication 对象可通过 instance() 函数访问,该函数返回等效于全局 qApp 指针的指针。
//QApplication 的主要职责范围是:
//它使用用户的桌面设置初始化应用程序,例如palette()、font() 和doubleClickInterval()。
//它会跟踪这些属性,以防用户全局更改桌面,例如通过某种控制面板。
//它执行事件处理,这意味着它从底层窗口系统接收事件并将它们分派给相关的小部件。
//通过使用 sendEvent() 和 postEvent(),您可以将自己的事件发送到小部件。
//它解析常见的命令行参数并相应地设置其内部状态。 有关更多详细信息,请参阅下面的构造函数文档。
//它定义了应用程序的外观和感觉,它被封装在一个 QStyle 对象中。
//这可以在运行时使用 setStyle() 进行更改。
//它通过 translate() 提供对用户可见的字符串的本地化。
//它提供了一些神奇的对象,比如 clipboard()。
//它知道应用程序的窗口。您可以使用 widgetAt() 询问哪个小部件位于某个位置,
//获取 topLevelWidgets() 和 closeAllWindows() 等列表。
//它管理应用程序的鼠标光标处理,参见 setOverrideCursor()
//由于 QApplication 对象进行了如此多的初始化,因此必须在创建与用户界面相关的任何其他对象之前创建它。
//QApplication 还处理常见的命令行参数。
//因此,在应用程序本身对 argv 进行任何解释或修改之前创建它通常是一个好主意。
//Groups of functions
//系统设置
/*
desktopSettingsAware(), 
setDesktopSettingsAware(), 
cursorFlashTime(), 
setCursorFlashTime(), 
doubleClickInterval(), 
setDoubleClickInterval(), 
setKeyboardInputInterval(), 
wheelScrollLines(), 
setWheelScrollLines(), 
palette(), setPalette(), 
font(), setFont(), fontMetrics().
*/
//事件处理
/*
exec(), 
processEvents(), 
exit(), 
quit(). 
sendEvent(), 
postEvent(), 
sendPostedEvents(), 
removePostedEvents(), notify().
*/

//GUI styles
/*
style(), setStyle().
*/

//文本处理
/*
installTranslator(), removeTranslator() translate().
*/
//Widgets

/*
allWidgets(), 
topLevelWidgets(), 
activePopupWidget(), 
activeModalWidget(), 
clipboard(), 
focusWidget(), 
activeWindow(), 
widgetAt().
*/

//Advanced cursor handling
/*
overrideCursor(), setOverrideCursor(), restoreOverrideCursor().
*/

//这个例子展示了如何在自定义应用程序中使用 Qt Assistant 作为自定义帮助查看器。
//这是分两个阶段完成的。
//首先创建文档,定制Qt助手;
//其次,在应用程序中添加了启动和控制 Qt 助手的功能。
//该示例由四个类组成:
//Assistant 提供了启动 Qt Assistant 的功能。
//MainWindow 是主应用程序窗口。
//FindFileDialog 允许用户使用通配符匹配来搜索文件。
//TextEdit 提供了一个富文本浏览器,可确保 HTML 文档中引用的图像正确显示。
//注意:我们只会评论与主要问题相关的实现部分,即让 Qt Assistant 充当我们的 Simple Text Viewer 应用程序的自定义帮助查看器。
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow window;
    window.show();
    return app.exec();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

//在应用程序中,可以通过菜单、工具栏按钮和键盘快捷键调用许多常用命令。
//由于用户希望每个命令都以相同的方式执行,而不管使用的用户界面如何,将每个命令表示为一个动作是很有用的。
//操作可以添加到菜单和工具栏,并自动保持同步。
//例如,在文字处理器中,如果用户按下粗体工具栏按钮,粗体菜单项将被自动选中。
//QAction 可能包含图标、菜单文本、快捷方式、状态文本、“这是什么?” 文本和工具提示。
//其中大部分可以在构造函数中设置。 它们也可以通过 
//setIcon()、setText()、setIconText()、setShortcut()、
//setStatusTip()、setWhatsThis() 和 setToolTip() 独立设置。 
//对于菜单项,可以使用 setFont() 设置单独的字体。

//我们建议将操作创建为使用它们的窗口的子项。在大多数情况下,操作将是应用程序主窗口的子项。

//小部件应用程序中的 QAction
//创建 QAction 后,应将其添加到相关菜单和工具栏,然后连接到将执行操作的槽。 例如:
/*
    const QIcon openIcon = QIcon::fromTheme("document-open",QIcon(":/images/open.png"))
    QAction *openAct = new QAction(openIcon,tr("&Open..."),this);
    openAct->setShortcuts(QKeySequence::Open);
    openAct->setStatusTip(tr("Open an existing file"));
    connect(openAct,&QAction::triggered,this,&MainWindow::open);
    fileMenu->addAction(openAct);
    fileToolBar->addAction(openAct);
*/
//使用 QWidget::addAction() 或 QGraphicsWidget::addAction() 将Action添加到小部件。
//。 请注意,操作必须先添加到小部件才能使用。
//当快捷方式应该是全局的(即 Qt::ApplicationShortcut 作为 Qt::ShortcutContext)时,这也是正确的。
//动作可以创建为独立的对象。
//但它们也可能是在构建菜单期间创建的。
//QMenu 类包含用于创建适合用作菜单项的操作的函数。

//菜单小部件是一个选择菜单。
//它可以是菜单栏中的下拉菜单,也可以是独立的上下文菜单。
//当用户单击相应的项目或按下指定的快捷键时,菜单栏会显示下拉菜单。
//使用 QMenuBar::addMenu() 将菜单插入菜单栏中
//上下文菜单通常由一些特殊的键盘键或右键单击来调用。
//它们可以与 popup() 异步执行,也可以与 exec() 同步执行。
//菜单也可以响应按钮按下而被调用; 这些就像上下文菜单,只是它们的调用方式不同。

//Actions
//菜单由一系列操作项组成。使用 addAction()、addActions() 和 insertAction() 函数添加动作。
//一个动作垂直表示并由 QStyle 呈现。
//此外,动作可以有一个文本标签,一个在最左侧绘制的可选图标,以及诸如“Ctrl+X”之类的快捷键序列。
//可以使用 actions() 找到菜单所包含的现有操作。
//有四种操作项:分隔符、显示子菜单的操作、小部件和执行操作的操作。
//分隔符使用 addSeparator() 插入,子菜单使用 addMenu() 插入,所有其他项都被视为操作项。
//插入操作项时,您通常指定接收器和插槽。
//每当项目被触发()时,接收者都会收到通知。
//此外,QMenu 提供了两个信号,triggered() 和 hovered(),它们表示从菜单触发的 QAction。
//您可以使用 clear() 清除菜单并使用 removeAction() 删除单个操作项。
//QMenu 还可以提供 tear-off 菜单。  tear-off 菜单是包含菜单副本的顶级窗口。
//这使得用户可以“tear off”经常使用的菜单并将它们放置在屏幕上方便的位置。
//如果您想要特定菜单的此功能,请使用 setTearOffEnabled() 插入tear-off的句柄。
//可以使用 QWidgetAction 类将小部件插入菜单中。
//此类的实例用于保存小部件,并通过采用 QAction 的 addAction() 重载插入菜单。
//如果 QWidgetAction 触发了 trigger() 信号,菜单将关闭。


QT_BEGIN_NAMESPACE
class QAction;
class QMenu;
QT_END_NAMESPACE

class Assistant;
class TextEdit;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

private slots:
    void about();
    void showDocumentation();
    void open();

protected:
    void closeEvent(QCloseEvent *event) override;

private:
    void createActions();
    void createMenus();

    Assistant *assistant;
    TextEdit *textViewer;

    QMenu *fileMenu;
    QMenu *helpMenu;

    QAction *assistantAct;
    QAction *clearAct;
    QAction *openAct;
    QAction *exitAct;
    QAction *aboutAct;
    QAction *aboutQtAct;
};

#endif

mainwindow.cpp

#include "assistant.h"
#include "findfiledialog.h"
#include "mainwindow.h"
#include "textedit.h"

#include <QAction>
#include <QApplication>
#include <QLibraryInfo>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>

//MainWindow 类为应用程序主窗口提供两个菜单:
//文件菜单让用户打开和查看现有文件,
//而帮助菜单提供有关应用程序和 Qt 的信息,
//并让用户打开 Qt 助手以显示应用程序的文档 .
//为了能够访问帮助功能,我们在 MainWindow 的构造函数中初始化 Assistant 对象。
MainWindow::MainWindow()
{
    assistant = new Assistant;
// ![0]
    textViewer = new TextEdit;
    //[static, since 6.0] QString QLibraryInfo::path(QLibraryInfo::LibraryPath p)
    //返回由 p 指定的路径。    
    //QLibraryInfo::ExamplesPath 安装时示例的路径。
    //QLatin1String 许多 QString 的成员函数被重载以接受 const char * 而不是 QString。
    //这包括复制构造函数、赋值运算符、比较运算符以及各种其他函数,
    //例如 insert()、replace() 和 indexOf()。
    //这些函数通常经过优化,以避免为 const char * 数据构造 QString 对象。
    //例如,假设 str 是一个 QString,
    /*
        if(str == "auto" || str == "extern"
            ||str == "static"||str == "register")
            {
                ...
            }
            //is much faster than
        if(str == QString("auto") || str == QString("extern")
            ||str == QString("static") || str == QString("register"))
        {
    
        }
    */
    //因为它没有构造四个临时 QString 对象并对字符数据进行深拷贝。
    //定义 QT_NO_CAST_FROM_ASCII 的应用程序(如 QString 文档中所述)无权访问 QString 的 const char * API。
    //为了提供一种指定常量 Latin-1 字符串的有效方法,
    //Qt 提供了 QLatin1String,它只是一个非常薄的 const char * 包装器。
    //使用QLatin1String,上面的示例代码变成
    /*
        if(str == QLatin1String("auto")
            ||str ==  QLatin1String("extern")
            ||str ==  QLatin1String("static")
            ||str ==  QLatin1String("register"))
        {}
    */
    //这输入的时间有点长,但它提供了与代码的第一个版本完全相同的好处,
    //并且比使用 QString::fromLatin1() 转换 Latin-1 字符串更快。
    //多亏了 QString(QLatin1String) 构造函数,
    //QLatin1String 可以在任何需要 QString 的地方使用。 例如:
    //QLabel *label = new QLabel(QLatin1String("MOD"),this);
    //注意:如果您使用 QLatin1String 参数调用的函数实际上并未重载以采用 QLatin1String
    //则隐式转换为 QString 将触发内存分配,这通常是您首先要使用 QLatin1String 避免的情况。
    //在这些情况下,使用 QStringLiteral 可能是更好的选择。


    textViewer->setContents(QLibraryInfo::path(QLibraryInfo::ExamplesPath)
            + QLatin1String("/assistant/simpletextviewer/documentation/intro.html"));
    //void QMainWindow::setCentralWidget(QWidget *widget)
    //将给定的小部件设置为主窗口的中央小部件。
    //注意: QMainWindow 获得小部件指针的所有权并在适当的时候将其删除。
    setCentralWidget(textViewer);

    createActions();
    createMenus();
    //此属性保存窗口标题(标题)
    //此属性仅对顶级小部件有意义,例如窗口和对话框。
    //如果未设置标题,则标题基于 windowFilePath。
    //如果这些都没有设置,则标题是一个空字符串。
    setWindowTitle(tr("Simple Text Viewer"));
    resize(750, 400);
// ![1]
}
//! [1]

//最后,我们必须重新实现受保护的 QWidget::closeEvent() 事件处理程序,
//以确保在终止应用程序之前正确关闭应用程序的 Qt Assistant 实例。
void MainWindow::closeEvent(QCloseEvent *)
{
    delete assistant;
}

void MainWindow::about()
{
    QMessageBox::about(this, tr("About Simple Text Viewer"),
                       tr("This example demonstrates how to use\n"
                          "Qt Assistant as help system for your\n"
                          "own application."));
}

//在 showDocumentation() 槽中,我们使用文档主页的 
//URL 调用 Assistant 类的 showDocumentation() 函数。
void MainWindow::showDocumentation()
{
    assistant->showDocumentation("index.html");
}

void MainWindow::open()
{
    FindFileDialog dialog(textViewer, assistant);
    dialog.exec();
}

//然后我们为 Simple Text Viewer 应用程序创建所有操作。
//特别感兴趣的是可以通过 F1 快捷方式或帮助 > 帮助目录菜单项访问的assistantAct 操作。
//此操作连接到 MainWindow 类的 showDocumentation() 槽。
void MainWindow::createActions()
{
    assistantAct = new QAction(tr("Help Contents"), this);
    assistantAct->setShortcut(QKeySequence::HelpContents);
    connect(assistantAct, &QAction::triggered, this, &MainWindow::showDocumentation);

    openAct = new QAction(tr("&Open..."), this);
    openAct->setShortcut(QKeySequence::Open);
    connect(openAct, &QAction::triggered, this, &MainWindow::open);

    clearAct = new QAction(tr("&Clear"), this);
    clearAct->setShortcut(tr("Ctrl+C"));
    connect(clearAct, &QAction::triggered, textViewer, &QTextEdit::clear);

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcuts(QKeySequence::Quit);
    connect(exitAct, &QAction::triggered, this, &QWidget::close);

    aboutAct = new QAction(tr("&About"), this);
    connect(aboutAct, &QAction::triggered, this, &MainWindow::about);

    aboutQtAct = new QAction(tr("About &Qt"), this);
    connect(aboutQtAct, &QAction::triggered, QApplication::aboutQt);
//! [5]
}
//! [5]

void MainWindow::createMenus()
{
    fileMenu = new QMenu(tr("&File"), this);
    fileMenu->addAction(openAct);
    fileMenu->addAction(clearAct);
    fileMenu->addSeparator();
    fileMenu->addAction(exitAct);

    helpMenu = new QMenu(tr("&Help"), this);
    helpMenu->addAction(assistantAct);
    helpMenu->addSeparator();
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);


    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(helpMenu);
}

textedit.h

#ifndef TEXTEDIT_H
#define TEXTEDIT_H

#include <QTextEdit>
#include <QUrl>

class TextEdit : public QTextEdit
{
    Q_OBJECT

public:
    TextEdit(QWidget *parent = nullptr);
    void setContents(const QString &fileName);

private:
    //QVariant 由于 C++ 禁止联合包含具有非默认构造函数或析构函数的类型,
    //因此大多数有趣的 Qt 类不能在unions中使用。
    //如果没有 QVariant,这对于 QObject::property() 和数据库工作等来说将是一个问题。
    //QVariant 对象一次保存一个 typeId() 的单个值。 
    //(某些类型是多值的,例如字符串列表。)
    //您可以找出变体包含的类型 T,
    //使用 convert() 将其转换为其他类型,
    //使用 toT() 函数之一获取其值 (例如,toSize()),
    //并检查是否可以使用 canConvert() 将类型转换为特定类型。
    //名为 toT() 的方法(例如,toInt()、toString())是常量。
    //如果您要求存储类型,它们将返回存储对象的副本。
    //如果您要求可以从存储的类型生成的类型, toT() 会复制并转换对象本身并保持不变。
    //如果您要求的类型无法从存储的类型中生成,则结果取决于类型; 有关详细信息,请参阅函数文档。
    //下面是一些示例代码来演示 QVariant 的使用:
    /*
        QDataStream out(...);
        QVariant v(123);  变体现在包含一个 int
        int x = v.toInt(); //x = 123
        out<<v; // 写一个类型标签和一个 int 到 out
        v = QVariant(tr("hello")); // 变体现在包含一个 QString
        int y = v.toInt();  // y = 0 since v cannot be converted to an int
        QString s = v.toString();  // s = tr("hello")  (see QObject::tr()
        out<<v; // 写一个类型标签和一个 QString 到 out
        ///
        QDataString in(...); //(opening the previouly written stream)  
        in >> v;  读取一个 Int 变量
        int z = v.toInt(); //z = 123
        qDebug("Type is %s",v.typeName());
        v = v.toInt() + 100; //The Variant now holds the value 223
        v = QVariant(QStringList()); //The variant now holds a QStrringList
    */
    //您甚至可以将 QList<QVariant> 和 QMap<QString, QVariant> 值存储在一个变体中,
    //,因此您可以轻松构建任意类型的任意复杂的数据结构。
    //这是非常强大和通用的,但可能比在标准数据结构中存储特定类型的内存和速度效率低。
    //QVariant 还支持空值的概念。
    //如果变体不包含初始化值或包含空指针,则变体为空。
    /*
        QVariant x; // x.isNull() == true
        QVariant y = QVariant::fromValue(nullptr); // y.isNull() == true
       //QVariant 可以扩展以支持 QMetaType::Type 枚举中提到的类型之外的其他类型。 有关详细信息,请参阅创建自定义 Qt 类型。 
    */
    //关于 GUI 类型的说明
    //由于 QVariant 是 Qt Core 模块的一部分,
    //因此无法提供 Qt GUI 中定义的数据类型的转换功能,
    //例如 QColor、QImage 和 QPixmap。
    //换句话说,没有 toColor() 函数。
    //相反,您可以使用 QVariant::value() 或 qvariant_cast() 模板函数。 例如:
    /*
        QVariant variant;
        QColor color = variant.value<QColor>();
    */

    //对于 QVariant 支持的所有数据类型,包括 GUI 相关类型,逆转换(例如,从 QColor 到 QVariant)是自动的:
    /*
        QColor color = palette().backgroud().color();
        QVariant variant = color;
    */

    QVariant loadResource(int type, const QUrl &name) override;
    QUrl srcUrl;
};

#endif

textedit.cpp

#include "textedit.h"

#include <QFile>
#include <QFileInfo>
//QTextEdit 是一个先进的 WYSIWYG 查看器/编辑器,支持使用 HTML 样式标签或 
//Markdown 格式的富文本格式。 它经过优化,可处理大型文档并快速响应用户输入。
//QTextEdit 适用于段落和字符。
//一个段落是一个格式化的字符串,它被自动换行以适应小部件的宽度。
//默认情况下,阅读纯文本时,换行符表示一个段落。
//一个文档由零个或多个段落组成。 段落中的单词按照段落的对齐方式对齐。
//段落由硬换行符分隔。 段落中的每个字符都有自己的属性,例如字体和颜色。


//QTextEdit 可以显示图像、列表和表格。
//如果文本太大而无法在文本编辑的视口中查看,则会出现滚动条。
//文本编辑可以加载纯文本和富文本文件。
//可以使用 HTML 4 标记的子集来描述富文本; 有关更多信息,请参阅支持的 HTML 子集页面。
//如果您只需要显示一小段富文本,请使用 QLabel。
//Qt 中的富文本支持旨在提供一种快速、可移植和
//高效的方式来向应用程序添加合理的在线帮助工具,并为富文本编辑器提供基础。
//如果您发现 HTML 支持不足以满足您的需求,
//您可以考虑使用 Qt WebKit,它提供了一个全功能的 Web 浏览器小部件。
//QTextEdit 上鼠标光标的形状默认为 Qt::IBeamCursor。
//它可以通过 viewport() 的 cursor 属性进行更改。
//使用 QTextEdit 作为显示小部件
//QTextEdit 可以显示一个大的 HTML 子集,包括表格和图像。
//可以使用 setHtml() 设置或替换文本,它会删除任何现有文本并将其替换为在 setHtml() 调用中传递的文本。
//如果您使用旧版 HTML 调用 setHtml(),然后调用 toHtml(),则返回的文本可能具有不同的标记,但会呈现相同的内容。
//可以使用 clear() 删除整个文本。
//也可以使用 setMarkdown() 设置或替换文本,同样的警告也适用:
//如果您随后调用 toMarkdown(),返回的文本可能会有所不同,
//但会尽可能保留其含义。
//可以解析带有一些嵌入 HTML 的 Markdown,具有与 setHtml() 相同的限制;
//但是 toMarkdown() 只写“纯”的 Markdown,没有任何嵌入的 HTML。
//可以使用 QTextCursor 类或使用便利函数 insertHtml()、
//insertPlainText()、append() 或 paste() 插入文本本身。
//QTextCursor 还能够将复杂的对象(如表格或列表)插入到文档中
//并处理创建选择并将更改应用于所选文本。
//默认情况下,文本编辑在空格处包装单词以适合文本编辑小部件。
//setLineWrapMode() 函数用于指定您想要的换行类型,如果您不想要任何换行,则使用 NoWrap。
//调用 setLineWrapMode() 以设置固定像素宽度 FixedPixelWidth 或字符列(例如 80 列)
//FixedColumnWidth 与 setLineWrapColumnOrWidth() 指定的像素或列。
//如果您对小部件的宽度 WidgetWidth 使用自动换行
//您可以使用 setWordWrapMode() 指定是在空白处还是在任何地方换行。
//find() 函数可用于在文本中查找和选择给定的字符串。
//如果您想限制 QTextEdit 中的段落总数
//例如它在日志查看器中通常很有用
//那么您可以使用 QTextDocument 的 maximumBlockCount 属性。
//只读键绑定
//当 QTextEdit 以只读方式使用时,键绑定仅限于导航,并且只能使用鼠标选择文本:
// UpMoves 向上移动一行。
// DownMoves 向下移动一行。
// Left 向左移动一个字符。
// Right 向右移动一个字符。
// PageUp 向上移动一页(视口)。
// PageDown 向下移动一页(视口)。
// Home移动到文本的开头。
// EndMoves 到文本的结尾。
// Alt+Wheel 水平滚动页面(Wheel 是鼠标滚轮)。
// Ctrl+滚轮缩放文本。
// Ctrl+A 选择所有文本。
//文本编辑可能能够提供一些元信息。 
//例如,documentTitle() 函数将返回 HTML <title> 标签内的文本。
//注意:只有当 font-size 未设置为固定大小时,才能放大 HTML 文档。
//使用 QTextEdit 作为编辑器
//所有关于使用 QTextEdit 作为显示小部件的信息也适用于此。
//当前字符格式的属性使用 setFontItalic()、setFontWeight()、
//setFontUnderline()、setFontFamily()、setFontPointSize()、
//setTextColor() 和 setCurrentFont() 设置。 
//当前段落的对齐方式是使用 setAlignment() 设置的。
//文本选择由 QTextCursor 类处理,该类提供创建选择、检索文本内容或删除选择的功能。
//您可以使用 textCursor() 方法检索与用户可见光标对应的对象。
//如果您想在 QTextEdit 中设置一个选择,
//只需在 QTextCursor 对象上创建一个选择
//然后使用 setTextCursor() 使该光标成为可见光标
//可以使用 copy() 将选择复制到剪贴板,或使用 cut() 剪切到剪贴板。
//可以使用 selectAll() 选择整个文本。
//当光标移动并且基础格式属性更改时
//发出 currentCharFormatChanged() 信号以反映新光标位置处的新属性。
//只要文本发生变化(作为 setText() 的结果或通过编辑器本身),就会发出 textChanged() 信号。
//QTextEdit 持有一个 QTextDocument 对象,
//可以使用 document() 方法检索该对象。
//您还可以使用 setDocument() 设置您自己的文档对象。
//QTextDocument 提供了一个 isModified() 函数
//如果文本自加载后或自上次调用 setModified 以来已被修改,
//则该函数将返回 true,并将 false 作为参数。
//此外,它还提供了撤销和重做的方法。

//Drag and Drop
//QTextEdit 还支持自定义拖放行为。
//默认情况下,当用户将这些 MIME 类型的数据放到文档中时
//QTextEdit 将插入纯文本、HTML 和富文本。
//重新实现 canInsertFromMimeData() 和 insertFromMimeData() 以添加对其他 MIME 类型的支持。
//例如,要允许用户将图像拖放到 QTextEdit 上,您可以通过以下方式实现这些功能:
/*
    bool TextEdit::canInsertFromMimeData(const QMimeData *source) const
    {
        if(source->hasImage())
            return true;
        else
            return QTextEdit::canInsertFromMimeData(source);
    }
*/
//我们通过返回 true 来添加对图像 MIME 类型的支持。
//对于所有其他 MIME 类型,我们使用默认实现。
/*
void TextEdit::insertFromMimeData(const QMimeData *source)
{
    if(source->hasImage())
    {
        QImage image = qvariant_cast<QImage>(source->imageData());
        QTextCursor cursor = this->textCursor();
        QTextDocument *document =  this->document();
        document->addResource(QTextDocument::ImageResource,QUrl("image"),image);
        cursor.insertImage("image");
    }
}
*/
//我们从 MIME 源保存的 QVariant 中解压图像并将其作为资源插入到文档中。


TextEdit::TextEdit(QWidget *parent)
    : QTextEdit(parent)
{
    //此属性保存文本编辑是否为只读
    //在只读文本编辑中,用户只能浏览文本和选择文本; 修改文本是不可能的
    setReadOnly(true);
}

void TextEdit::setContents(const QString &fileName)
{
    //QFileInfo 提供有关文件系统中文件的名称和位置(路径)
    //访问权限以及它是目录还是符号链接等信息。
    //文件的大小和上次修改/读取时间也可用
    //QFileInfo 还可用于获取有关 Qt 资源的信息。
    //QFileInfo 可以指向具有相对或绝对文件路径的文件。
    //绝对文件路径以目录分隔符“/”(或 Windows 上的驱动器规范)开头。
    //相对文件名以目录名或文件名开头,并指定相对于当前工作目录的路径。
    //绝对路径的一个例子是字符串“/tmp/quartz”。
    //相对路径可能看起来像“src/fatlib”。
    //。 您可以使用函数 isRelative() 来检查 QFileInfo 使用的是相对文件路径还是绝对文件路径。
    //您可以调用函数 makeAbsolute() 将相对 QFileInfo 的路径转换为绝对路径。
    //注意:以冒号 (:) 开头的路径始终被视为绝对路径,因为它们表示 QResource。
    //QFileInfo 处理的文件在构造函数中设置或稍后使用 setFile() 设置。
    //使用exists() 查看文件是否存在,使用size() 获取其大小。
    //文件的类型通过 isFile()、isDir() 和 isSymLink() 获得。
    //symLinkTarget() 函数提供符号链接指向的文件的名称。
    //在 Unix(包括 macOS 和 iOS)上,
    //此类中的属性 getter 函数返回目标文件的时间和大小等属性
    //,而不是符号链接,因为 Unix 透明地处理符号链接。
    //使用 QFile 打开符号链接可以有效地打开链接的目标。 例如:
    /*
        #ifdef Q_OS_UNIX
        QFileInfo info1("/home/bob/bin/untabify")
        info1.isSymLink(); //returns true
        info1.absoluteFilePath(); //return "/home/bob/bin/untabify"
        info1.size();   //return 56201
        info1.symLinkTarget(); //return "/opt/pretty++/bin/untabify"

        QFileInfo info2(info1.symLinkTarget());
        info2.isSymLink(); //return false
        info2.absoluteFilePath(); //returns "/opt/pretty++/bin/untabify"
        info2.size(); //return 56201

        #endif
    */
    //在 Windows 上,快捷方式(.lnk 文件)当前被视为符号链接。
    //在 Unix 系统上,属性 getter 返回目标文件的大小,而不是 .lnk 文件本身。
    //此行为已被弃用,并且可能会在 Qt 的未来版本中删除,之后 .lnk 文件将被视为常规文件。
    /*
        #ifdef Q_OS_WIN
        QFileInfo info1("C:\\Documents and Settings\\Bob\\untabify.lnk");
        info1.isSymLink(); //return true;
        info1.absoluteFilePath(); //returns "C:/Documents and Settings/Bob/untabify.lnk"
        info1.size(); // returns 743
        info1.symLinkTarget(); //returns "C:/Pretty++/untabify"

        QFileInfo info2(info1.symLinkTarget());
        info2.isSymLink(); //return false
        info2.absoluteFilePath(); //return "C:/Pretty++/untabify"
        info2.size(); //return 63942
        #endif
    */
    //可以使用 path() 和 fileName() 提取文件名的元素。
    //可以使用 baseName()、suffix() 或 completeSuffix() 提取 fileName() 的部分。
    //由 Qt 类创建的目录的 QFileInfo 对象将没有尾随文件分隔符。
    //如果您希望在您自己的文件信息对象中使用尾随分隔符,只需在给定构造函数或 setFile() 的文件名后附加一个。
    //文件的日期由birthTime()、lastModified()、lastRead() 和fileTime() 返回。
    //有关文件访问权限的信息是通过 isReadable()、isWritable() 和 isExecutable() 获得的。
    //文件的所有权可从 owner()、ownerId()、group() 和 groupId() 获得。
    //您可以使用 permission() 函数在单个语句中检查文件的权限和所有权。
    //注意:在 NTFS 文件系统上,出于性能原因,默认情况下禁用所有权和权限检查。
    //要启用它,请包含以下行:
    //extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
    //然后通过将 qt_ntfs_permission_lookup 递增和递减 1 来打开和关闭权限检查。
    /*
        qt_ntfs_permission_lookup++;//turn checking on
        qt_ntfs_permission_lookup--;//turn if off again        
    */
    //性能问题
    //QFileInfo 的一些函数查询文件系统,但出于性能原因,一些函数只对文件名本身进行操作。
    //例如:要返回相对文件名的绝对路径,absolutePath() 必须查询文件系统。
    //然而,path() 函数可以直接处理文件名,因此速度更快。
    //注意:为了提高性能,QFileInfo 缓存有关文件的信息。
    //因为文件可以被其他用户或程序,甚至同一程序的其他部分更改,
    //所以有一个函数可以刷新文件信息:refresh()。
    //如果您想关闭 QFileInfo 的缓存并强制它在每次从它请求信息时访问文件系统,
    //请调用 setCaching(false)。 如果要确保从文件系统读取所有信息,请使用 stat()。

    QFileInfo fi(fileName);
    //absoluteFilePath返回包含文件名的绝对路径。
    //绝对路径名由完整路径和文件名组成。
    //在 Unix 上,这将始终以根目录“/”开头。
    //在 Windows 上,这将始终以“D:/”开头,
    //其中 D 是驱动器号,但未映射到驱动器号的网络共享除外,在这种情况下,路径将以“//sharename/”开头。
    //QFileInfo 将大写驱动器号。 请注意, QDir 不会这样做。
    //下面的代码片段显示了这一点。
    /*
        QFileInfo fi("c:/temp/foo"); =>fi.absoluteFilePath=>"C:/temp/foo"
    */
    //此函数返回与 filePath() 相同的值
    //,除非 isRelative() 为真。
    //与 canonicalFilePath() 相反
    //符号链接或多余的“.” 或“..”元素不一定被删除。

    //[static] QUrl QUrl::fromLocalFile(const QString &localFile)
    //返回 localFile 的 QUrl 表示,解释为本地文件
    //此函数接受由斜杠分隔的路径以及此平台的本机分隔符。
    //此函数还接受带有双前导斜杠(或反斜杠)的路径来指示远程文件,
    //如“//servername/path/to/file.txt”。
    //请注意,实际上只有某些平台才能使用 QFile::open() 打开此文件。
    //空的 localFile 会导致空的 URL(自 Qt 5.4 起)。
    /*
        qDebug()<<QUrl::fromLocalFile("file.txt"); //QUrl("file:file.txt")
        qDebug()<<QUrl::fromLocalFile("/home/user/file.txt"); //Qurl("file:///home/user/file.txt")
        qDebug()<<QUrl::fromLocalFile("file:file.txt"); //doesn't make sense;expects path, not url with schem
    */
    //在上面代码片段的第一行中,文件 URL 是根据本地相对路径构造的。
    //具有相对路径的文件 URL 仅在存在可对其进行解析的基本 URL 时才有意义。 例如
    /*
        QUrl url = QUrl::fromLocalFile("file.txt");
        QUrl baseUrl = QUrl("file:/home/user/");
        //wrong:prints QUrl("file:file.txt") as url already has a scheme
        qDebug()<<baseUrl.resolved(url);
    */ 
    //因此,最好对相对文件路径使用相对 URL(即无方案):
    /*
        QUrl url = QUrl("file.txt")
        QUrl baseUrl = QUrl("file:/home/user/");
        //prints QUrl("file:///home/user/file.txt")
        qDebug()<<baseUrl.resolved(url);
    */
    srcUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
    //QFile 是一种用于读写文本和二进制文件和资源的 I/O 设备。
    //QFile 可以单独使用,或者更方便地与 QTextStream 或QDataStream 一起使用。
    //文件名通常在构造函数中传递,但可以随时使用 setFileName() 设置。
    //无论操作系统如何,QFile 都希望文件分隔符为“/”。 不支持使用其他分隔符(例如,“\”)。
    //您可以使用exists() 检查文件是否存在,并使用remove() 删除文件。
    //(更高级的文件系统相关操作由 QFileInfo 和 QDir 提供。)
    //文件用open() 打开,用close() 关闭,用flush() 刷新。
    //通常使用 QDataStream 或 QTextStream 读取和写入数据,
    //但您也可以调用 QIODevice 继承的函数 read()、readLine()、readAll()、write()。
    //QFile 还继承了 getChar()、putChar() 和 ungetChar(),它们一次处理一个字符。
    //文件的大小由 size() 返回。
    //您可以使用 pos() 获取当前文件位置,或使用 seek() 移动到新文件位置。
    //如果您已到达文件末尾,则 atEnd() 返回 true。
    
    //Reading Files Directly
    //以下示例逐行读取文本文件:
    /*
        QFile file("in.txt");
        if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return;
        while(!file.atEnd())
        {
            QByteArray line = file.readLine();
            process_line(line);
        }
    */
    //传递给 open() 的 QIODevice::Text 标志告诉 Qt 将 Windows 样式的行终止符(“\r\n”)转换为 C++ 样式的终止符(“\n”)。
    //默认情况下,QFile 假定为二进制,即它不对存储在文件中的字节执行任何转换。

    //Using Streams to Read Files
    //下一个示例使用 QTextStream 逐行读取文本文件:
    /*
        QFile file("in.txt");
        if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return;
        QTextStream in(&file);  
        while(!in.atEnd())
        {
            QString line = in.readLine();
            process_line(line);
        }
    */
    //QTextStream 负责将存储在磁盘上的 8 位数据转换为 16 位 Unicode QString。
    //默认情况下,它假定文件以 UTF-8 编码。 这可以使用 QTextStream::setEncoding() 更改。
    //要写文本,我们可以使用operator<<(),它被重载,左边是QTextStream,右边是各种数据类型(包括QString):
    /*
        QFile file("out.txt");
        if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
            return;
        QTextStream out(&file);
        out<<"The magic number is: "<<49<<"\n";
    */
    //QDataStream 与此类似,您可以使用 operator<<() 写入数据
    //使用 operator>>() 读取数据。 有关详细信息,请参阅类文档。
    //当您使用 QFile、QFileInfo 和 QDir 访问带有 Qt 的文件系统时,
    //您可以使用 Unicode 文件名。
    //在 Unix 上,这些文件名被转换为 8 位编码。
    //如果您想使用标准 C++ API(<cstdio> 或 <iostream>)或特定于平台的 API 来访问文件而不是 QFile,
    //您可以使用 encodeName() 和 decodeName() 函数在 Unicode 文件名和 8- 位文件名。
    //在 Unix 上,有一些特殊的系统文件(例如在 /proc 中)
    //它们的 size() 将始终返回 0,但您仍然可以从这样的文件中读取更多数据;
    //数据是直接响应您调用 read() 生成的。
    //但是,在这种情况下,您不能使用 atEnd() 来确定是否有更多数据要读取
    //(因为 atEnd() 将为声称大小为 0 的文件返回 true)。
    //相反,您应该调用 readAll(),或者重复调用 read() 或 readLine(),
    //直到无法读取更多数据。 下一个示例使用 QTextStream 逐行读取/proc/modules:
    /*
        QFile file("/proc/modules");
        if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return;
        QTextStream in(&file);
        QString line = in.readLine();
        while(!line.isNull())
        {
            process_line(line);
            line = in.readline();
        }
    */
    //Signals
    //与其他 QIODevice 实现(例如 QTcpSocket)不同,
    //QFile 不会发出 aboutToClose()、bytesWritten() 或 readyRead() 信号。
    //这个实现细节意味着 QFile 不适合读写某些类型的文件,比如 Unix 平台上的设备文件。
    //平台特定问题
    //文件权限在类 Unix 系统和 Windows 上的处理方式不同。
    //在类 Unix 系统上的不可写目录中,无法创建文件。
    //在 Windows 上情况并非总是如此,例如,“我的文档”目录通常不可写,但仍然可以在其中创建文件。
    //Qt 对文件权限的理解是有限的
    //这尤其影响 QFile::setPermissions() 函数。
    //在 Windows 上,Qt 将只设置传统的只读标志,并且只有在没有任何 Write* 标志被传递时才会设置。
    //Qt 不操作访问控制列表 (ACL),这使得该功能对于 NTFS 卷几乎没有用。
    //它可能仍可用于使用 VFAT 文件系统的 U 盘。 POSIX ACL 也不会被操纵

    QFile file(fileName);
    //[override virtual] bool QFile::open(QIODeviceBase::OpenMode mode)
    //重新实现:QIODevice::open(QIODeviceBase::OpenMode 模式)。
    //使用OpenMode模式打开文件,成功则返回true; 否则为假。
    //模式必须为 QIODevice::ReadOnly、QIODevice::WriteOnly 或 QIODevice::ReadWrite。
    //可能还有其他标志,例如 QIODevice::Text 和 QIODevice::Unbuffered。
    //注意:在WriteOnly 或ReadWrite 模式下,如果相关文件尚不存在,该函数将在打开之前尝试创建一个新文件。
    //bool QFile::open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags = DontCloseHandle)
    //在给定的模式下打开现有的文件句柄 fh。
    //handleFlags 可用于指定其他选项。 成功则返回真; 否则返回false
    /*
        #include <stdio.h>

        void printError(const char* msg)
        {
            QFile file;
            file.open(stderr,QIODevice::WriteOnly);
            file.write(msg,qstrlen(msg)); //write to stderr
            file.close();
        }
    */
    //当使用此函数打开 QFile 时,
    //close() 的行为由 AutoCloseHandle 标志控制。
    //如果指定了 AutoCloseHandle,并且此函数成功,
    //则调用 close() 关闭采用的句柄。
    //否则, close() 实际上不会关闭文件,而只会刷新它。
    //Warning:
    //如果 fh 不引用常规文件,例如,它是 stdin、stdout 或 stderr,则您可能无法使用 seek()。
    //size() 在这些情况下返回 0。
    //有关更多信息,请参阅 QIODevice::isSequential()。
    //由于此函数在不指定文件名的情况下打开文件,因此您不能将此 QFile 与 QFileInfo 一起使用。
    if (file.open(QIODevice::ReadOnly)) {
        //QByteArray QIODevice::readAll()
        //从设备读取所有剩余数据,并将其作为字节数组返回。
        //这个功能没有报错的函数; 返回一个空的 QByteArray 
        //可能意味着当前没有数据可供读取,或者发生了错误。
        QString data(file.readAll());
        //bool QString::endsWith(const QString &s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
        //如果字符串以 s 结尾,则返回 true; 否则返回false。
        if (fileName.endsWith(".html"))
            //void setHtml(const QString &text)
            //此属性为文本编辑的文本提供了 HTML 界面
            //toHtml() 将文本编辑的文本返回为 html。
            //setHtml() 更改文本编辑的文本。任何先前的文本都将被删除,
            //撤消/重做历史记录将被清除。 输入文本被解释为 html 格式的富
            //文本。 currentCharFormat() 也被重置,除非 textCursor() 已经
            //在文档的开头。
            //注意:调用者有责任确保在创建包含 HTML 的 QString 并将其传
            //递给 setHtml() 时正确解码文本。
            //默认情况下,对于新创建的空文档,此属性包含用于描述没有正文文本的 HTML 4.0 文档的文本。            
            setHtml(data);
        else
            //[slot] void QTextEdit::setPlainText(const QString &text)
            //将文本编辑的文本更改为字符串文本。
            //任何先前的文本都将被删除。
            //Notes:
            //text 被解释为纯文本。
            //撤消/重做历史也被清除。
            //currentCharFormat() 被重置,除非 textCursor() 已经在文档的开头。
            setPlainText(data);
    }
}

QVariant TextEdit::loadResource(int type, const QUrl &name)
{
    //QTextDocument::ImageResource
    //该资源包含图像数据。
    //当前支持的数据类型是 QMetaType::QPixmap 和 QMetaType::QImage。
    //如果相应的变体是 QMetaType::QByteArray 类型,则 Qt 尝试使用
    //QImage::loadFromData 加载图像。
    //当前不支持 QMetaType::QIcon。
    //图标需要首先转换为支持的类型之一,例如使用 QIcon::pixmap。
    if (type == QTextDocument::ImageResource) {
        //QString QUrl::toLocalFile() const
        //返回格式化为本地文件路径的此 URL 的路径。
        //返回的路径将使用正斜杠,即使它最初是用反斜杠创建的。
        //如果此 URL 包含非空主机名,它将以 SMB 网络上的形式
        //(例如,“//servername/path/to/file.txt”)在返回值中编码。
        /*
            qDebug()<<QUrl("file:file.txt").toLocalFile(); //"file.txt"
            qDebug()<<QUrl("file:/home/user/file.txt").toLocalFile(); //"/home/user/file.txt"
            qDebug()<<QUrl("file.txt").toLocalFile();//"";wasn't a local file
        */
        QFile file(srcUrl.resolved(name).toLocalFile());
        if (file.open(QIODevice::ReadOnly))
            //QByteArray QIODevice::readAll()
            //从设备读取所有剩余数据,并将其作为字节数组返回
            //这个功能没有报错的办法;
            //返回一个空的 QByteArray 可能意味着当前没有数据可供读取,或者发生了错误。
            return file.readAll();
    }
    //[virtual invokable] QVariant QTextEdit::loadResource(int type, const QUrl &name)
    //加载由给定类型和名称指定的资源。
    //这个函数是 QTextDocument::loadResource() 的扩展。
    //[virtual protected invokable] QVariant QTextDocument::loadResource(int type, const QUrl &name)
    //从具有给定名称的资源加载指定类型的数据。
    //该函数由富文本引擎调用,
    //以请求未由 QTextDocument 直接存储但仍与其关联的数据。
    //例如,图像被 QTextImageFormat 对象的 name 属性间接引用。
    //当被 Qt 调用时,type 是QTextDocument::ResourceType 的值之一。
    //如果 QTextDocument 是具有可调用 loadResource 方法
    //(例如 QTextEdit、QTextBrowser 或 QTextDocument 本身)的 QObject 的子对象,则默认实现会尝试从父对象检索数据。
    return QTextEdit::loadResource(type, name);
}

findfiledialog.h

#ifndef FINDFILEDIALOG_H
#define FINDFILEDIALOG_H

#include <QDialog>

QT_BEGIN_NAMESPACE
//QComboBox 提供了一种以占用最少屏幕空间的方式向用户呈现选项列表的方法。
//组合框是一个显示当前项目的选择小部件,可以弹出一个可选择项目的列表。
//组合框可以是可编辑的,允许用户修改列表中的每个项目。
//组合框可以包含像素图和字符串;
//insertItem() 和 setItemText() 函数被适当地重载。
//对于可编辑的组合框,提供了函数 clearEditText() 以清除显示的字符串而不更改组合框的内容。
//如果组合框的当前项目发生变化,则会发出三个信号:
//currentIndexChanged()、currentTextChanged() 和 activate()。
//currentIndexChanged() 和 currentTextChanged() 总是被发出,
//无论更改是通过编程还是通过用户交互完成,
//而 activate() 仅在更改由用户交互引起时发出。
//当用户突出显示组合框弹出列表中的项目时,会发出 highlight() 信号。
//所有三个信号都存在两种版本,一种带有 QString 参数,另一种带有 int 参数。
//。 如果用户选择或突出显示像素图,则仅发出 int 信号。
//每当可编辑组合框的文本发生更改时,就会发出 editTextChanged() 信号。
//当用户在可编辑的组合框中输入新字符串时,小部件可能会也可能不会插入它,并且可以将它插入到多个位置。
//默认策略是 InsertAtBottom,但您可以使用 setInsertPolicy() 更改此策略。
//可以使用 QValidator 将输入限制为可编辑的组合框;
//请参见 setValidator()。 默认情况下,接受任何输入。
//例如,可以使用插入函数 insertItem() 和 insertItems() 填充组合框。
//可以使用 setItemText() 更改项目。
//可以使用 removeItem() 删除项目,可以使用 clear() 删除所有项目。
//当前项的文本由 currentText() 返回,编号项的文本由 text() 返回。
//当前项目可以使用 setCurrentIndex() 设置。
//组合框中的项目数由 count() 返回;
//可以使用 setMaxCount() 设置最大项目数。
//您可以允许使用 setEditable() 进行编辑。
//对于可编辑的组合框,您可以使用 setCompleter() 设置自动完成功能,并使用 setDuplicatesEnabled() 设置用户是否可以添加重复项。
//QComboBox 为其弹出列表使用模型/视图框架并存储其项目。
//默认情况下, QStandardItemModel 存储项目, QListView 子类显示弹出列表。
//您可以直接访问模型和视图(使用 model() 和 view()),但 QComboBox 还提供设置和获取项目数据的功能(例如,setItemData() 和 itemText())。
//您还可以设置新模型和视图(使用 setModel() 和 setView())。
//对于组合框标签中的文本和图标,使用具有 Qt::DisplayRole 和 Qt::DecorationRole 的模型中的数据。
//请注意,您不能更改 view() 的 SelectionMode,例如,通过使用 setSelectionMode()。

class QComboBox;

//对话框和消息框通常以符合该平台界面指南的布局呈现按钮。
//不同平台的对话框总是有不同的布局。
//QDialogButtonBox 允许开发人员向其添加按钮,并将自动为用户的桌面环境使用适当的布局。
//对话框的大多数按钮都遵循某些角色。 此类角色包括:
//接受或拒绝对话。
//寻求帮助。
//对对话框本身执行操作(例如重置字段或应用更改)。
//也可以有其他方式关闭对话框,这可能会导致破坏性结果。
//大多数对话框都有几乎可以被视为标准的按钮
//(例如确定和取消按钮)。 有时以标准方式创建这些按钮会很方便。
//有几种使用 QDialogButtonBox 的方法。
//一种方法是自己创建按钮(或按钮文本)并将它们添加到按钮框,指定它们的作用。
/*
    findButton = new QPushButton(tr("&Find"));
    findButton->setDefault(true);

    moreButton = new QPushButton(tr("&More"));
    moreButton->setCheckable(true);
    moreButton->setAutoDefault(false);
*/

//或者, QDialogButtonBox 提供了几个您可以使用的标准按钮
//(例如确定、取消、保存)。 它们作为标志存在,因此您可以在
//构造函数中将它们组合在一起。

/*
    buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
                                |QDialogButtonBox::Cancel);
    connect(buttonBox,&QDialogButtonBox::accepted,this,&QDialog::accept);
    connect(buttonBox,&QDialogButtonBox::rejected,this,&QDialog::reject);
*/
//您可以混合搭配普通按钮和标准按钮。
//目前,如果按钮框是水平的,按钮的布局方式如下:
//当在按钮框中单击按钮时,会为实际按下的按钮发出 clicked() 信号。
//为方便起见,如果按钮具有 AcceptRole、RejectRole 或 
//HelpRole,则分别发出 Accepted()、rejected() 或 helpRequested() 信号。
//如果你想要一个特定的按钮是默认的,
//你需要自己调用 QPushButton::setDefault() 。
//但是,如果没有设置默认按钮并在使用 QPushButton::autoDefault 
//属性时保留哪个按钮是跨平台的默认按钮,则在显示 QDialogButtonBox 时,
//第一个具有接受角色的按钮将成为默认按钮,
class QDialogButtonBox;
//QLabel 用于显示文本或图像。
//不提供用户交互功能。
//标签的视觉外观可以通过多种方式配置,
//它可以用于为另一个小部件指定一个焦点助记键。
//QLabel 可以包含以下任何内容类型:
//Plain text 将 QString 传递给 setText()。
//Rich text 将包含富文本的 QString 传递给 setText()。
//A pixmap 将 QPixmap 传递给 setPixmap()。
//A movie 将 QMovie 传递给 setMovie()。
//A number  将 int 或 double 传递给 setNum(),它将数字转换为纯文本
//Nothing   与空的纯文本相同。 这是默认设置。 由 clear() 设置。
//警告:当将 QString 传递给构造函数或调用 setText() 时,
//请确保清理您的输入
//因为 QLabel 会尝试猜测它是将文本显示为纯文本还是富文本(HTML 4 标记的子集)。
//您可能希望显式调用 setTextFormat(),
//例如 以防您希望文本为纯格式但无法控制文本源(例如,当显示从 Web 加载的数据时)。
//当使用这些功能中的任何一个更改内容时,任何先前的内容都会被清除。
//默认情况下,标签显示左对齐、垂直居中的文本和图像,
//其中要显示的文本中的任何选项卡都会自动展开。
//但是,可以通过多种方式调整和微调 QLabel 的外观。
//可以使用 setAlignment() 和 setIndent() 调整 QLabel 小部件区域内内容的定位
//文本内容还可以使用 setWordWrap() 沿单词边界换行。
//例如,此代码在右下角设置了一个带有两行文本的下沉面板(两行与标签右侧齐平):
/*
    QLabel *label = new QLabel(this);
    label->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    label->setText("first line\nsecond line");
    label->setAlignment(Qt::AlignBottom | Qt::AlignRight);
*/
//QLabel 继承自 QFrame 的属性和函数也可用于指定要用于任何给定标签的小部件框架。
//QLabel 通常用作交互式小部件的标签。
//为此,QLabel 提供了一种有用的机制来添加助记符(请参阅 QKeySequence),
//该助记符会将键盘焦点设置到其他小部件(称为 QLabel 的“伙伴”)。 例如:
/*
    QLineEdit *phoneEdit = new QLineEdit(this);
    QLabel *phoneLabel = new QLabel("&Phone:",this);
    phoneLabel->setBuddy(phoneEdit);
*/
//在此示例中,当用户按下 Alt+P 时,
//键盘焦点将转移到标签的伙伴(QLineEdit)。
//如果好友是一个按钮(从 QAbstractButton 继承),触发助记符将模拟按钮点击。
class QLabel;
//工具按钮是一种特殊按钮,可提供对特定命令或选项的快速访问。
//与普通命令按钮相反,工具按钮通常不显示文本标签,而是显示图标
//当使用 QToolBar::addAction() 创建新的 QAction 实例或使用 QToolBar::addAction() 将现有操作添加到工具栏时,
//通常会创建工具按钮。
//也可以以与任何其他小部件相同的方式构建工具按钮,
//并将它们与布局中的其他小部件一起排列
//工具按钮的一种经典用法是选择工具
//例如,绘图程序中的“钢笔”工具。
//这将通过使用 QToolButton 作为切换按钮来实现(参见 setCheckable())。
//QToolButton 支持自动提升。
//在自动提升模式下该按钮仅在鼠标指向时绘制 3D 帧。
//当在 QToolBar 中使用按钮时,该功能会自动打开。 使用 setAutoRaise() 更改它。
//工具按钮的图标设置为 QIcon。
//这使得可以为禁用和活动状态指定不同的像素图。
//当按钮的功能不可用时,使用禁用的像素图。
//由于鼠标指针悬停在按钮上而自动抬起按钮时,将显示活动像素图。
//按钮的外观和尺寸可通过 setToolButtonStyle() 和 setIconSize() 进行调整。
//当在 QMainWindow 的 QToolBar 内使用时,
//按钮会自动调整到 QMainWindow 的设置
//(参见 QMainWindow::setToolButtonStyle() 和 QMainWindow::setIconSize())。
//除了图标,工具按钮还可以显示箭头符号,由 arrowType 指定。
//工具按钮可以在弹出菜单中提供其他选项。
//可以使用 setMenu() 设置弹出菜单。
//使用 setPopupMode() 配置可用于带有菜单集的工具按钮的不同模式。
//默认模式是 DelayedPopupMode,有时与 Web 浏览器中的“后退”按钮一起使用。
//按住按钮一段时间后,会弹出一个菜单,显示要跳转到的可能页面列表。
//超时取决于样式,请参阅 QStyle::SH_ToolButton_PopupDelay。
class QToolButton;
//QTreeWidget 类是一个方便的类,它提供了一个标准的树形小部件,
//它具有类似于 Qt 3 中的 QListView 类使用的经典的基于项目的界面。
//这个类基于 Qt 的模型/视图架构,并使用默认模型来保存项目 ,每一个都是一个 QTreeWidgetItem。
//不需要模型/视图框架灵活性的开发人员可以使用此类非常轻松地创建简单的分层列表。
//更灵活的方法包括将 QTreeView 与标准项目模型相结合。
//这允许将数据的存储与其表示分开。
//在最简单的形式中,可以通过以下方式构建树小部件:
/*
    QTreeWidget *treeWidget = new QTreeWidget();
    treeWidget->setColumnCount(1);
    QList<QTreeWidgetItem*> items;
    for(int i = 0;i < 10; ++i)
        items.append(new QTreeWidgetItem(static_cast<QTreeWidget*>(nullptr),QStringList(QString("item:%1").arg(i))));
    treeWidget->insertToLevelItems(0,items);
*/
//在将项目添加到树小部件之前,必须使用 setColumnCount() 设置列数。
//这允许每个项目有一个或多个标签或其他装饰。
//使用的列数可以通过 columnCount() 函数找到。
//树可以有一个标题,其中包含小部件中每一列的部分。
//通过使用 setHeaderLabels() 提供字符串列表来为每个部分设置标签是最简单的,
//但是可以使用 QTreeWidgetItem 构造自定义标题并使用 setHeaderItem() 函数将其插入到树中。
//树中的项目可以根据预定义的排序顺序按列排序。
//如果启用排序,用户可以通过单击列标题对项目进行排序。
//可以通过调用 setSortingEnabled() 启用或禁用排序。
//isSortingEnabled() 函数指示是否启用排序。
class QTreeWidget;

class QTreeWidgetItem;
//树小部件项用于保存树小部件的信息行。
//行通常包含几列数据,每列可以包含一个文本标签和一个图标。
//QTreeWidgetItem 类是一个方便的类,
//,它取代了 Qt 3 中的 QListViewItem 类。
//它提供了一个与 QTreeWidget 类一起使用的项目。
//项目通常由父级构造,该父级是 QTreeWidget(用于顶级项目)或 
//QTreeWidgetItem(用于树较低级别的项目)
//例如,下面的代码构造了一个顶级项来表示世界的城市,并添加了一个 Oslo 条目作为子项:
/*
    QTreeWidgetItem* cities = new QTreeWidgetItem(treeWidget);
    cities->setText(0,tr("Cities"));
    QTreeWidgetItem* osloItem = new QTreeWidgetItem(cities);
    osloItem->setText(0,tr("Oslo"));
    osloItem->setText(1,tr("Yes"));
*/
//通过指定它们在构造时遵循的项目,可以按特定顺序添加项目:
/*
    QTreeWidgetItem *planes = new QTreeWidgetItem(treeWidget,cities);
    planets->setText(0,tr("Planets"));
*/
//项目中的每一列都可以有自己的背景画笔,
//该画笔是通过 setBackground() 函数设置的。
//可以使用 background() 找到当前的背景画笔。
//每列的文本标签可以使用自己的字体和画笔呈现。
//这些是用 setFont() 和 setForeground() 函数指定的,并用 font() 和 foreground() 读取。
//顶级项目与树中较低级别项目之间的主要区别在于顶级项目没有 parent()。
//此信息可用于区分项目之间的差异,并且有助于了解何时从树中插入和删除项目。
//可以使用 takeChild() 删除项的子项,并使用 insertChild() 函数将其插入到子项列表中的给定索引处。
//默认情况下,项目是启用的、可选择的、可检查的,并且可以是拖放操作的来源。
//可以通过使用适当的值调用 setFlags() 来更改每个项目的标志(请参阅 Qt::ItemFlags)。
//可以使用 setCheckState() 函数选中和取消选中可检查项。
//相应的 checkState() 函数指示该项目当前是否被选中。
//子类化
//当子类化 QTreeWidgetItem 以提供自定义项时,
//,可以为它们定义新类型,以便将它们与标准项区分开来。
//需要此功能的子类的构造函数需要使用等于或大于 UserType 的新类型值调用基类构造函数。
QT_END_NAMESPACE

class Assistant;
class TextEdit;

//Simple Text Viewer 应用程序提供了一个查找文件对话框,
//允许用户使用通配符匹配来搜索文件。
//搜索在指定目录中执行,并且用户可以选择浏览现有文件系统以查找相关目录。
class FindFileDialog : public QDialog
{
    Q_OBJECT

public:
    FindFileDialog(TextEdit *editor, Assistant *assistant);

private slots:
    void browse();
    void help();
    void openFile();
    void update();

private:
    void findFiles();
    void showFiles(const QStringList &files);

    void createButtons();
    void createComboBoxes();
    void createFilesTree();
    void createLabels();
    void createLayout();

    Assistant *currentAssistant;
    TextEdit *currentEditor;
    QTreeWidget *foundFilesTree;

    QComboBox *directoryComboBox;
    QComboBox *fileNameComboBox;

    QLabel *directoryLabel;
    QLabel *fileNameLabel;

    QDialogButtonBox *buttonBox;

    QToolButton *browseButton;
};
//! [0]

#endif

findfiledialog.cpp

#include "assistant.h"
#include "findfiledialog.h"
#include "textedit.h"

#include <QComboBox>
#include <QDialogButtonBox>
#include <QDir>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QRegularExpression>
#include <QToolButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>

//在构造函数中,我们保存对作为参数传递的 Assistant 和 QTextEdit 对象的引用。
//Assistant 对象将在 FindFileDialog 的 help() 槽中使用
//我们很快就会看到,而 QTextEdit 将在对话框的 openFile() 槽中使用以显示所选文件。
FindFileDialog::FindFileDialog(TextEdit *editor, Assistant *assistant)
    : QDialog(editor)
{
    currentAssistant = assistant;
    currentEditor = editor;
//! [0]

    createButtons();
    createComboBoxes();
    createFilesTree();
    createLabels();
    createLayout();

    //void QComboBox::addItem(const QString &text, const QVariant &userData = QVariant())
    //使用给定的文本将一个项目添加到组合框
    //并包含指定的 userData(存储在 Qt::UserRole 中)。
    //该项目将附加到现有项目的列表中。
    //[static] QString QDir::toNativeSeparators(const QString &pathName)
    //返回 pathName ,其中“/”分隔符转换为适用于底层操作系统的分隔符。
    //在 Windows 上, toNativeSeparators("c:/winnt/system32") 返回 "c:\winnt\system32"。
    //返回的字符串可能与某些操作系统上的参数相同,例如在 Unix 上。
    directoryComboBox->addItem(QDir::toNativeSeparators(QDir::currentPath()));
    fileNameComboBox->addItem("*");
    findFiles();

    setWindowTitle(tr("Find File"));
//! [1]
}
//! [1]

void FindFileDialog::browse()
{
    //此属性保存当前文本
    //如果组合框可编辑,则当前文本为行编辑显示的值。
    //否则,如果组合框为空或未设置当前项,则为当前项的值或空字符串。
    QString currentDirectory = directoryComboBox->currentText();
    //[static] QString QFileDialog::getExistingDirectory(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = ShowDirsOnly)
    //这是一个方便的静态函数,它将返回用户选择的现有目录。
    /*
        QString dir = QFileDialog::getExistingDirectory(this,tr("Open Directory"),
                                                        "/home",
                                                        QFileDialog::ShowDirsOnly
                                                        |QFileDialog::DontResolveSymlinks);
    */
    //此函数使用给定的父小部件创建一个模态文件对话框。
    //如果 parent 不是 nullptr,对话框将显示在父小部件的中心。
    //对话框的工作目录设置为dir,
    //标题设置为caption。
    //其中任何一个都可以是空字符串,在这种情况下,将分别使用当前目录和默认标题。
    //options 参数包含有关如何运行对话框的各种选项,
    //有关您可以传递的标志的更多信息,请参阅 QFileDialog::Option 枚举。
    //为确保本机文件对话框,必须设置 ShowDirsOnly。
    //在 Windows 和 macOS 上,此静态函数将使用本机文件对话框而不是 QFileDialog。
    //但是,本机 Windows 文件对话框不支持在目录选择器中显示文件。
    //您需要传递 DontUseNativeDialog 以使用 QFileDialog 显示文件。
    //在 Unix/X11 上,文件对话框的正常行为是解析和遵循符号链接。
    //例如,如果 /usr/tmp 是 /var/tmp 的符号链接,则在输入 /usr/tmp 后,文件对话框将更改为 /var/tmp。
    //如果选项包含 DontResolveSymlinks,则文件对话框会将符号链接视为常规目录。
    //在 Windows 上,对话框将旋转一个阻塞模式事件循环,该循环不会调度任何 QTimers,
    //如果 parent 不是 nullptr,那么它将把对话框定位在父标题栏的正下方。
    QString newDirectory = QFileDialog::getExistingDirectory(this,
                               tr("Select Directory"), currentDirectory);
    if (!newDirectory.isEmpty()) {
        directoryComboBox->addItem(QDir::toNativeSeparators(newDirectory));
        //此属性保存组合框中当前项目的索引。
        //插入或删除项目时,当前索引可以更改。
        //默认情况下,对于空组合框或未设置当前项的组合框,此属性的值为 -1。
        directoryComboBox->setCurrentIndex(directoryComboBox->count() - 1);
        update();
    }
}

//FindFileDialog 类中最相关的成员是私有 help() 槽。
//该插槽连接到对话框的帮助按钮
//并通过调用助手的 showDocumentation() 
//函数将当前的 Qt 助手实例与对话框的文档一起带到前台
void FindFileDialog::help()
{
    currentAssistant->showDocumentation("filedialog.html");
}

void FindFileDialog::openFile()
{
    auto item  = foundFilesTree->currentItem();
    if (!item)
        return;

    QString fileName = item->text(0);
    QString path = QDir(directoryComboBox->currentText()).filePath(fileName);

    currentEditor->setContents(path);
    close();
}

void FindFileDialog::update()
{
    findFiles();
    buttonBox->button(QDialogButtonBox::Open)->setEnabled(
            foundFilesTree->topLevelItemCount() > 0);
}

void FindFileDialog::findFiles()
{
    QString wildCard = fileNameComboBox->currentText();
    if (!wildCard.endsWith('*'))
        wildCard += '*';
    QRegularExpression filePattern(QRegularExpression::wildcardToRegularExpression(wildCard));

    QDir directory(directoryComboBox->currentText());

    const QStringList allFiles = directory.entryList(QDir::Files | QDir::NoSymLinks);
    QStringList matchingFiles;

    for (const QString &file : allFiles) {
        if (filePattern.match(file).hasMatch())
            matchingFiles << file;
    }
    showFiles(matchingFiles);
}

void FindFileDialog::showFiles(const QStringList &files)
{
    foundFilesTree->clear();

    for (int i = 0; i < files.count(); ++i) {
        QTreeWidgetItem *item = new QTreeWidgetItem(foundFilesTree);
        item->setText(0, files[i]);
    }

    if (files.count() > 0)
        foundFilesTree->setCurrentItem(foundFilesTree->topLevelItem(0));
}

void FindFileDialog::createButtons()
{
    browseButton = new QToolButton;
    browseButton->setText(tr("..."));
    connect(browseButton, &QAbstractButton::clicked, this, &FindFileDialog::browse);

    buttonBox = new QDialogButtonBox(QDialogButtonBox::Open
                                     | QDialogButtonBox::Cancel
                                     | QDialogButtonBox::Help);
    connect(buttonBox, &QDialogButtonBox::accepted, this, &FindFileDialog::openFile);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
    connect(buttonBox, &QDialogButtonBox::helpRequested, this, &FindFileDialog::help);
}

void FindFileDialog::createComboBoxes()
{
    directoryComboBox = new QComboBox;
    fileNameComboBox = new QComboBox;

    fileNameComboBox->setEditable(true);
    fileNameComboBox->setSizePolicy(QSizePolicy::Expanding,
                                    QSizePolicy::Preferred);

    directoryComboBox->setMinimumContentsLength(30);
    directoryComboBox->setSizeAdjustPolicy(
            QComboBox::AdjustToContents);
    directoryComboBox->setSizePolicy(QSizePolicy::Expanding,
                                     QSizePolicy::Preferred);

    connect(fileNameComboBox, &QComboBox::editTextChanged,
            this, &FindFileDialog::update);
    connect(directoryComboBox, &QComboBox::currentTextChanged,
            this, &FindFileDialog::update);
}

void FindFileDialog::createFilesTree()
{
    foundFilesTree = new QTreeWidget;
    foundFilesTree->setColumnCount(1);
    foundFilesTree->setHeaderLabels(QStringList(tr("Matching Files")));
    foundFilesTree->setRootIsDecorated(false);
    foundFilesTree->setSelectionMode(QAbstractItemView::SingleSelection);

    connect(foundFilesTree, &QTreeWidget::itemActivated,
            this, &FindFileDialog::openFile);
}

void FindFileDialog::createLabels()
{
    directoryLabel = new QLabel(tr("Search in:"));
    fileNameLabel = new QLabel(tr("File name (including wildcards):"));
}

void FindFileDialog::createLayout()
{
    QHBoxLayout *fileLayout = new QHBoxLayout;
    fileLayout->addWidget(fileNameLabel);
    fileLayout->addWidget(fileNameComboBox);

    QHBoxLayout *directoryLayout = new QHBoxLayout;
    directoryLayout->addWidget(directoryLabel);
    directoryLayout->addWidget(directoryComboBox);
    directoryLayout->addWidget(browseButton);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(fileLayout);
    mainLayout->addLayout(directoryLayout);
    mainLayout->addWidget(foundFilesTree);
    mainLayout->addStretch();
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);
}

assistant.h

#ifndef ASSISTANT_H
#define ASSISTANT_H

#include <QCoreApplication>
#include <QProcess>
#include <QScopedPointer>
#include <QString>

//通过 Assistant 类控制 Qt Assistant
//我们将首先看看如何从远程应用程序启动和操作 Qt 助手。 为此,我们创建了一个名为 Assistant 的类。
//此类提供了一个用于显示文档页面的公共函数,
//以及一个用于确保 Qt Assistant 启动并运行的私有帮助函数。
class Assistant
{
    //Q_DECLARE_TR_FUNCTIONS(context)
    //Q_DECLARE_TR_FUNCTIONS() 宏使用以下签名声明并实现了转换函数 tr():
    /*
        static inline QString tr(const char* sourceText,
                                 const char* comment = nullptr);
    */
    //如果您想在不继承自 QObject 的类中使用 QObject::tr(),则此宏很有用。
    //Q_DECLARE_TR_FUNCTIONS() 必须出现在类定义的最顶部(在第一个 public: 或 protected: 之前)。 例如:
    /*
        class MyMfcView : public CView
        {
            Q_CECLARE_TR_FUNCTIONS(MyMfcView);
        public:
            MyMfcView();
            ....
        }
        //上下文参数通常是类名,但它可以是任何文本。
    */
    Q_DECLARE_TR_FUNCTIONS(Assistant)

public:
    Assistant();
    ~Assistant();
    void showDocumentation(const QString &file);

private:
    bool startAssistant();
    void showError(const QString &message);
    //运行进程
    //要启动进程,请将要运行的程序的名称和命令行参数作为参数传递给 start()。
    //参数作为 QStringList 中的单个字符串提供。
    //或者,您可以将程序设置为使用 setProgram() 和 setArguments() 运行,然后调用 start() 或 open()。
    //例如,以下代码片段通过将包含“-style”和“fusion”的字符串作为参数列表中的两个项目传递,
    //在 X11 平台上以 Fusion 风格运行模拟时钟示例:
    /*
        QObject *parent;
        ...
        QString program = "./path/to/Qt/examples/widgets/analogclock";
        QStringList arguments;
        arguments << "-style"<<"fusion";

        QProcess *myProcess = new QProcess(parent);
        myProcess->start(program,arguments);
    */
    //然后QProcess 进入Starting 状态,当程序启动后,QProcess 进入Running 状态并发出started()。
    //QProcess 允许您将进程视为顺序 I/O 设备。
    //您可以像使用 QTcpSocket 访问网络连接一样对进程进行写入和读取。
    //然后,您可以通过调用 write() 写入进程的标准输入,
    //通过调用 read()、readLine() 和 getChar() 读取标准输出。
    //因为它继承了QIODevice,所以QProcess也可以作为QXmlReader的输入源,
    //或者用于生成要使用QNetworkAccessManager上传的数据。
    //当进程退出时,QProcess 重新进入NotRunning 状态(初始状态),并发出finished()。
    //Finished() 信号提供进程的退出代码和退出状态作为参数,
    //您还可以调用 exitCode() 获取最后一个完成的进程的退出代码,并调用 exitStatus() 获取其退出状态。
    //如果在任何时间点发生错误,QProcess 将发出 errorOccurred() 信号。
    //您还可以调用 error() 来查找最后发生的错误类型,并调用 state() 来查找当前进程状态。
    //Communicating via Channels
    //进程有两个预定义的输出通道:
    //标准输出通道 (stdout) 提供常规控制台输出,标准错误通道 (stderr) 通常提供进程打印的错误。 这些通道代表两个独立的数据流。
    //您可以通过调用 setReadChannel() 在它们之间切换。
    //当当前读取通道上的数据可用时,QProcess 会发出 readyRead()。
    //当新的标准输出数据可用时,它也会发出 readyReadStandardOutput(),
    //当新的标准错误数据可用时,会发出 readyReadStandardError()。
    //除了调用 read()、readLine() 或 getChar(),您还可以通过调用 
    //readAllStandardOutput() 或 readAllStandardError() 从两个通道中的任何一个显式读取所有数据。
    //渠道的术语可能会产生误导。
    //请注意,进程的输出通道对应于 QProcess 的读取通道,而进程的输入通道对应于 QProcess 的写入通道。
    //这是因为我们使用 QProcess 读取的是进程的输出,而我们写入的则成为进程的输入。
    //QProcess 可以合并两个输出通道,这样来自运行进程的标准输出和标准错误数据都使用标准输出通道。
    //在开始激活此功能的过程之前,使用 MergedChannels 调用 setProcessChannelMode()。
    //通过将 ForwardedChannels 作为参数传递,
    //您还可以选择将正在运行的进程的输出转发到调用主进程。
    //也可以只转发一个输出通道——通常会使用 ForwardedErrorChannel,
    //但 ForwardedOutputChannel 也存在。
    //请注意,在 GUI 应用程序中使用通道转发通常是一个坏主意 - 您应该以图形方式显示错误。
    //。QProcess 可以合并两个输出通道,以便来自正在运行的进程的标准输出和标准错误数据都使用标准输出通道。
    //在开始激活此功能的过程之前,使用 MergedChannels 调用 setProcessChannelMode()。
    //通过将 ForwardedChannels 作为参数传递,您还可以选择将正在运行的进程的输出转发到调用主进程。
    //也可以只转发一个输出通道——通常会使用 ForwardedErrorChannel,
    //但 ForwardedOutputChannel 也存在。
    //请注意,在 GUI 应用程序中使用通道转发通常是一个坏主意 - 您应该改为以图形方式显示错误。
    //某些进程需要特殊的环境设置才能运行。
    //您可以通过调用 setProcessEnvironment() 为您的进程设置环境变量。
    //要设置工作目录,请调用 setWorkingDirectory()。
    //默认情况下,进程在调用进程的当前工作目录中运行。
    //属于使用 QProcess 启动的 GUI 应用程序的窗口的定位和屏幕 Z 顺序由底层窗口系统控制。
    //对于 Qt 5 应用程序,可以使用 -qwindowgeometry 命令行选项指定定位;
    //X11 应用程序通常接受 -geometry 命令行选项。
    //注意:在 QNX 上,由于操作系统的限制,设置工作目录可能会导致除 QProcess 调用者线程之外的所有应用程序线程在生成过程中暂时冻结。
    //Synchronous Process API
    //QProcess 提供了一组函数,允许它在没有事件循环的情况下使用,通过挂起调用线程直到发出某些信号:
    //waitForStarted() 阻塞直到进程启动。
    //waitForReadyRead() 阻塞,直到新数据可用于在当前读取通道上读取。
    //waitForBytesWritten() 阻塞,直到一个有效负载的数据写入进程。
    //waitForFinished() 阻塞直到进程完成。
    //从主线程(调用 QApplication::exec() 的线程)调用这些函数可能会导致您的用户界面冻结。
    //下面的示例运行 gzip 来压缩字符串“Qt Rocks!”,没有事件循环:
    /*
        QProcess gzip;
        gzip.start("gzip",QStringList()<<"-c");
        if(!gzip.waitForStarted())
            return false;

        gzip.write("Qt rocks!");
        gzip.closeWriteChannel();

        if(!gzip.waitForFinished())
            return false;
        QByteArray result = gzip.readAll();
    */
    //Notes for Windows Users
    //某些 Windows 命令(例如 dir)不是由单独的应用程序提供,而是由命令解释器本身提供。
    //如果您尝试使用 QProcess 直接执行这些命令,它将不起作用。
    //一种可能的解决方案是执行命令解释器本身(某些 Windows 系统上的 cmd.exe),并要求解释器执行所需的命令。
    void finished(int exitCode, QProcess::ExitStatus status);
    QScopedPointer<QProcess> m_process;
};

#endif

assistant.cpp

#include "assistant.h"

#include <QApplication>
#include <QByteArray>
#include <QDir>
#include <QLibraryInfo>
#include <QMessageBox>
#include <QStandardPaths>

Assistant::Assistant() = default;

//[static] bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
//断开对象发送方中的信号与对象接收方中的方法的连接。
//如果连接成功断开,则返回 true; 否则返回false。
//当所涉及的任何一个对象被破坏时,信号槽连接就会被移除。
//disconnect() 通常以三种方式使用,如以下示例所示。
//断开所有连接到对象信号的连接
/*
    disconnect(myObject,nullptr,nullptr,nullptr)
*/
//相当于非静态重载函数
// myObject->disconnect();

//断开所有连接到特定信号的东西
//disconnect(myObject,SIGNAL(mySignal()),nullptr,nullptr);
//相当于非静态重载函数
//myObject->disconnect(SIGNAL(mySignal()));
//断开特定接收器
//disconnect(myObject,nullptr,myReceiver,nullptr);
//相当于非静态重载函数
//myObject->disconnect(myReceiver);
//nullptr 可以用作通配符,分别表示“任何信号”、“任何接收对象”或“接收对象中的任何插槽”。
//发件人可能永远不会是 nullptr。 (您不能在一次调用中断开来自多个对象的信号。)
//如果信号是 nullptr,它会断开接收器和方法与任何信号的连接。 如果不是,则仅断开指定的信号。
//如果接收器是 nullptr,它会断开连接到信号的任何东西。 如果不是,则不会断开接收器以外的对象中的插槽。
//如果 method 是 nullptr,它会断开连接到接收器的任何东西。 
//如果没有,只有名为 method 的插槽将被断开连接,而所有其他插槽都将保持不变。 如果忽略接收器,则该方法必须为 nullptr,因此您无法断开所有对象上特定命名的插槽。

//最后,我们确保 Qt Assistant 在应用程序关闭的情况下正确终止。
//QProcess 的析构函数会终止进程,这意味着应用程序无法执行诸如保存用户设置之类的操作,这会导致设置文件损坏。
//为了避免这种情况,我们要求 Qt Assistant 在 Assistant 类的析构函数中终止。
Assistant::~Assistant()
{
    if (!m_process.isNull() && m_process->state() == QProcess::Running) {
        QObject::disconnect(m_process.data(), &QProcess::finished, nullptr, nullptr);
        //[slot] void QProcess::terminate()
        //尝试终止进程。
        //该进程可能不会因调用此函数而退出(它有机会提示用户输入任何未保存的文件等)。
        //在 Windows 上,terminate() 将 WM_CLOSE 消息发送到进程的所有顶级窗口,然后发送到进程本身的主线程。
        //在 Unix 和 macOS 上发送 SIGTERM 信号。
        m_process->terminate();
        //bool QProcess::waitForFinished(int msecs = 30000)
        //阻塞直到进程完成并发出了 finished() 信号,或者直到经过了 msecs 毫秒。
        //如果进程完成则返回真;
        //否则返回 false(如果操作超时,如果发生错误,或者如果这个 QProcess 已经完成)。
        //这个函数可以在没有事件循环的情况下运行。
        //在编写非 GUI 应用程序和在非 GUI 线程中执行 I/O 操作时,它很有用。
        m_process->waitForFinished(3000);
    }
}
//showDocumentation() 的实现现在很简单。
//首先,我们确保 Qt Assistant 正在运行
//然后我们通过进程的 stdin 通道发送显示页面的请求
//此处非常重要的是,该命令由行尾标记终止以刷新通道。
void Assistant::showDocumentation(const QString &page)
{
    if (!startAssistant())
        return;

    QByteArray ba("SetSource ");
    ba.append("qthelp://org.qt-project.examples.simpletextviewer/doc/");
    //qint64 QIODevice::write(const char *data, qint64 maxSize)
    //最多将 maxSize 个字节的数据从 data 写入设备。
    //返回实际写入的字节数,如果发生错误,则返回 -1。
    m_process->write(ba + page.toLocal8Bit() + '\n');
}
//! [1]

QString documentationDirectory()
{
    //QStringList 继承自 QList<QString>。
    //与 QList 一样,QStringList 也是隐式共享的。
    //它提供基于索引的快速访问以及快速插入和删除。
    //将字符串列表作为值参数传递既快速又安全。
    //QList 的所有功能也适用于 QStringList。
    //例如可以使用isEmpty()来测试列表是否为空,可以调用append()、prepend()、
    //insert()、replace()、removeAll()、removeAt()、removeFirst()等函数 、removeLast() 和 removeOne() 来修改 QStringList。
    //QStringList 提供了一些方便的函数,使处理字符串列表更容易:
    //Initializing
    //默认构造函数创建一个空列表。
    //您可以使用 initializer-list 构造函数来创建一个包含元素的列表:
    //QStringList fonts = {"Arial","Helvetica","Times"};
    //Adding Strings
    //可以使用 insert()、append()、operator+=() 和 operator<<() 函数将字符串添加到列表中。
    //operator<<() 可用于方便地将多个元素添加到列表中:
    //fonts << "Couries"<<"Verdana";
    //遍历字符串
    //要迭代列表,您可以使用索引位置或 QList 的 Java 样式和 STL 样式迭代器类型:
    /*
        for(int i = 0; i < fonts.size();++i)
            cout<<fonts.at(i).toLocal8bit().constData()<<Qt::endl;
    */
    //Java-style iterator:
    /*
        QStringListIterator javaStyleIterator(fonts);
        while(javaStyleIterator.hasNext())
            cout<<javaStyleIterator.next().toLocal8Bit().constData()<<Qt::endl;
    */
    //STL-style iterator:
    /*
        QStringList::const_iterator constIterator;
        for(constIterator = fonts.constBegin();constIterator!=fonts.constEnd();
            ++constIterator)
        cout<<(*constIterator).toLocal8Bit().constData()<<Qt::endl;
    */
    //QStringListIterator 类只是 QListIterator<QString> 的类型定义。
    //QStringList 还提供了 QMutableStringListIterator 类,它是 QMutableListIterator<QString> 的类型定义。
    //Manipulating the Strings
    //QStringList 提供了几个函数,允许您操作列表的内容。
    //您可以使用 join() 函数将字符串列表中的所有字符串连接成一个字符串(带有可选的分隔符)。 例如:
    /*
        QString str = fonts.join(", ");
        //str == "Arial,Helvetica,Times,Courier"
    */
    //join 的参数可以是单个字符或字符串。
    //要将字符串分解为字符串列表,请使用 QString::split() 函数:
    /*
        QStringList list;
        list = str.split(',');
        //list:["Arial","Helvetica","Times","Courier"]
    */
    //split 的参数可以是单个字符、字符串或 QRegularExpression。
    //此外,operator+() 函数允许您将两个字符串列表连接为一个。 要对字符串列表进行排序,请使用 sort() 函数。
    //QString list 还提供了 filter() 函数,它允许您提取一个新列表,
    //该列表仅包含那些包含特定子字符串(或匹配特定正则表达式)的字符串:
    //QStringList monospacedFonts = fonts.filter(QRegularExpression("Courier|Fixed"));
    //contains() 函数告诉您列表是否包含给定字符串
    //而 indexOf() 函数返回给定字符串第一次出现的索引。
    //另一方面,lastIndexOf() 函数返回字符串最后一次出现的索引。
    //最后,replaceInStrings() 函数依次对字符串列表中的每个字符串调用 QString::replace()。 例如:
    /*
        QStringList files;
        files<<"$QTDIR/src/moc/moc.y"
             <<"$QTDIR/src/moc/moc.1"
             <<"$QTDIR/include/qconfig.h"

        files.replaceInStrings("$QTDIR","/usr/lib/qt");
        //files:["/usr/lib/qt/src/moc/moc.y",...]
    */
    QStringList paths;
#ifdef SRCDIR
    paths.append(QLatin1String(SRCDIR));
#endif
    paths.append(QLibraryInfo::path(QLibraryInfo::ExamplesPath));
    paths.append(QCoreApplication::applicationDirPath());
    //[static] QStringList QStandardPaths::standardLocations(QStandardPaths::StandardLocation type)
    //返回类型文件所属的所有目录。
    //目录列表从高优先级到低优先级排序,如果可以确定,
    //则从 writableLocation() 开始。 如果未定义类型的位置,则此列表为空。
    paths.append(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation));
    //[since 5.7] template <typename T> typename std::add_const<T>::type &qAsConst(T &t)
    //将 t 强制转换为 const T。
    //这个函数是 C++17 的 std::as_const() 的 Qt 实现,一个类似于 std::move() 的强制转换函数。
    //但是当 std::move() 将左值转换为右值时,该函数将非常量左值转换为常量左值
    //像 std::as_const() 一样,它不适用于右值,因为它不能在不留下悬空引用的情况下有效地实现右值。
    //它在 Qt 中的主要用途是防止隐式共享的 Qt 容器分离:  
    /*
        QString s = ...;
        for(QChar ch : s) //detaches 's' (performs  a deep-copy if 's' was shared)
            process(ch);
        for(QChar ch : qAsConst(s)) //ok,no detach attempt
            process(ch);        
    */ 
    //当然,在这种情况下,您可以(并且可能应该)首先将 s 声明为 const:
    /*
        const Qstring s = ...;
        for(QChar ch:s) //ok,no detach attempt on const objects
            process(ch);
    */
    //但通常这并不容易。
    //重要的是要注意 qAsConst() 不复制它的参数,
    //它只是执行一个 const_cast<const T&>(t)。
    //这也是它被设计为对右值失败的原因:返回的引用很快就会过时。 
    //因此,虽然这有效(但分离了返回的对象):
    /*
        for(QChar ch : funcReturningQString())
            process(ch); //ok,the returned object is kept alive for the loop's duration
    这不会:
        for(QChar ch : qAsConst(funcReturningQString()))
            process(ch); //ERROR: ch is copied from deleted memory
    
        //为了防止这个构造被编译(并在运行时失败),qAsConst() 有第二个已删除的重载,它绑定到右值。
    */
    for (const auto &dir : qAsConst(paths)) {
        const QString path = dir + QLatin1String("/documentation");
        if (QFileInfo::exists(path))
            return path;
    }
    return QString();
}


//启动 Qt 助手是在函数 startAssistant() 中通过简单地创建和启动 QProcess 来完成的。
//如果进程已经在运行,函数会立即返回。 否则,必须设置并启动该过程。
//要启动该过程,我们需要 Qt Assistant 的可执行名称以及在自定义模式下运行 
//Qt Assistant 的命令行参数。
//可执行文件的名称有点棘手,因为它取决于平台,但幸运的是它只在 macOS 上有所不同。
//可以在启动 Qt 助手时使用 -collectionFile 命令行参数更改显示的文档。
//在没有任何选项的情况下启动时,Qt Assistant 会显示一组默认的文档。
//安装 Qt 时,Qt Assistant 中的默认文档集包含 Qt 参考文档以及 Qt 附带的工具,例如 Qt Designer 和 qmake。
//在我们的示例中,我们通过将特定于应用程序的集合文件传递给进程的命令行选项,用自定义文档替换默认文档集。
//作为最后一个参数,我们添加了 -enableRemoteControl,它使 Qt Assistant 侦听其 stdin 通道的命令
//,例如显示文档中某个页面的命令。
//然后我们启动进程并等待它实际运行。 
//如果由于某种原因无法启动 Qt Assistant,startAssistant() 将返回 false。
bool Assistant::startAssistant()
{
    if (m_process.isNull()) {
        //[virtual] bool QIODevice::reset()
        //seek到随机访问设备的输入开始。成功返回真; 否则返回 false(例如,如果设备未打开)。
        //请注意,在 QFile 上使用 QTextStream 时,在 QFile 上调用 reset() 不会得到预期的结果,
        //因为 QTextStream 会缓冲文件。 改用 QTextStream::seek() 函数。
        m_process.reset(new QProcess());
        //[signal] void QProcess::finished(int exitCode, QProcess::ExitStatus exitStatus = NormalExit)
        //该信号在进程完成时发出。 exitCode是进程的退出码(只对正常退出有效),exitStatus是退出状态。
        //进程完成后,QProcess 中的缓冲区仍然完好无损。
        //您仍然可以读取进程在完成之前可能已写入的任何数据。
        QObject::connect(m_process.data(), &QProcess::finished,
                         m_process.data(), [this](int exitCode, QProcess::ExitStatus status) {
                             this->finished(exitCode, status);
                         });
    }

    //该进程正在运行并准备好进行读取和写入。
    if (m_process->state() != QProcess::Running) {
        //许多信息是在配置和构建 Qt 时建立的。
        //此类提供访问该信息的抽象。
        //通过使用此类的静态函数,应用程序可以获得有关应用程序在运行时使用的 Qt 库实例的信息。
        //QLibraryInfo::BinariesPath
        //安装 Qt 二进制文件(工具和应用程序)的路径。
        QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath);
#ifndef Q_OS_DARWIN
        app += QLatin1String("/assistant");
#else
        app += QLatin1String("/Assistant.app/Contents/MacOS/Assistant");
#endif

        const QString collectionDirectory = documentationDirectory();
        if (collectionDirectory.isEmpty()) {
            showError(tr("The documentation directory cannot be found"));
            return false;
        }
        //许多 QString 的成员函数被重载以接受 const char * 而不是 QString。
        //这包括复制构造函数、赋值运算符、比较运算符以及各种其他函数,例如 insert()、replace() 和 indexOf()。
        //这些函数通常经过优化,以避免为 const char * 数据构造 QString 对象。 例如,假设 str 是一个 QString,        
        //定义 QT_NO_CAST_FROM_ASCII 的应用程序(如 QString 文档中所述)无法访问 QString 的 const char * API。
        //了提供一种指定常量 Latin-1 字符串的有效方法,Qt 提供了 QLatin1String,它只是一个非常薄的 const char * 包装器。 使用QLatin1String,上面的示例代码变成
        QStringList args{QLatin1String("-collectionFile"),
                         collectionDirectory + QLatin1String("/simpletextviewer.qhc"),
                         QLatin1String("-enableRemoteControl")};

        m_process->start(app, args);
        //bool QProcess::waitForStarted(int msecs = 30000)
        //阻塞直到进程启动并发出了开始()信号,或者直到 msecs 毫秒已经过去。
        //如果进程启动成功则返回真; 否则返回 false(如果操作超时或发生错误)。
        //这个函数可以在没有事件循环的情况下运行。 在编写非 GUI 应用程序和在非 GUI 线程中执行 I/O 操作时,它很有用。        
        if (!m_process->waitForStarted()) {
            showError(tr("Unable to launch Qt Assistant (%1): %2")
                              .arg(QDir::toNativeSeparators(app), m_process->errorString()));
            return false;
        }
    }
    return true;
}
//! [2]

void Assistant::showError(const QString &message)
{
    //[static] QMessageBox::StandardButton QMessageBox::critical(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons = Ok, QMessageBox::StandardButton defaultButton = NoButton)
    //在指定的父小部件前面打开一个具有给定标题和文本的关键消息框。
    //标准按钮被添加到消息框中。 defaultButton 指定按下 Enter 时使用的按钮。
    //defaultButton 必须引用在按钮中给出的按钮。 如果 defaultButton 是 QMessageBox::NoButton,QMessageBox 会自动选择一个合适的默认值。
    QMessageBox::critical(QApplication::activeWindow(),
                          tr("Simple Text Viewer"), message);
}

void Assistant::finished(int exitCode, QProcess::ExitStatus status)
{
    //[static] QString QString::fromLocal8Bit(const char *str, qsizetype size)
    //返回用 8 位字符串 str 的第一个大小字符初始化的 QString。
    //如果 size 为 -1,则使用 strlen(str) 代替。
    //在 Unix 系统上,这等效于 fromUtf8(),在 Windows 上,使用的是系统当前代码页。
    //QByteArray QProcess::readAllStandardError()
    //无论当前的读取通道如何,此函数都会以 QByteArray 的形式返回进程标准错误中的所有可用数据。
    const QString stdErr = QString::fromLocal8Bit(m_process->readAllStandardError());
    if (status != QProcess::NormalExit) {
        showError(tr("Assistant crashed: ").arg(stdErr));
    } else if (exitCode != 0) {
        showError(tr("Assistant exited with %1: %2").arg(exitCode).arg(stdErr));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值