【QT学习教程2】文件操作功能实现

目录

0. 前言

一、界面布局

二、文件操作功能的实现

2.1 思路整理

2.2 初始化界面功能的实现

2.3 判断当前文本是否可以"修改"的功能实现

2.4 新建文本功能的实现

2.5 保存功能的实现

2.6 另存为功能的实现

2.7 文件保存功能的实现

2.8 加载文件功能的实现

2.9 界面退出、文本打开、关闭、剪切、粘贴、复制、撤销功能的实现

2.10 信号与槽机制

2.11 误触关闭预防功能的实现

2.12 文本查找功能的实现


0. 前言

所有教程是给自己看,学的是思想、逻辑,操作不一定100%完整,见谅。

一、界面布局

界面布局属于比较简单的内容,直接贴上结果图。

【QT学习教程2】文件操作功能实现_第1张图片

【QT学习教程2】文件操作功能实现_第2张图片

【QT学习教程2】文件操作功能实现_第3张图片

二、文件操作功能的实现

2.1 思路整理

第一,用户打开界面总会存在一个初始界面。

第二,用户可以新建一个文本,但前提是当前文本内容为“空”或已保存。

第三,用户可以保存当前文本,无论是否文本内容为”空“,都可以保存。

第四,用户可以打开一个已经存在的文本,但前提是当前文本内容为”空“或已保存。

第五,用户可以关闭当前文本界面,不退出操作界面,但前提是当前文本内容为”空“或已保存。

第六,用户可以退出当前操作界面,但前提是当前文本内容为”空“或已保存。

第七,用户可以对当前文本内容进行复制、粘贴、撤销等操作。

第八,用户可以通过输入关键词在当前文本内查找有效信息。

第九,操作界面其他一些附件的设计。

综上所述,可以发现,只要是对文本进行任何”修改“类的工作,都需要确认当前文本的状态,故因此可以设计一个专门的函数处理这件事;此外,各个函数模块单独设计即可。

2.2 初始化界面功能的实现

首先在当前.h文件的class中添加如下函数声明,权限为public

    void InitWindow();                             //初始化界面

其次定义两个成员变量,权限为private

    bool isUntitled;                               //true表示文件没被保存过,false表示有
    QString curFile;                               //当前文件的路径

最后,在当前.cpp文件中定义初始化界面函数:

void QtDemo01::InitWindow() {
    isUntitled = true;                             //初始化文件为未保存状态
    curFile = tr("未命名.txt");                    //初始化文件名
    setWindowTitle(curFile);                       //设置当前窗口的标题
    ui.textEdit->clear();
    ui.textEdit->setVisible(true);                 //使界面可见,可以修改为false看看效果
}

2.3 判断当前文本是否可以"修改"的功能实现

首先,在.h文件的class中添加函数声明,权限为public

    bool maybeSave();                //判断当前文本内容是否为"空"或已保存

最后,在.cpp文件中定义新建文本函数:

//判断当前文本内容是否需要保存
bool QtDemo01::maybeSave() {
    if (ui.textEdit->document()->isModified()) {        //文件初始状态是空白,若文件被更改了(增加了新内容),则考虑重新保存
        QMessageBox box;                                //生成对话框对象
        box.setWindowTitle("警告");                     //设置对话框标题
        box.setIcon(QMessageBox::Warning);              //使用QMessageBox自带的警告图片作为对话框图片
        box.setText(curFile + "尚未保存,是否保存?");    //设置对话框要显示的文本
        /*
            自定义按钮,其中按钮中的角色一般有有如下几种:
            1. QMessageBox::AcceptRole          --->    单击按钮使对话框被接受
            2. QMessageBox::RejectRole          --->    单击按钮使对话框被拒绝
            3. QMessageBox::YesRole             --->    按钮类似于"是"的按钮
            4. QMessageBox::NoRole              --->    按钮类似于"不"的按钮
            注:按钮的顺序与程序编写顺序无关,与平台有关.
        */
        QPushButton* yesBtn = box.addButton("是(&Y)"
                        ,QMessageBox::YesRole);         //生成文本内容为“是(&Y)”的Yes按钮是按钮
        QPushButton* noBox = box.addButton("否(&N)", QMessageBox::NoRole);
        QPushButton* cancelBox = box.addButton("取消", QMessageBox::RejectRole); 
        box.exec();                                     //循环等待用户下达指令
        if (box.clickedButton() == yesBtn) {            //当用户点击了yes按钮,转到保存函数内操作.addButton与clickedButton对应使用
            return save();
        }
        else if (box.clickedButton() == noBox) {        //当用户点击no按钮,则不保存文本界面
            return true;
        }
        else if(box.clickedButton() == cancelBox) {     //当用户点击cancel按钮,则取消当前操作
            return false;
        }
    }
    return true;
}

