[Qt及Qt Quick开发实战精解] 第1章 多文档编辑器

Posted linuxandmcu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Qt及Qt Quick开发实战精解] 第1章 多文档编辑器相关的知识,希望对你有一定的参考价值。

??这一章的例子是对《Qt Creator快速人门》基础应用篇各章节知识的综合应用, 也是一个规范的实例程序。之所以说其规范,是因为在这个程序中,我们对菜单什么时候可用/什么时候不可用、关闭程序时应该先保存已修改且尚未保存的文件等细节都做了严格的约束。而一个真正实用的应用程序,也就应该如此。

??本章应用了基础篇的众多知识点,但这里只是讲解程序流程与框架,没有涉及太多知识细节的讲解。这个实例主要是对主窗口部件的应用,所以可以学完《Qt Creator快速入门》的前5章再来学习本章,这样可以达到更好的效果。该实例是基于Qt中的MDI Example示例程序 的,它在Main Windows分类下。这个程序就是以QMainWindow类为主窗口,以QMdiArea类为多文档区域,以QTextEdit类为子窗口部件,从而实现了一个多文档 编辑器的应用。最终的运行效果如图1-1所示。

技术分享图片
图1-1 多文档编辑器界面


1.1 界面设计

??先进行界面的设计,这里主要是对主窗口菜单栏和工具栏的设计。打开Qt Creator,创建新的项目。(项目源码路径:src?1 ?1 - 1 ?myMdi)新建Qt Gui应用,项目名称myMdi,类名默认为MainWindow,基类默认为QMainWindow都不做改动。 完成后双击mainwindow. ui文件进人设计模式,然后添加各个菜单,所有的菜单动作如图1-2所示,最终的菜单栏和工具栏如图1-3所示。设计菜单时,如果将来触发这个菜单会弹出一个对话框进行详细设置,那么就在这个菜单文本后面添加"..."号,例如这里的“打开文件”菜单和“另存为”菜单。这里还要注意,添加动作时,一定要使动作名称和这里的Action编辑器中所使用的名称保持一致,因为在后面的程序中还要用到它们。添加工具栏的工具是用鼠标把Action编辑器的Action拖动到工具栏做到的,图片资源文件来自工程目录下的image文件夹。

技术分享图片
图1-2 Action编辑器

技术分享图片
图1-3 菜单栏与工具栏

??设计完菜单栏与工具栏后,向主窗口中心区域拖入一个MdiArea部件,并单击主窗口界面,按下Ctrl + G快捷键,使其处于栅格布局之中。可以看一下对象列表窗口,确保MdiArea部件的objectName是mdiArea,而文件菜单、编辑菜单、窗口菜单和帮助菜单的objectName分别是menuF、menuE、menuW和menuH;如果不是,需 要在属性栏中更改,因为后面的程序中要用到。

1.2 创建子窗口类

??为了实现多文档操作,需要向QMdiArea中添加子窗口,而为了可以更好地操作子窗口,必须子类化子窗口的中心部件。因为这里子窗口的中心部件使用了QTextEdit类,所以要实现自己的类,它必须继承自QTextEdit,然后在其中添加我们的功能函数。
??(项目源码路径:srcll -2myMdi)往项目中添加新文件,模板选择“C+ + 类”,类名为MdiChild,基类为QTextEdit,类型信息选择“继承自QWidget”。完成后在mdichild. h文件中添加代码:

#include <QWidget>
#include <QTextEdit>

class MdiChild:public QTextEdit
{
Q_OBJECT

public:
    explicit MdiChild(QWidget *parent = nullptr);

    void newFile(); // 新建文件
    bool loadFile(const QString &fileName); // 加载文件
    bool save(); // 保存操作
    bool saveAs(); // 另存为操作
    bool saveFile(const QString &fileName); // 保存文件
    QString getFileNameFromPath(); // 从文件路径中提取出文件名
    QString getCurFileName() { return curFile; } // 获得返回的当前文件名称(路径)

protected:
    void closeEvent(QCloseEvent *event);    // 关闭事件

private slots:
    void documentWasModified(); //文档被更改时,窗口显示更改状态标志

private:
    bool maybeSave();                       // 判断是否需要保存
    void setCurrentFile(const QString &fileName);  // 设置当前文件

    QString curFile; // 保存新建文件时自动产生的当前文件名称(路径)
    bool isUnsaved_flag; //该标志位判断文件是否为“未保存状态”,若是,则打开文件对话框执行“另存为”操作,否则直接保存
};

??这里在头文件中声明了11个函数,定义了两个变量。其中,currentFile()函数 返回当前的文件路径,只有一行代码,就直接在这里定义了。所以真正需要设计的只有10个函数,还有curFile与isUnsaved_flag两个变量,分别用于保存当前文件的路径和作为文件是否被保存过的标志。因为对于所有的应用程序,只有涉及新建、保存和关闭等操作时,都是使用的这些函数进行设置的,它们是一个整体,所以这里要将它们同时罗列出来。这些函数主要完成了下面几个操作:

  1. 新建文件操作newFile()
    • 设置窗口编号;
    • 设置文件未被保存过“isUnsaved_flag = true;”;
    • 保存文件路径,给curFile赋初值;
    • 设置子窗口标题;
    • 关联文档内容改变信号到显示文档更改状态标志槽documentWasModified()。
  2. 加载文件操作loadFile()
    • 打开指定的文件,并读取文件内容到编辑器;
    • 设置当前文件setCurrentFile(),该函数可以获取文件路径,完成文件和窗口状态的设置;
    • 关联文档内容改变信号到显示文档更改状态标志槽documentWasModified()。
  3. 保存操作save()
    • 如果文件没有被保存过(用isUnsaved_flag判断),执行另存为操作saveAs() ;
    • 否则直接保存文件saveFile(),该函数先打开指定文件,然后将编辑器的内容写入该文件,最后设置当前文件setCurrentFile()。
  4. 另存为操作saveAs()
    • 从文件对话框获取文件路径;
    • 如果路径不为空,则保存文件saveFile()。
  5. 关闭操作 closeEvent()
    • 如果maybeSave()函数返回为真,则关闭窗口。maybeSave()函数判断文档 是否被更改过,如果被更改过,则弹出对话框,让用户选择是否保存更改,或者取消关闭操作。如果用户选择保存更改,则返回保存操作save()的结果,如 果选择取消,则返回false。否则,直接返回true。
    • 如果maybeSave()函数返回为假,则忽略该事件。

