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

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

  • 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 项目实战 | 多界面编辑器_第1张图片

界面设计

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

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

添加资源文件 myImage.qrc:

Qt 项目实战 | 多界面编辑器_第2张图片

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

Qt 项目实战 | 多界面编辑器_第3张图片

菜单栏和工具栏:

Qt 项目实战 | 多界面编辑器_第4张图片

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

Qt 项目实战 | 多界面编辑器_第5张图片

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

Qt 项目实战 | 多界面编辑器_第6张图片

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

Qt 项目实战 | 多界面编辑器_第7张图片

帮助子菜单:

Qt 项目实战 | 多界面编辑器_第8张图片

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

Qt 项目实战 | 多界面编辑器_第9张图片

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

Qt 项目实战 | 多界面编辑器_第10张图片

创建子窗口类

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

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

Qt 项目实战 | 多界面编辑器_第11张图片

在 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

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

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

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

#include "mdichild.h"

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

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

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

void MainWindow::on_actionNew_triggered()
{
    //创建 MdiChild
    MdiChild* child = new MdiChild;
    //多文档区域添加子窗口
    ui->mdiArea->addSubWindow(child);
    //新建文件
    child->newFile();
    //显示子窗口
    child->show();
}

测试结果:

Qt 项目实战 | 多界面编辑器_第12张图片

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

Qt 项目实战 | 多界面编辑器_第13张图片

你可能感兴趣的:(Qt,qt,编辑器,开发语言)