上述函数的目的是:若当前文本内容为空,则返回true,也就是告诉系统,可以创建一个新文本。若当前文本内容非空,则进入用户确认阶段。首先,利用QMessageBox类创建对话框,该对话框往往是用于提示、警告用户。用户通过QPushButton按钮类实现,按钮发送指令给系统。仅当用户选择 no,当前文本会被清空。若用户选择 yes,则会对当前文本保存 --> 进入Save()函数。

2.4 新建文本功能的实现

.h文件的class中添加,权限为public

    void newFile();                                //新建文件

.cpp文件中添加函数定义:

void QtDemo01::newFile() {
    if (maybeSave()) {
        InitWindow();
    }
}

上述函数的目的:首先判定当前文本内容是否为“空”或已保存,若用户选择 no 或 当前文本为“空”,则刷新界面,相当于新建了一个新文本。

2.5 保存功能的实现

同样地,权限为public

    bool save();                                   //保存操作

同样地,定义函数:

bool QtDemo01::save() {
    if (isUntitled) {                                   //文件没有保存过
        return saveAs();
    }
    else {
        return saveFile(curFile);
    }
}

上述函数的目的:由于界面初始化作用,若第一个保存文件,则直接进入saveAs()函数;反之,若此时isUntitled == false,则表明当前文本已经被保存过。也就是说,不用重新选择保存路径了,因为正在操作的文本已经有了一个保存路径。没错,这个布尔类型变量isUntitled的目的就是判断是否需要选择保存路径!那么saveAs()与saveFile(const QString& filename)的区别就在于是否需要选择路径的问题。

2.6 另存为功能的实现

权限public

    bool saveAs();                                 //另存为操作

函数定义:

bool QtDemo01::saveAs() {
    /*
        使用用户选择的文件名保存文件.在此处,表明保存文件名为curFile这个文件.
        参数二:保存文件对话框的标题
        参数三:用户选择的文件名
        参数四(可选):保存类型
    */
    QString filename = QFileDialog::getSaveFileName(this, tr("另存为"), curFile);
    if (filename.isEmpty())
        return false;
    return saveFile(filename);
}

如2.5所述,我们选择分两步走。第一步:选择保存路径;第二步:保存文件。所以,关键点在与saveFile(const QString& filename)函数的编写!QFileDialog是文件对话框的类,无论是打开文件、保存文件都是使用这个类。

2.7 文件保存功能的实现

权限public

    bool saveFile(const QString& filename);        //保存文件

函数定义:

//真正的文件保存操作
bool QtDemo01::saveFile(const QString& filename) {
    QFile file(filename);                               //加载待操作文件,filename是待操作文件

    /*
        open()函数内的参数是文件的打开模式.总共有如下几种:
        1. QFile::ReadOnly                  只读
        2. QFile::WriteOnly                 只写
        3. QFile::ReadWrite                 可读可写
        4. QFile::Append                    追加模式,写入的数据会被追加到文件末尾
        5. QFile::Truncate                  重写模式,写入的数据将原有数据全部清除(此模式要与ReadOnly或WriteOnly搭配使用)
        6. QFile::Text                      读取文件时,将结尾结束符\r\n转化成\n.写入文件时,将结尾结束符转换成本地模式,如\n -> \r\n
    */
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        /*
            warning对话框
            参数二:对话框标题
            参数三:对话框内容
            参数四:(可选)对话框按钮
        */
        QMessageBox::warning(this, tr("多文档编辑器"), tr("无法写入文件 %1: \n %2").arg(filename).arg(file.errorString()));
        return false;
    }
    QTextStream out(&file);                             //构造一个指向file文件的QTextStream对象,输出数据流
    QApplication::setOverrideCursor(Qt::WaitCursor);    //鼠标指针变为等待状态
    out << ui.textEdit->toPlainText();                  //获取文本内容(toPlainTest()函数获取),并且传输给out文本流( << 运算符 )
    QApplication::restoreOverrideCursor();              //鼠标指针恢复初始状态
    isUntitled = false;
    curFile = QFileInfo(filename).canonicalFilePath();  //获得文件标准路径
    setWindowTitle(curFile);
    return true;
}

如上述代码,QFile包含有关于文件操作的类,包括读\写文件,打开\关闭文件等等,通常与QTextStream一同使用。文件的打开方式也有多种,如上述代码注释所写。还记得QMessageBox吗?是的,它的作用就是 提示 或 警告 用户。QTextStream式处理文件流相关的类,能够对文件进行读取和写入。QApplication这个类比较宽泛。这里使用主要是针对正在保存文件时,希望鼠标达到“转圈圈”的效果;当读取完成(<<运算符表示将ui.textEdit->toPlainText()的内容输入到文件流out中)后,鼠标恢复正常。而之后,因为保存路径已经选择,所以isUntitled改变状态,当前文件操作界面的标题也随之改变。

