• Qt 项目实战 | 多界面文本编辑器


    官方博客:https://www.yafeilinux.com/

    Qt开源社区:https://www.qter.org/

    参考书:《Qt 及 Qt Quick 开发实战精解》

    Qt 项目实战 | 多界面文本编辑器

    开发环境:Qt Creator 4.6.2 Based on Qt 5.9.6

    在这里插入图片描述

    界面设计

    这里主要是对主窗口和工具栏的设计。

    新建 Qt Gui 应用,项目名称 myMdi,类名默认 MainWindow,基类默认为 QMainWindow都不做改动。

    添加资源文件 myImage.qrc:

    在这里插入图片描述

    双击 mainwindow.ui 进入设计模式,添加菜单:

    在这里插入图片描述

    菜单栏和工具栏:

    在这里插入图片描述

    文件子菜单,注意有2个分隔符:

    在这里插入图片描述

    编辑子菜单,注意有1个分隔符:

    在这里插入图片描述

    窗口子菜单,注意有2个分隔符:

    在这里插入图片描述

    帮助子菜单:

    在这里插入图片描述

    设计完菜单栏和工具栏后,向主窗口中心区域拖入一个 MDI Area 部件,并单击主窗口界面,按下 Ctrl + G,使其处于栅格布局。

    在这里插入图片描述

    确保 MDI Area 部件的 objectName 是 mdiArea,而文件菜单、编辑菜单、窗口菜单、帮助菜单的 objectName 分别是 menuF、menuE、menuW、menuH。

    在这里插入图片描述

    创建子窗口类

    为了实现多文档操作,需要向 QMdiArea 中添加子窗口,我们需要子类化子窗口的中心部件。

    新建C++类文件,类名为 MdiChild,基类为 QTextEdit,类型信息选择“继承自 QWidget”:

    在这里插入图片描述

    在 mdichild.h 添加代码:

    #ifndef MDICHILD_H
    #define MDICHILD_H
    
    #include 
    #include 
    
    class MdiChild : public QTextEdit
    {
        Q_OBJECT
    private:
        QString curFile;  //当前文件路径
        bool isUntitled;  //作为当前文件是否被保存到硬盘的标志
    
        bool maybeSave();                              //是否需要保存
        void setCurrentFile(const Qstring& fileName);  //设置当前文件
    protected:
        void closeEvent(QCloseEvent* event);  //关闭事件
    public:
        explicit MdiChild(QWidget* parent = 0);
        void newFile();                            //新建文件
        bool loadFile(const Qstring& fileName);    //加载文件
        bool save();                               //保存操作
        bool saveAs();                             //另存为操作
        bool saveFile(const QString& fileName);    //保存文件
        QString userFriendlyCurrentFile();         //提取文件名
        QString currentFile() { return curFile; }  //返回当前文件路径
    private slots:
        void documentWasModified();  //文档被更改时,窗口显示更改状态标志
    };
    
    #endif  // MDICHILD_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在 mdichild.cpp 添加代码:

    #include "mdichild.h"
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    // 是否需要保存
    bool MdiChild::maybeSave()
    {
        // 如果文档被更改过
        if (document()->isModified())
        {
            QMessageBox box;
            box.setWindowTitle(tr("多文档编辑器"));
            box.setText(tr("是否保存对“%1”的更改?").arg(userFriendlyCurrentFile()));
            box.setIcon(QMessageBox::Warning);
            // 添加按钮,QMessageBox::YesRole可以表明这个按钮的行为
            QPushButton* yesBtn = box.addButton(tr("是(&Y)"), QMessageBox::YesRole);
            box.addButton(tr("否(&N)"), QMessageBox::NoRole);
            QPushButton* cancelBtn = box.addButton(tr("取消"), QMessageBox::RejectRole);
            // 弹出对话框,让用户选择是否保存修改,或者取消关闭操作
            box.exec();
            if (box.clickedButton() == yesBtn)
            {
                // 如果用户选择是,则返回保存操作的结果
                return save();
            }
            else if (box.clickedButton() == cancelBtn)
            {
                // 如果选择取消,则返回false
                return false;
            }
        }
        return true;  // 如果文档没有更改过,则直接返回true
    }
    
    // 设置当前文件
    void MdiChild::setCurrentFile(const QString& fileName)
    {
        // canonicalFilePath()可以除去路径中的符号链接,“.”和“..”等符号
        curFile = QFileInfo(fileName).canonicalFilePath();
        // 文件已经被保存过了
        isUntitled = false;
        // 文档没有被更改过
        document()->setModified(false);
        // 窗口不显示被更改标志
        setWindowModified(false);
        // 设置窗口标题,userFriendlyCurrentFile() 函数返回文件名
        setWindowTitle(userFriendlyCurrentFile() + "[*]");
    }
    
    // 关闭操作,在关闭事件中执行
    void MdiChild::closeEvent(QCloseEvent* event)
    {
        if (maybeSave())
        {
            // 如果 maybeSave() 函数返回 true,则关闭窗口
            event->accept();
        }
        else
        {
            // 否则忽略该事件
            event->ignore();
        }
    }
    
    MdiChild::MdiChild(QWidget* parent) : QTextEdit(parent)
    {
        // 设置在子窗口关闭时销毁这个类的对象
        setAttribute(Qt::WA_DeleteOnClose);
        // 初始 isUntitled 为 true
        isUntitled = true;
    }
    
    // 新建文件操作
    void MdiChild::newFile()
    {
        // 设置窗口编号,因为编号一直被保存,所以需要使用静态变量
        static int sequenceNumber = 1;
        // 新建的文档没有被保存过
        isUntitled = true;
        // 将当前文件命名为未命名文档加编号,编号先使用再加 1
        curFile = tr("未命名文档%1.txt").arg(sequenceNumber++);
        // 设置窗口标题,使用[*]可以在文档被更改后在文件名称后显示“*”号
        setWindowTitle(curFile + "[*]" + tr(" - 多文档编辑器"));
        // 当文档被更改时发射 contentsChanged() 信号,执行 documentWasModified() 槽函数
        connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
    }
    
    // 加载文件
    bool MdiChild::loadFile(const QString& fileName)
    {
        // 新建 QFile 对象
        QFile file(fileName);
    
        // 只读方式打开文件,出错则提示,并返回 false
        if (!file.open(QFile::ReadOnly | QFile::Text))
        {
            QMessageBox::warning(this, tr("多文档编辑器"),
                                 tr("无法读取文件 %1:\n%2.").arg(fileName).arg(file.errorString()));
            return false;
        }
        // 新建文本流对象
        QTextStream in(&file);
        // 设置鼠标状态为等待状态
        QApplication::setOverrideCursor(Qt::WaitCursor);
        // 读取文件的全部文本内容,并添加到编辑器中
        setPlainText(in.readAll());
        // 恢复鼠标状态
        QApplication::restoreOverrideCursor();
        // 设置当前文件
        setCurrentFile(fileName);
        connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
        return true;
    }
    
    // 保存操作
    bool MdiChild::save()
    {
        if (isUntitled)
        {
            // 如果文件未被保存过,则执行另存为操作
            return saveAs();
        }
        else
        {
            // 否则直接保存文件
            return saveFile(curFile);
        }
    }
    
    // 另存为操作
    bool MdiChild::saveAs()
    {
        // 使用文件对话框获取文件路径
        QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"), curFile);
        if (fileName.isEmpty())
        {
            // 如果文件路径为空,则返回 false
            return false;
        }
        // 否则保存文件
        return saveFile(fileName);
    }
    
    // 保存文件
    bool MdiChild::saveFile(const QString& fileName)
    {
        QFile file(fileName);
        if (!file.open(QFile::WriteOnly | QFile::Text))
        {
            QMessageBox::warning(this, tr("多文档编辑器"),
                                 tr("无法写入文件 %1:\n%2.").arg(fileName).arg(file.errorString()));
            return false;
        }
        QTextStream out(&file);
        // 设置应用程序强制光标为等待旋转光标(设置鼠标状态为等待状态)
        QApplication::setOverrideCursor(Qt::WaitCursor);
        // 以纯文本文件写入
        out << toPlainText();
        // 恢复光标(恢复鼠标状态)
        QApplication::restoreOverrideCursor();
        setCurrentFile(fileName);
        return true;
    }
    
    // 提取文件名
    QString MdiChild::userFriendlyCurrentFile()
    {
        // 从文件路径中提取文件名
        return QFileInfo(curFile).fileName();
    }
    
    // 文档被更改时,窗口显示更改状态标志
    void MdiChild::documentWasModified()
    {
        // 根据文档的isModified()函数的返回值,判断编辑器内容是否被更改了
        // 如果被更改了,就要在设置了[*]号的地方显示“*”号,这里会在窗口标题中显示
        setWindowModified(document->isModified());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185

    下面对这个类进行简单的测试。

    在 mainwindow.cpp 中添加以下代码:

    #include "mdichild.h"
    
    • 1

    因为程序中使用了中文,要在 main.cpp 文件中添加头文件和代码:

    #include 
    
    • 1
    // 解决 Qt 中文乱码问题
    // QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));
    
    • 1
    • 2
    • 3

    转到设计模式,在 Action Editor 中“新建文件”动作上右击,转到它的触发信号 triggered() 的槽,并更改如下:

    void MainWindow::on_actionNew_triggered()
    {
        //创建 MdiChild
        MdiChild* child = new MdiChild;
        //多文档区域添加子窗口
        ui->mdiArea->addSubWindow(child);
        //新建文件
        child->newFile();
        //显示子窗口
        child->show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试结果:

    在这里插入图片描述

    打开多个界面也没有问题。

    在这里插入图片描述

    实现菜单的功能

    更新菜单状态与新建文件操作

    功能描述:

    • 更新菜单状态,使得一些菜单在开始时处于不可用状态。
    • 实现新建文件操作

    更改 mainwindow.h:

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    class MdiChild;
    #include 
    
    namespace Ui
    {
        class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    private:
        Ui::MainWindow* ui;
        QAction* actionSeparator;    // 间隔器
        MdiChild* activeMdiChild();  // 活动窗口
    
    public:
        explicit MainWindow(QWidget* parent = 0);
        ~MainWindow();
    
    private slots:
        void on_actionNew_triggered();  // 新建文件菜单
        void updateMenus();             // 更新菜单
        MdiChild* createMdiChild();     // 创建子窗口
    };
    
    #endif  // MAINWINDOW_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    更改mainwindow.cpp:

    #include "mainwindow.h"
    
    #include 
    
    #include "mdichild.h"
    #include "ui_mainwindow.h"
    
    // 活动窗口
    MdiChild* MainWindow::activeMdiChild()
    {
        // 如果有活动窗口,则将其内的中心部件转换为 MdiChild 类型
        if (QMdiSubWindow* activeSubWindow = ui->mdiArea->activeSubWindow())
            return qobject_cast<MdiChild*>(activeSubWindow->widget());
        // 没有活动窗口,直接返回 0
        return 0;
    }
    
    MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        // 创建间隔器动作并在其中设置间隔器
        actionSeparator = new QAction(this);
        actionSeparator->setSeparator(true);
        // 更新菜单
        updateMenus();
        // 当有活动窗口时更新菜单
        connect(ui->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateMenus()));
    }
    
    MainWindow::~MainWindow() { delete ui; }
    
    void MainWindow::on_actionNew_triggered()
    {
        // 创建 MdiChild
        MdiChild* child = createMdiChild();
        // 新建文件
        child->newFile();
        // 显示子窗口
        child->show();
    }
    
    // 更新菜单
    void MainWindow::updateMenus()
    {
        // 根据是否有活动窗口来设置各个动作是否可用
        bool hasMdiChild = (activeMdiChild() != 0);
        ui->actionSave->setEnabled(hasMdiChild);
        ui->actionSaveAs->setEnabled(hasMdiChild);
        ui->actionPaste->setEnabled(hasMdiChild);
        ui->actionClose->setEnabled(hasMdiChild);
        ui->actionCloseAll->setEnabled(hasMdiChild);
        ui->actionTile->setEnabled(hasMdiChild);
        ui->actionCascade->setEnabled(hasMdiChild);
        ui->actionNext->setEnabled(hasMdiChild);
        ui->actionPrevious->setEnabled(hasMdiChild);
        //设置间隔器是否显示
        actionSeparator->setVisible(hasMdiChild);
        // 有活动窗口且有被选择的文本,剪切复制才可用
        bool hasSelection = (activeMdiChild() && activeMdiChild()->textCursor().hasSelection());
        ui->actionCut->setEnabled(hasSelection);
        ui->actionCopy->setEnabled(hasSelection);
        // 有活动窗口且文档有撤销操作时,撤销动作可用
        ui->actionUndo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isUndoAvailable());
        // 有活动窗口且文档有恢复操作时,恢复动作可用
        ui->actionRedo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isRedoAvailable());
    }
    
    // 创建子窗口部件
    MdiChild* MainWindow::createMdiChild()
    {
        // 创建 MdiChild 部件
        MdiChild* child = new MdiChild;
        //向多文档区域添加子窗口,child 为中心部件
        ui->mdiArea->addSubWindow(child);
        // 根据 QTextEdit 类的是否可以复制信号设置剪切复制动作是否可用
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool)));
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
        // 根据 QTextDocument 类的是否可以撤销恢复信号设置撤销恢复动作是否可用
        connect(child->document(), SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
        connect(child->document(), SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
        return child;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    测试:

    在这里插入图片描述

    在这里插入图片描述

    可以看出,有子窗口时,保存和另存为图标是亮的。

    在子窗口输入文字后,撤销图标变亮。

    在这里插入图片描述

    选中一些文字后,复制和粘贴图标变亮。

    在这里插入图片描述

    实现打开文件操作

    功能描述:

    实现打开文件操作。当要打开一个文件时,先判断这个文件是否已经被打开,这样就需要遍历多文档区域子窗口的文件,如果发现该文件已经打开,则直接设置该子窗口为活动窗口;否则加载要打开的文件,,并添加新的子窗口。

    更新 mainwindow.h:

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    class MdiChild;
    class QMdiSubWindow;
    
    #include 
    
    namespace Ui
    {
        class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    private:
        Ui::MainWindow* ui;
        QAction* actionSeparator;                              // 间隔器
        MdiChild* activeMdiChild();                            // 活动窗口
        QMdiSubWindow* findMdiChild(const QString& fileName);  // 查找子窗口
    
    public:
        explicit MainWindow(QWidget* parent = 0);
        ~MainWindow();
    
    private slots:
        void on_actionNew_triggered();             // 新建文件菜单
        void on_actionOpen_triggered();            // 打开文件菜单
        void updateMenus();                        // 更新菜单
        MdiChild* createMdiChild();                // 创建子窗口
        void setActiveSubWindow(QWidget* window);  // 设置活动子窗口
    };
    
    #endif  // MAINWINDOW_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    更新 mainwindow.cpp:

    #include "mainwindow.h"
    
    #include 
    #include 
    
    #include "mdichild.h"
    #include "ui_mainwindow.h"
    
    // 活动窗口
    MdiChild* MainWindow::activeMdiChild()
    {
        // 如果有活动窗口,则将其内的中心部件转换为 MdiChild 类型
        if (QMdiSubWindow* activeSubWindow = ui->mdiArea->activeSubWindow())
            return qobject_cast<MdiChild*>(activeSubWindow->widget());
        // 没有活动窗口,直接返回 0
        return 0;
    }
    
    // 查找子窗口
    QMdiSubWindow* MainWindow::findMdiChild(const QString& fileName)
    {
        QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
        // 利用foreach语句遍历子窗口列表,如果其文件路径和要查找的路径相同,则返回该窗口
        foreach (QMdiSubWindow* window, ui->mdiArea->subWindowList())
        {
            MdiChild* mdiChild = qobject_cast<MdiChild*>(window->widget());
            if (mdiChild->currentFile() == canonicalFilePath)
                return window;
        }
        return 0;
    }
    
    MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        // 创建间隔器动作并在其中设置间隔器
        actionSeparator = new QAction(this);
        actionSeparator->setSeparator(true);
        // 更新菜单
        updateMenus();
        // 当有活动窗口时更新菜单
        connect(ui->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateMenus()));
    }
    
    MainWindow::~MainWindow() { delete ui; }
    
    // 新建文件菜单
    void MainWindow::on_actionNew_triggered()
    {
        // 创建 MdiChild
        MdiChild* child = createMdiChild();
        // 新建文件
        child->newFile();
        // 显示子窗口
        child->show();
    }
    
    // 打开文件菜单
    void MainWindow::on_actionOpen_triggered()
    {
        // 获取文件路径
        QString fileName = QFileDialog::getOpenFileName(this);
        // 如果路径不为空,则查看该文件是否已经打开
        if (!fileName.isEmpty())
        {
            QMdiSubWindow* existing = findMdiChild(fileName);
            // 如果已经存在,则将对应的子窗口设置为活动窗口
            if (existing)
            {
                ui->mdiArea->setActiveSubWindow(existing);
                return;
            }
            // 如果没有打开,则新建子窗口
            MdiChild* child = createMdiChild();
            if (child->loadFile(fileName))
            {
                ui->statusBar->showMessage(tr("打开文件成功"), 2000);
                child->show();
            }
            else
            {
                child->close();
            }
        }
    }
    
    // 更新菜单
    void MainWindow::updateMenus()
    {
        // 根据是否有活动窗口来设置各个动作是否可用
        bool hasMdiChild = (activeMdiChild() != 0);
        ui->actionSave->setEnabled(hasMdiChild);
        ui->actionSaveAs->setEnabled(hasMdiChild);
        ui->actionPaste->setEnabled(hasMdiChild);
        ui->actionClose->setEnabled(hasMdiChild);
        ui->actionCloseAll->setEnabled(hasMdiChild);
        ui->actionTile->setEnabled(hasMdiChild);
        ui->actionCascade->setEnabled(hasMdiChild);
        ui->actionNext->setEnabled(hasMdiChild);
        ui->actionPrevious->setEnabled(hasMdiChild);
        //设置间隔器是否显示
        actionSeparator->setVisible(hasMdiChild);
        // 有活动窗口且有被选择的文本,剪切复制才可用
        bool hasSelection = (activeMdiChild() && activeMdiChild()->textCursor().hasSelection());
        ui->actionCut->setEnabled(hasSelection);
        ui->actionCopy->setEnabled(hasSelection);
        // 有活动窗口且文档有撤销操作时,撤销动作可用
        ui->actionUndo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isUndoAvailable());
        // 有活动窗口且文档有恢复操作时,恢复动作可用
        ui->actionRedo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isRedoAvailable());
    }
    
    // 创建子窗口部件
    MdiChild* MainWindow::createMdiChild()
    {
        // 创建 MdiChild 部件
        MdiChild* child = new MdiChild;
        //向多文档区域添加子窗口,child 为中心部件
        ui->mdiArea->addSubWindow(child);
        // 根据 QTextEdit 类的是否可以复制信号设置剪切复制动作是否可用
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool)));
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
        // 根据 QTextDocument 类的是否可以撤销恢复信号设置撤销恢复动作是否可用
        connect(child->document(), SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
        connect(child->document(), SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
        return child;
    }
    
    // 设置活动子窗口
    void MainWindow::setActiveSubWindow(QWidget* window)
    {
        // 如果传递了窗口部件,则将其设置为活动窗口
        if (!window)
            return;
        ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    测试:

    按下 Ctrl + O,选择要打开的文件。显示如下所示。

    在这里插入图片描述

    添加子窗口列表

    功能描述:

    每添加一个子窗口就可以在窗口菜单中罗列它的文件名,而且可以在这个列表中选择一个子窗口,将它设置为活动窗口。

    更改 mainwindow.h:

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    class MdiChild;
    class QMdiSubWindow;
    class QSignalMapper;
    
    #include 
    
    namespace Ui
    {
        class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    private:
        Ui::MainWindow* ui;
        QAction* actionSeparator;                              // 间隔器
        QSignalMapper* windowMapper;                           // 信号映射器
        MdiChild* activeMdiChild();                            // 活动窗口
        QMdiSubWindow* findMdiChild(const QString& fileName);  // 查找子窗口
    
    public:
        explicit MainWindow(QWidget* parent = 0);
        ~MainWindow();
    
    private slots:
        void on_actionNew_triggered();             // 新建文件菜单
        void on_actionOpen_triggered();            // 打开文件菜单
        void updateMenus();                        // 更新菜单
        MdiChild* createMdiChild();                // 创建子窗口
        void setActiveSubWindow(QWidget* window);  // 设置活动子窗口
        void updateWindowMenu();                   // 更新窗口菜单
    };
    
    #endif  // MAINWINDOW_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    更改 mainwindow.cpp:

    #include "mainwindow.h"
    
    #include 
    #include 
    #include 
    
    #include "mdichild.h"
    #include "ui_mainwindow.h"
    
    // 活动窗口
    MdiChild* MainWindow::activeMdiChild()
    {
        // 如果有活动窗口,则将其内的中心部件转换为 MdiChild 类型
        if (QMdiSubWindow* activeSubWindow = ui->mdiArea->activeSubWindow())
            return qobject_cast<MdiChild*>(activeSubWindow->widget());
        // 没有活动窗口,直接返回 0
        return 0;
    }
    
    // 查找子窗口
    QMdiSubWindow* MainWindow::findMdiChild(const QString& fileName)
    {
        QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
        // 利用foreach语句遍历子窗口列表,如果其文件路径和要查找的路径相同,则返回该窗口
        foreach (QMdiSubWindow* window, ui->mdiArea->subWindowList())
        {
            MdiChild* mdiChild = qobject_cast<MdiChild*>(window->widget());
            if (mdiChild->currentFile() == canonicalFilePath)
                return window;
        }
        return 0;
    }
    
    MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        // 创建间隔器动作并在其中设置间隔器
        actionSeparator = new QAction(this);
        actionSeparator->setSeparator(true);
        // 更新菜单
        updateMenus();
        // 当有活动窗口时更新菜单
        connect(ui->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateMenus()));
    
        // 创建信号映射器
        windowMapper = new QSignalMapper(this);
        // 映射器重新发送信号,根据信号设置活动窗口
        connect(windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
        // 更新窗口菜单,并且设置当窗口菜单将要显示的时候更新窗口菜单
        updateWindowMenu();
        connect(ui->menuW, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu()));
    }
    
    MainWindow::~MainWindow() { delete ui; }
    
    // 新建文件菜单
    void MainWindow::on_actionNew_triggered()
    {
        // 创建 MdiChild
        MdiChild* child = createMdiChild();
        // 新建文件
        child->newFile();
        // 显示子窗口
        child->show();
    }
    
    // 打开文件菜单
    void MainWindow::on_actionOpen_triggered()
    {
        // 获取文件路径
        QString fileName = QFileDialog::getOpenFileName(this);
        // 如果路径不为空,则查看该文件是否已经打开
        if (!fileName.isEmpty())
        {
            QMdiSubWindow* existing = findMdiChild(fileName);
            // 如果已经存在,则将对应的子窗口设置为活动窗口
            if (existing)
            {
                ui->mdiArea->setActiveSubWindow(existing);
                return;
            }
            // 如果没有打开,则新建子窗口
            MdiChild* child = createMdiChild();
            if (child->loadFile(fileName))
            {
                ui->statusBar->showMessage(tr("打开文件成功"), 2000);
                child->show();
            }
            else
            {
                child->close();
            }
        }
    }
    
    // 更新菜单
    void MainWindow::updateMenus()
    {
        // 根据是否有活动窗口来设置各个动作是否可用
        bool hasMdiChild = (activeMdiChild() != 0);
        ui->actionSave->setEnabled(hasMdiChild);
        ui->actionSaveAs->setEnabled(hasMdiChild);
        ui->actionPaste->setEnabled(hasMdiChild);
        ui->actionClose->setEnabled(hasMdiChild);
        ui->actionCloseAll->setEnabled(hasMdiChild);
        ui->actionTile->setEnabled(hasMdiChild);
        ui->actionCascade->setEnabled(hasMdiChild);
        ui->actionNext->setEnabled(hasMdiChild);
        ui->actionPrevious->setEnabled(hasMdiChild);
        //设置间隔器是否显示
        actionSeparator->setVisible(hasMdiChild);
        // 有活动窗口且有被选择的文本,剪切复制才可用
        bool hasSelection = (activeMdiChild() && activeMdiChild()->textCursor().hasSelection());
        ui->actionCut->setEnabled(hasSelection);
        ui->actionCopy->setEnabled(hasSelection);
        // 有活动窗口且文档有撤销操作时,撤销动作可用
        ui->actionUndo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isUndoAvailable());
        // 有活动窗口且文档有恢复操作时,恢复动作可用
        ui->actionRedo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isRedoAvailable());
    }
    
    // 创建子窗口部件
    MdiChild* MainWindow::createMdiChild()
    {
        // 创建 MdiChild 部件
        MdiChild* child = new MdiChild;
        //向多文档区域添加子窗口,child 为中心部件
        ui->mdiArea->addSubWindow(child);
        // 根据 QTextEdit 类的是否可以复制信号设置剪切复制动作是否可用
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool)));
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
        // 根据 QTextDocument 类的是否可以撤销恢复信号设置撤销恢复动作是否可用
        connect(child->document(), SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
        connect(child->document(), SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
        return child;
    }
    
    // 设置活动子窗口
    void MainWindow::setActiveSubWindow(QWidget* window)
    {
        // 如果传递了窗口部件,则将其设置为活动窗口
        if (!window)
            return;
        ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window));
    }
    
    // 更新窗口菜单
    void MainWindow::updateWindowMenu()
    {
        // 先清空菜单,然后再添加各个菜单动作
        ui->menuW->clear();
        ui->menuW->addAction(ui->actionClose);     // 关闭
        ui->menuW->addAction(ui->actionCloseAll);  // 关闭所有窗口
        ui->menuW->addSeparator();                 // 分隔符
        ui->menuW->addAction(ui->actionTile);      // 平铺
        ui->menuW->addAction(ui->actionCascade);   // 层叠
        ui->menuW->addSeparator();                 // 分隔符
        ui->menuW->addAction(ui->actionNext);      // 下一个
        ui->menuW->addAction(ui->actionPrevious);  // 前一个
        ui->menuW->addAction(actionSeparator);
        // 如果有活动窗口,则显示间隔器
        QList<QMdiSubWindow*> windows = ui->mdiArea->subWindowList();
        actionSeparator->setVisible(!windows.isEmpty());
        // 遍历各个子窗口
        for (int i = 0; i < windows.size(); i++)
        {
            MdiChild* child = qobject_cast<MdiChild*>(windows.at(i)->widget());
            QString text;
            // 如果窗口数小于 9,则设置编号为快捷键
            if (i < 9)
            {
                text = tr("&%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
            }
            else
            {
                text = tr("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
            }
            // 添加动作到菜单
            QAction* action = ui->menuW->addAction(text);
            // 设置动作可以选择
            action->setCheckable(true);
            // 设置当前活动窗口动作为选中状态
            action->setChecked(child == activeMdiChild());
            // 关联动作的触发信号到信号映射器的 map() 槽函数上,这个函数会发射 mapped() 信号
            connect(action, SIGNAL(triggered()), windowMapper, SLOT(map()));
            // 将动作与相应的窗口部件进行映射,在发射 mapped() 信号时就会以这个窗口部件为参数
            windowMapper->setMapping(action, windows.at(i));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190

    测试:

    在这里插入图片描述

    实现其他菜单功能

    进入设计模式,点击其他 action 的槽,更新它们的 triggered() 代码:

    // 保存菜单
    void MainWindow::on_actionSave_triggered()
    {
        if (activeMdiChild() && activeMdiChild()->save())
            ui->statusBar->showMessage(tr("文件保存成功"), 2000);
    }
    
    // 另存为菜单
    void MainWindow::on_actionSaveAs_triggered()
    {
        if (activeMdiChild() && activeMdiChild()->saveAs())
            ui->statusBar->showMessage(tr("文件保存成功"), 2000);
    }
    
    // 退出菜单
    void MainWindow::on_actionExit_triggered()
    {
        // 这里的 qApp 是 QApplication 对象的全局指针
        qApp->quit();  // 等效于 qApp->exit(0);
        // 这行代码相当于 QApplication::quit();
    }
    
    // 撤销菜单
    void MainWindow::on_actionUndo_triggered()
    {
        if (activeMdiChild())
            activeMdiChild()->undo();
    }
    
    // 恢复菜单
    void MainWindow::on_actionRedo_triggered()
    {
        if (activeMdiChild())
            activeMdiChild()->redo();
    }
    
    // 剪切菜单
    void MainWindow::on_actionCut_triggered()
    {
        if (activeMdiChild())
            activeMdiChild()->cut();
    }
    
    // 复制菜单
    void MainWindow::on_actionCopy_triggered()
    {
        if (activeMdiChild())
            activeMdiChild()->copy();
    }
    
    // 粘贴菜单
    void MainWindow::on_actionPaste_triggered()
    {
        if (activeMdiChild())
            activeMdiChild()->paste();
    }
    
    // 关闭菜单
    void MainWindow::on_actionClose_triggered() { ui->mdiArea->closeActiveSubWindow(); }
    
    // 关闭所有窗口菜单
    void MainWindow::on_actionCloseAll_triggered() { ui->mdiArea->closeAllSubWindows(); }
    
    // 平铺菜单
    void MainWindow::on_actionTile_triggered() { ui->mdiArea->tileSubWindows(); }
    
    // 层叠菜单
    void MainWindow::on_actionCascade_triggered() { ui->mdiArea->cascadeSubWindows(); }
    
    // 下一个菜单
    void MainWindow::on_actionNext_triggered() { ui->mdiArea->activateNextSubWindow(); }
    
    // 前一个菜单
    void MainWindow::on_actionPrevious_triggered() { ui->mdiArea->activatePreviousSubWindow(); }
    
    // 关于菜单
    void MainWindow::on_actionAbout_triggered() { QMessageBox::about(this, tr("关于本软件"), tr("开发者:UestcXiye")); }
    
    // 关于 Qt 菜单
    void MainWindow::on_actionAboutQt_triggered()
    {
        // 这里的 qApp 是 QApplication 对象的全局指针
        qApp->aboutQt();
        // 这行代码相当于 QApplication::aboutQt();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    测试:

    在这里插入图片描述

    完善程序功能

    保存窗口设置

    QSettings 类提供平台无关的永久保存应用程序设置的方法。

    在 mainwindow.h 文件新增私有函数声明:

    void readSettings();   // 读取窗口设置
    void writeSettings();  // 写入窗口设置
    
    • 1
    • 2

    再新增 protected 函数声明:

    void closeEvent(QCloseEvent* event);  // 关闭事件
    
    • 1

    在 mainwindow.cpp 文件添加代码:

    #include 
    #include 
    
    • 1
    • 2

    MainWindow 类的构造函数添加代码:

    // 初始窗口时读取窗口设置信息
    readSettings();
    
    • 1
    • 2

    三个函数的实现:

    // 读取窗口设置
    void MainWindow::readSettings()
    {
        QSettings settings("uestc_xiye", "myMdi");
        QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
        QSize size = settings.value("size", QSize(400, 400)).toSize();
        move(pos);
        resize(size);
    }
    
    // 写入窗口设置
    void MainWindow::writeSettings()
    {
        QSettings settings("uestc_xiye", "myMdi");
        // 写入位置信息
        settings.setValue("pos", pos());
        // 写入大小信息
        settings.setValue("size", size());
    }
    
    // 关闭事件
    void MainWindow::closeEvent(QCloseEvent* event)
    {
        // 先执行多文档区域的关闭操作
        ui->mdiArea->closeAllSubWindows();
        // 如果还有窗口没有关闭,则忽略该事件
        if (ui->mdiArea->currentSubWindow())
        {
            event->ignore();
        }
        else
        {
            // 在关闭前写入窗口设置
            writeSettings();
            event->accept();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    自定义右键菜单

    右键菜单默认是英文的,我们需要重新实现 QTextEdit 类的上下文菜单事件。

    首先在 mdichild.h 中添加头文件:

    #include 
    
    • 1

    新增 protected 函数:

    void contextMenuEvent(QContextMenuEvent* e);  // 右键菜单事件
    
    • 1

    函数定义:

    // 右键菜单事件
    void MdiChild::contextMenuEvent(QContextMenuEvent* e)
    {
        // 创建菜单,并向其中添加动作
        QMenu* menu = new QMenu;
        QAction* undo = menu->addAction(tr("撤销(&U)"), this, SLOT(undo()), QKeySequence::Undo);
        undo->setEnabled(document()->isUndoAvailable());
        QAction* redo = menu->addAction(tr("恢复(&R)"), this, SLOT(redo()), QKeySequence::Redo);
        redo->setEnabled(document()->isRedoAvailable());
        menu->addSeparator();
        QAction* cut = menu->addAction(tr("剪切(&T)"), this, SLOT(cut()), QKeySequence::Cut);
        cut->setEnabled(textCursor().hasSelection());
        QAction* copy = menu->addAction(tr("复制(&C)"), this, SLOT(copy()), QKeySequence::Copy);
        copy->setEnabled(textCursor().hasSelection());
        menu->addAction(tr("粘贴(&P)"), this, SLOT(paste()), QKeySequence::Paste);
        QAction* clear = menu->addAction(tr("清空"), this, SLOT(clear()));
        clear->setEnabled(!document()->isEmpty());
        menu->addSeparator();
        QAction* select = menu->addAction(tr("全选"), this, SLOT(selectAll()), QKeySequence::SelectAll);
        select->setEnabled(!document()->isEmpty());
        // 获取鼠标的位置,然后在这个位置显示菜单
        menu->exec(e->globalPos());
        // 最后销毁这个菜单
        delete menu;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    测试:

    在这里插入图片描述

    其他功能

    功能描述:

    • 在状态栏中显示编辑器中光标所在的行号和列号
    • 设置窗口的标题和状态栏的一些显示

    在 mainwindow.h 添加私有槽声明:

    void showTextRowAndCol(); // 显示文本的行号和列号
    
    • 1

    添加私有函数声明:

    void initWindow(); // 初始化窗口
    
    • 1

    修改函数:

    // 创建子窗口部件
    MdiChild* MainWindow::createMdiChild()
    {
        // 创建 MdiChild 部件
        MdiChild* child = new MdiChild;
        //向多文档区域添加子窗口,child 为中心部件
        ui->mdiArea->addSubWindow(child);
        // 根据 QTextEdit 类的是否可以复制信号设置剪切复制动作是否可用
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool)));
        connect(child, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
        // 根据 QTextDocument 类的是否可以撤销恢复信号设置撤销恢复动作是否可用
        connect(child->document(), SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
        connect(child->document(), SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
        // 每当编辑器中的光标位置改变,就重新显示行号和列号
        connect(child, SIGNAL(cursorPositionChanged()), this, SLOT(showTextRowAndCol()));
        return child;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    函数定义:

    // 显示文本的行号和列号
    void MainWindow::showTextRowAndCol()
    {
        // 如果有活动窗口,则显示其中光标所在的位置
        if (activeMdiChild())
        {
            // 因为获取的行号和列号都是从 0 开始的,所以我们这里进行了加 1
            int rowNum = activeMdiChild()->textCursor().blockNumber() + 1;
            int colNum = activeMdiChild()->textCursor().columnNumber() + 1;
            ui->statusBar->showMessage(tr("%1行 %2列").arg(rowNum).arg(colNum), 2000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面来看初始化窗口函数。

    添加头文件:

    #include 
    
    • 1

    定义初始化函数:

    // 初始化窗口
    void MainWindow::initWindow()
    {
        setWindowTitle(tr("多文档编辑器"));
        // 在工具栏上单击鼠标右键时,可以关闭工具栏
        ui->mainToolBar->setWindowTitle(tr("工具栏"));
        // 当多文档区域的内容超出可视区域后,出现滚动条
        ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        ui->statusBar->showMessage(tr("欢迎使用多文档编辑器"));
        QLabel* label = new QLabel(this);
        label->setFrameStyle(QFrame::Box | QFrame::Sunken);
        label->setText(tr("CSDN 博客"));
        // 标签文本为富文本
        label->setTextFormat(Qt::RichText);
        // 可以打开外部链接
        label->setOpenExternalLinks(true);
        ui->statusBar->addPermanentWidget(label);
        ui->actionNew->setStatusTip(tr("创建一个文件"));
        // 设置其他动作的状态提示
        ui->actionOpen->setStatusTip(tr("打开一个已经存在的文件"));
        ui->actionSave->setStatusTip(tr("保存文档到硬盘"));
        ui->actionSaveAs->setStatusTip(tr("以新的名称保存文档"));
        ui->actionExit->setStatusTip(tr("退出应用程序"));
        ui->actionUndo->setStatusTip(tr("撤销先前的操作"));
        ui->actionRedo->setStatusTip(tr("恢复先前的操作"));
        ui->actionCut->setStatusTip(tr("剪切选中的内容到剪贴板"));
        ui->actionCopy->setStatusTip(tr("复制选中的内容到剪贴板"));
        ui->actionPaste->setStatusTip(tr("粘贴剪贴板的内容到当前位置"));
        ui->actionClose->setStatusTip(tr("关闭活动窗口"));
        ui->actionCloseAll->setStatusTip(tr("关闭所有窗口"));
        ui->actionTile->setStatusTip(tr("平铺所有窗口"));
        ui->actionCascade->setStatusTip(tr("层叠所有窗口"));
        ui->actionNext->setStatusTip(tr("将焦点移动到下一个窗口"));
        ui->actionPrevious->setStatusTip(tr("将焦点移动到前一个窗口"));
        ui->actionAbout->setStatusTip(tr("显示本软件的介绍"));
        ui->actionAboutQt->setStatusTip(tr("显示Qt的介绍"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    最后在 MainWindow 类的构造函数添加:

    // 初始化窗口
    initWindow();
    
    • 1
    • 2

    小结

    程序最终演示:

    在这里插入图片描述

    项目源码

    CSDN:Multi-document Editor.zip

    GitHub:UestcXiye/Multi-document-Editor

  • 相关阅读:
    Python爬虫怎么挣钱?解析Python爬虫赚钱方式,轻轻松松月入两万,再也不用为钱发愁啦
    如何学习爬虫技术(问答版)
    促科技创新:高德数据优化篇之OceanBase最佳实践
    uniapp或者vue项目环境变量的模式来自动替换网络请求的baseUrl
    2. zk集群部署
    [附源码]计算机毕业设计JAVA卡牌交易网站
    01 `Linux`基础
    ardupilot开发 --- 外设适配器、外设拓展、AP_Periph 篇
    PostMan动态设置全局变量
    Spring如何使用XML实现自动装配Bean呢?
  • 原文地址:https://blog.csdn.net/ProgramNovice/article/details/133998629