??下面一次性贴出了mdichild.cpp中的所有代码,没有像书中一样分步骤贴出,并进行说明:

#include "mdichild.h"
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
#include <QApplication>
#include <QFileInfo>
#include <QFileDialog>
#include <QCloseEvent>
#include <QPushButton>

MdiChild::MdiChild(QWidget *parent) :
    QTextEdit(parent)
{
    // 这样可以在子窗口关闭时销毁这个类的对象
    setAttribute(Qt::WA_DeleteOnClose);

    // 初始isUntitled为true
    isUnsaved_flag = true;
}

// 新建文件
void MdiChild::newFile()
{
    // 设置窗口编号,因为窗口一直被保存,所以需要使用静态变量
    static int windowNumber = 1; //窗口编号从1开始

    // 新建的文档没有被保存过
    isUnsaved_flag = true;

    // 将当前文件命名为:未命名文档加窗口编号,窗口编号先使用再加1
    curFile = tr("未命名文档%1.txt").arg(windowNumber++);

    // 设置窗口标题,使用[*]可以在文档被更改后在文件名称后才显示”*“号
    setWindowTitle(curFile + "[*]" + tr(" - 多文档编辑器"));

    // 当文档内容被更改时发射contentsChanged()信号,执行documentWasModified()槽函数,在标题栏上显示'*'
    connect(document(), SIGNAL(contentsChanged()),
            this, SLOT(documentWasModified()));
}

// 文档被更改时,窗口显示更改状态标志
void MdiChild::documentWasModified()
{
    // 根据文档的isModified()函数的返回值,判断我们编辑器内容是否被更改了
    // 如果被更改了,参数为true,则setWindowModified()就会在设置了[*]号的地方显示“*”号
    setWindowModified(document()->isModified()); //setWindowModified为库函数
}

// 加载文件
bool MdiChild::loadFile(const QString &fileName)
{
    // 新建QFile对象
    QFile file(fileName);

    // 只读方式打开文件,出错则打开消息提示对话框,并返回false
    if (!file.open(QFile::ReadOnly | QFile::Text))
    {
        // %1和%2分别可以被后面的arg()中的fileName和file.errorString()代替
        QMessageBox::warning(this, tr("多文档编辑器"),
                             tr("无法读取文件 %1:
%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;
}

// 设置当前文件,将加载文件的路径保存到fileName中
void MdiChild::setCurrentFile(const QString &fileName)
{
    // canonicalFilePath()可以除去路径中的符号链接,“.”和“..”等符号
    curFile = QFileInfo(fileName).canonicalFilePath();

    // 文件已经被保存过了
    isUnsaved_flag = false;

    // 文档没有被更改过
    document()->setModified(false);

    // 窗口不显示被更改标志-'*'
    setWindowModified(false);

    // 设置窗口标题,userFriendlyCurrentFile()返回文件名
    setWindowTitle(getFileNameFromPath() + "[*]");
}

// 从文件路径中提取出文件名
QString MdiChild::getFileNameFromPath()
{
    return QFileInfo(curFile).fileName(); // 从文件路径中提取文件名
}

// 保存操作
bool MdiChild::save()
{
    if (isUnsaved_flag)
    { // 如果文件未被保存过,则执行另存为操作
        return saveAs();
    }
    else
    {
        return saveFile(curFile); //否则直接保存文件
    }
}

// 另存为操作
bool MdiChild::saveAs()
{
    // 获取文件路径,如果为空,则返回false
    QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"),curFile);
    if (fileName.isEmpty())
        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:
%2.")
                             .arg(fileName).arg(file.errorString()));
        return false;
    }

    QTextStream out(&file);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    out << toPlainText(); // 以纯文本文件写入
    QApplication::restoreOverrideCursor();

    setCurrentFile(fileName);
    return true;
}

//关闭事件
void MdiChild::closeEvent(QCloseEvent *event)
{
    if (maybeSave()) { // 如果maybeSave()函数返回true,则关闭窗口
        event->accept();
    } else {   // 用户选择不保存修改,maybeSave返回false,则这个时间会被忽略掉,什么都不做
        event->ignore();
    }
}

// 判断是否需要保存: 在窗口关闭时判断文件是否需要保存,并让用户选择
bool MdiChild::maybeSave()
{
    // 如果文档被更改过,弹出警告框,让用户做出选择
    if (document()->isModified())
    {
        QMessageBox box;
        box.setWindowTitle(tr("多文档编辑器"));
        box.setText(tr("是否保存对“%1”的更改?")
                    .arg(getFileNameFromPath()));
        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
}

??

以上是关于[Qt及Qt Quick开发实战精解] 第1章 多文档编辑器的主要内容,如果未能解决你的问题,请参考以下文章

Qt_Quick开发实战精解_3

[Qt Creator 快速入门] 第5章 应用程序主窗口

QT开发编译问题备忘

C++Widgets编程(《Qt Creator快速入门》 第3版 学习笔记 )

Qt快速入门学习笔记(基础篇)

Qt5_TCP_Client01