2.8 加载文件功能的实现

声明,权限publilc

    bool loadFile(const QString& filename);        //打开文件

定义:

bool QtDemo01::loadFile(const QString& filename) {
    QFile file(filename);
    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);                          //构造一个指向file文件的QTextStream对象,输入数据流
    QApplication::setOverrideCursor(Qt::WaitCursor);//鼠标保持旋转状态
    ui.textEdit->setPlainText(in.readAll());        //将文件内容读取出来,并写入在ui编辑器中.
    QApplication::restoreOverrideCursor();
    curFile = QFileInfo(filename).canonicalFilePath();
    setWindowTitle(curFile);
    return true;
}

趁热打铁,看懂了2.7,那么这里难度就不大了,细节应该不用解释了,只是将输出文件流out改成了输入文件流in。

2.9 界面退出、文本打开、关闭、剪切、粘贴、复制、撤销功能的实现

这里直接放代码,但是该代码具体放置在那个函数里,后面再讲

1、退出界面
    ui.textEdit->setVisible(false);
    //qApp指向所有应用程序的全局指针
    qApp->quit();
2、打开文件
    QString FileName = QFileDialog::getOpenFileName(this,tr("请选择需要打开的文件"));
    if (!FileName.isEmpty()) {
        loadFile(FileName);
        ui.textEdit->setVisible(true);
    }
3、关闭当前文本
    ui.textEdit->setVisible(false);
4、撤销
    ui.textEdit->undo();
5、剪切
    ui.textEdit->cut();
6、复制
    ui.textEdit->copy();
7、粘贴
    ui.textEdit->paste();

可以看出,这都是ui.textEdit函数内置的函数。

2.10 信号与槽机制

Qt中特殊的通信方式:信号与槽。这里不具体介绍,信号与槽机制的关键在于槽函数的编写,而槽函数的名称可以自己定义,也可以按照Qt默认的方式定义。若自己定义,则需要自己写connect连接函数;若按照Qt默认的方式定义,则不需要写connect函数。如何选择呢?很简单,若Qt Creator中提供了发送者、信号、接收者,那我们就用默认的方式定义槽函数即可。

在本项目中,所有文件的操作都在Qt Creator中定义了,也就是说,Qt Creator中提供了发送者(各个action)、信号(triggered())、接收者(一般都是this)。

默认槽函数的定义方式如下:

void on__(); 
   
  

举个栗子,若我想定义打开文件的槽函数,首先,找到ui界面中相关action的信息,如下图。

【QT学习教程2】文件操作功能实现_第4张图片

对象名是:action_New,信号是triggered()

那么定义的槽函数就是:

void on_action_New_triggered() {
    ……
}

举一反三,所有的槽函数如下,

【QT学习教程2】文件操作功能实现_第5张图片

还记得2.9所述的功能需要放在哪里吗?放在各个槽函数内即可。

void QtDemo01::on_action_Open_triggered() {
    if (maybeSave()) {
        QString FileName = QFileDialog::getOpenFileName(this,tr("请选择需要打开的文件"));
        if (!FileName.isEmpty()) {
            loadFile(FileName);
            ui.textEdit->setVisible(true);
        }
    }
}

void QtDemo01::on_action_Exit_triggered() {
    on_action_Close_triggered();
    //qApp指向所有应用程序的全局指针
    qApp->quit();
}

void QtDemo01::on_action_Close_triggered() {
    if (maybeSave()) {
        ui.textEdit->setVisible(false);
    }
}

void QtDemo01::on_action_Revoke_triggered() {
    if (maybeSave()) {
        ui.textEdit->undo();
    }
}

void QtDemo01::on_action_Cut_triggered() {
    if (maybeSave()) {
        ui.textEdit->cut();
    }
}

void QtDemo01::on_action_Copy_triggered() {
    if (maybeSave()) {
        ui.textEdit->copy();
    }
}

void QtDemo01::on_action_Paste_triggered() {
    if (maybeSave()) {
        ui.textEdit->paste();
    }
}

2.11 误触关闭预防功能的实现

在正常使用软件期间,若用户误触软件界面的关闭按钮,且当前文本中有尚未保存的内容时,会直接损失数据。为了解决这种情况,需要对关闭事件的函数重新改写。

在.h文件的class类中声明关闭事件的函数。

protected:
    void closeEvent(QCloseEvent* event);

在.cpp文件中重写函数:

/*
    该函数目的是:
    当鼠标点击主界面右上角 X 时,文件没来得及保存.
    为了解决这种现象,重写CloseEvent函数,确保在maybeSave()返回true时才会直接关闭,否则询问后再关闭.
*/
void QtDemo01::closeEvent(QCloseEvent* event) {
    if (maybeSave()) {
        event->accept();
    }
    else {
        event->ignore();
    }
}

QCloseEvent类是关闭事件的类,至于事件,本人也是刚刚开始学习,所以此处见解较浅。函数的目的是:先检查当前文本是否为“空”或已保存,再执行关闭程序。

注意,这里的函数名closeEvent,不能修改一个,因为它是重写了,并不是重新定义!。

2.12 文本查找功能的实现

.h内需要做三件事,第一件事是类的前置声明(置于头文件下)、第二件事是创建两个对象(权限为private)、第三件事是声明两个函数(函数权限为public)。

    //类的前置声明
    class QLineEdit;
    class QDialog;
    QLineEdit* findLineEdit;
    QDialog* findDlg;

第一个参数是单行输入框的类对象声明。

第二个参数是查找对话框的对象声明。

    void on_action_Find_triggered();
    void showFindText();

第一个函数是默认槽函数,目的是按下查找action_Find,弹出文件查找框。

第二个函数是文件查找框的各项功能函数。

.cpp文件中首先在构造函数内通过代码的方式写好查找对话框的界面

    this->findDlg = new QDialog(this);                                      //创建一个对话框
    this->findDlg->setWindowTitle(tr("查找"));                              //设定该对话框标题是“查找”
    this->findLineEdit = new QLineEdit(this->findDlg);                      //在findDlg对话框中嵌入单行输入框
    QPushButton* btn = new QPushButton(tr("查找下一个"), this->findDlg);    //在findDlg对话框中嵌入一个按钮,该按钮名称是"查找下一个"
    QVBoxLayout* layout = new QVBoxLayout(this->findDlg);                   //对话框设置为垂直布局(如果在ui中设计多次,这应该很好理解)
    layout->addWidget(this->findLineEdit);                                  //在layout中加入单行输入框
    layout->addWidget(btn);                                                 //在layout中加入按钮Btn
    QObject::connect(btn, SIGNAL(clicked()), this, SLOT(showFindText()));   //信号与槽机制,当btn按下,发送信号clicked().QtDemo01Class接受信号,发动槽函数showFindText()

    //普通标签(可能会被覆盖)
    QLabel* statusLabel = new QLabel;
    statusLabel->setMinimumSize(150, 20);                                   //设置Label框的最小宽、最小高
    statusLabel->setFrameShape(QFrame::Box);                                //设置Label框的形状
    statusLabel->setFrameShadow(QFrame::Sunken);                            //设置Label框的阴影
    statusLabel->setText("欢迎使用文本操作界面");                           //设置Label框的文字内容
    ui.statusBar->addWidget(statusLabel);                                   //在ui界面的状态栏(状态栏在ui界面的最下端)加入一个Label(加入顺序从左至右)

    //永久标签
    QDate* nowDate = new QDate;
    QLabel* permenentLabel = new QLabel;
    permenentLabel->setMinimumSize(100, 20);
    permenentLabel->setFrameShape(QFrame::Box);
    permenentLabel->setFrameShadow(QFrame::Sunken);
    permenentLabel->setText(tr("今天是: %1 ").
        arg(nowDate->currentDate().toString("yyyy-MM-dd")));
    ui.statusBar->addPermanentWidget(permenentLabel);                        //永久对话框始终在最右端

接下来定义两个函数

void QtDemo01::on_action_Find_triggered() {
    findDlg->setVisible(true);
}

需要查找对话框出现,只需要设置为可见即可。

void QtDemo01::showFindText() {
    QString str = findLineEdit->text();                 //获取要查找的字符串
    /*
        textEdit中内置了查找函数.
        参数一: 查找的字符串
        参数二:查找的方式,常用的有以下几种,若不指定,则默认向前查找
            1. QTextDocument::FindBackward          --->    向后查找
            2. QTextDocument::FindCaseSensitively   --->    区分大小写
            3. QTextDocument::FindWholeWords        --->    全词匹配
    */
    if (ui.textEdit->find(str, QTextDocument::FindBackward)) {
        QPalette palette = ui.textEdit->palette();
        palette.setColor(QPalette::Highlight, palette.color(QPalette::Active, QPalette::Highlight));
        ui.textEdit->setPalette(palette);
    }
    else {
        QMessageBox::warning(this, tr("查找"), tr("查找不到您输入的信息  %1 !").arg(str));
    }
}

if语句内是查找后,将查找到的内容高亮显示。QPalette是调色板,高亮的本质就是改变字体背景颜色即可。

else语句内是什么?是的,还是QMessageBox类,就是 提示 和 警告 用户。若整篇阅读下来或自己敲下来,对这个类将会非常熟悉。

 

 

你可能感兴趣的:(qt,学习,开发语言)