Qt项目实战之文本编辑器---------第七集

上一集中我们实现了打开文档操作,那么紧接着就是去实现保存文档操作!我们想,打开文件,保存文件,是真的属于主窗口类么?其实并不是,文件的操作是属于子类的,进而保存文档的方法也应该是属于ChildWnd的成员方法。主窗口实际上也是间接调用。

在ChildWnd.h中添加函数:

public:
    bool saveDoc();//保存文档
    bool saveAsDoc();//另存为文档
    bool saveDocOpt(QString &docName);//真正执行保存操作
protected:
    void closeEvent(QCloseEvent *event);//关闭事件处理
private:
    bool promptSave();//尝试保存

saveDoc()函数定义:

bool ChildWnd::saveDoc()
{
    if(m_bSaved) return saveDocOpt(m_Currentpath);//保存过直接进行保存操作
    else saveAsDoc();//另存为

}

简单的直接保存,他会首先判断文档的保存状态,如果已经保存过了,那么久直接调用真正保存文档的函数saveDocOpt(),如果没有保存过,也就是可能是新建的文档,那么调用另存为函数。

saveAsDoc()函数定义:

bool ChildWnd::saveAsDoc()
{
    //另存为当然是提供一个文件窗口来提示保存
    QString docName=QFileDialog::getSaveFileName(this,
                                 "另存为",
                                 m_Currentpath,
                                 "HTML文档 (*.htm *html);;"
                                 "所有文件(*.*)");
    if(docName.isEmpty()) return false;
    else return saveDocOpt(docName);
}

当我们想到另存为这三个字,肯定脑海里是出现一个对话框,让我们设置文档的各种属性保存。就像电脑上面的一样,Qt也同样提供了这样的方式。利用类QFileDialog的getSaveFileName函数就会以类似的方式出现对话框。

当然我们这里获取getSaveFileName的返回值,再对它进行判断,如果为空那就返回false,否则调用真正的保存文档函数saveDocOpt。

saveDocOpt(QString& docName)函数实现:

bool ChildWnd::saveDocOpt(QString &docName)
{
    if(!(docName.endsWith(".htm",Qt::CaseInsensitive)||
         docName.endsWith(".html",Qt::CaseInsensitive))){
        //如果不是以指定格式结尾,添加指定格式
        docName+=".html";

    }
    QTextDocumentWriter writer(docName);
    bool isSuccess=writer.write(this->document());//write方法会返回写的状态
    if(isSuccess) setCurDoc(docName);//调研设置窗口状态函数
    return isSuccess;

}

首先进入函数以后,根据传经来的文件名字判断是不是预期设置的保存文件后缀 .htm和.html。如果不是以这两个文件后缀结尾的,那么就在文件之后添加上“.html”文件后缀。

closeEvent()函数实现:

void ChildWnd::closeEvent(QCloseEvent *event)
{
    if(promptSave()) event->accept();//接受事件
    else event->ignore();//忽略事件
}

这个函数实质就是当我们关闭文档窗口的时候,他会自动响应是否保存函数。

promptSave()函数实现:

bool ChildWnd::promptSave()
{
    //文档没有被修改
    if(!document()->isModified()) return true;

    QMessageBox::StandardButton result;
    result=QMessageBox::warning(this,
                               QString("系统提示"),
                               QString("文档%1已修改,是否保存?")
                                .arg(GetCurDocName()),
                                QMessageBox::Yes|
                                QMessageBox::Discard|
                                QMessageBox::No);
    if(result==QMessageBox::Yes){
        return saveDoc();
    }else if(result==QMessageBox::No){
        return false;
    }
    return true;//选择忽略就返回真


}

我们知道,就像原生的Wps一样,在没有保存已经修改的文档的时候会弹出来一个对话框提示保存。函数首先就会判断文档是否被编辑,如果编辑过,就直接返回true。这样再保存是没有必要的了。弹出消息对话框这里用到的就是QMessageBox::warning函数他会返回一个按钮,如果是yes调用saveDoc(),如果是No则不保存,直接返回false,如果是discard就返回真,也就是不保存。

在mainWindow.h添加函数定义:

 
public:
    void docSave();
    void docSaveAs();

private slots:
  void on_saveAction_triggered();

    void on_saveAsAction_triggered();

当然创建保存和另存为的槽函数都是利用设计模式中的转到槽实现的。

 

docSave()函数定义:

void MainWindow::docSave()
{
    if(activateChildWnd()&&activateChildWnd()->saveDoc()){
        statusBar()->showMessage("保存成功",3000);
    }
}

保存操作必须是活动子窗口,并且调用了保存文档saveDoc()函数之后为真,在状态栏设置提示信息。这里用到了statusBar()的showMessage函数。

docSaveAs()函数定义:

void MainWindow::docSaveAs()
{
    if(activateChildWnd()&&activateChildWnd()->saveAsDoc()){
        statusBar()->showMessage("保存成功",3000);
    }
}

槽函数定义:

void MainWindow::on_saveAction_triggered()
{
    docSave();//保存
}

void MainWindow::on_saveAsAction_triggered()
{
    docSaveAs();//另存为
}

这里的槽函数都是为保存和另存为的Action创建的槽函数,响应的函数也是相应的函数。

效果:

 

各文件源码:

ChildWnd.h

#ifndef CHILDWND_H
#define CHILDWND_H

#include<QTextEdit>

//思路就是主窗体创建一个子窗口编辑区,就是调用另外一个类的构造函数,那么就是ChildWnd这个类
class ChildWnd : public QTextEdit
{
    Q_OBJECT
public:
    ChildWnd();
    QString m_Currentpath;  //当前文档的路径
    void newDoc();          //新建文档
    QString GetCurDocName();//从文件路径中提取文档名
    bool loadDoc(const QString& docName);
    void setCurDoc(const QString& docName);//设置当前文档函数
    bool saveDoc();//保存文档
    bool saveAsDoc();//另存为文档
    bool saveDocOpt(QString &docName);//真正执行保存操作
protected:
    void closeEvent(QCloseEvent *event);//关闭事件处理
private:
    bool promptSave();//尝试保存
private slots:
    void docBeModified();   //文档修改时相应的槽函数
private:
    bool m_bSaved;          //文档是否保存

};

#endif // CHILDWND_H

mainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include"childwnd.h"
#include<QSignalMapper>
#include<QMdiSubWindow>
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void InitMainWindow();//初始化窗口函数
    void DocNew();
    void DocOpen();
    void docSave();
    void docSaveAs();
private slots:
    void on_newAction_triggered();
    void RefreshMenu();//刷新窗口菜单槽函数
    void addSubWindowListMenu();//刷新窗口信息函数
    void on_closeAction_triggered();

    void on_closeAllAction_triggered();

    void on_titleAction_triggered();

    void on_cascadeAction_triggered();

    void on_nextAction_triggered();

    void on_previousAction_triggered();

    void setActivSubWindow(QWidget*wnd);
    void on_openAction_triggered();

    void on_saveAction_triggered();

    void on_saveAsAction_triggered();

protected:
  void closeEvent(QCloseEvent *event);
private:
    void formatEnable();
    ChildWnd* activateChildWnd();//获取mdieara的活动子窗口
    //查找窗口函数
    QMdiSubWindow* FindChildWnd(const QString& docName);


private:
    Ui::MainWindow *ui;
    QSignalMapper* m_singnalmapper;// 创建一个信号映射器
};

#endif // MAINWINDOW_H

 childWnd.cpp

#include "childwnd.h"
#include<QFileInfo>
#include<QFileDialog>
#include<QTextDocumentWriter>
#include<QMessageBox>
#include<QCloseEvent>
ChildWnd::ChildWnd()
{
    //子窗口关闭时销毁类的实例对象,达到回收
    setAttribute(Qt::WA_DeleteOnClose);

    m_bSaved=false;//初始的时候编辑状态的未改变的
}

void ChildWnd::newDoc()
{
    static int wndSeqNum=1;
    m_Currentpath=QString("WPS 文档 %1").arg(wndSeqNum++);//每次创建文档的时候是名字不相同的,,利用wndSeqNum来达到目标

    //设置窗体标题,文档改动后加“*”号标识
    setWindowTitle(m_Currentpath+"[*]"+"-MyWPS");
    connect(document(),SIGNAL(contentsChanged()),
            this,SLOT(docBeModified()));

}

QString ChildWnd::GetCurDocName()
{
    return QFileInfo(m_Currentpath).fileName();//fileINfo类可以根据文件调用filename操作,返回从文件路径提取的文件名
}

bool ChildWnd::loadDoc(const QString &docName)
{
   //先判断加载进来的文档名字是否为空
    if(!docName.isEmpty()){
        QFile file(docName);//通过文件名获取文件的所有信息
        if(!file.exists()) return false;//文件不存在返回false

        if(!file.open(QFile::ReadOnly)) return false;//只读方式打开失败返回false
        QByteArray text=file.readAll();
        if(Qt::mightBeRichText(text)){
            setHtml(text);//如果是富文本就设置为HTml
        }else{
            setPlainText(text);//如果不是富文本设置为简单字符串格式

        }
        setCurDoc(docName);
        connect(document(),SIGNAL(contentsChanged()),
                this,SLOT(docBeModified()));
        return true;
    }
}

void ChildWnd::setCurDoc(const QString &docName)
{

    m_Currentpath=QFileInfo(docName).canonicalFilePath();//它会将文件的路径名称的“。”都去除
    m_bSaved=true;
    document()->setModified(false);//将文档是否被修改属性设置为false
    setWindowModified(false);       //窗口不显示改动标识
    setWindowTitle(GetCurDocName()+"[*]");//设置子窗口标题

}

bool ChildWnd::saveDoc()
{
    if(m_bSaved) return saveDocOpt(m_Currentpath);//保存过直接进行保存操作
    else saveAsDoc();//另存为

}

bool ChildWnd::saveAsDoc()
{
    //另存为当然是提供一个文件窗口来提示保存
    QString docName=QFileDialog::getSaveFileName(this,
                                 "另存为",
                                 m_Currentpath,
                                 "HTML文档 (*.htm *html);;"
                                 "所有文件(*.*)");
    if(docName.isEmpty()) return false;
    else return saveDocOpt(docName);
}

bool ChildWnd::saveDocOpt(QString &docName)
{
    if(!(docName.endsWith(".htm",Qt::CaseInsensitive)||
         docName.endsWith(".html",Qt::CaseInsensitive))){
        //如果不是以指定格式结尾,添加指定格式
        docName+=".html";

    }
    QTextDocumentWriter writer(docName);
    bool isSuccess=writer.write(this->document());//write方法会返回写的状态
    if(isSuccess) setCurDoc(docName);//调研设置窗口状态函数
    return isSuccess;

}

void ChildWnd::closeEvent(QCloseEvent *event)
{
    if(promptSave()) event->accept();//接受事件
    else event->ignore();//忽略事件
}

bool ChildWnd::promptSave()
{
    //文档没有被修改
    if(!document()->isModified()) return true;

    QMessageBox::StandardButton result;
    result=QMessageBox::warning(this,
                               QString("系统提示"),
                               QString("文档%1已修改,是否保存?")
                                .arg(GetCurDocName()),
                                QMessageBox::Yes|
                                QMessageBox::Discard|
                                QMessageBox::No);
    if(result==QMessageBox::Yes){
        return saveDoc();
    }else if(result==QMessageBox::No){
        return false;
    }
    return true;//选择忽略就返回真


}

void ChildWnd::docBeModified()
{
    setWindowModified(document()->isModified());
}

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

mainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include"childwnd.h"
#include<QDebug>
#include<QMdiSubWindow>
#include<QList>
#include<QCloseEvent>
#include<QFileDialog>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    InitMainWindow();
}

MainWindow::~MainWindow()
{
    delete ui;
}

//初始化窗口函数
void MainWindow::InitMainWindow()
{
    //初始化字符列表
    QFontDatabase fontdb;
    foreach (int fontsize, fontdb.standardSizes()) {
       //standarSizes函数返回的是标准的字符列表。这里是每次将标准字符的大小
        //传递给fontsize
        ui->FontSize_comboBox->addItem(QString::number(fontsize));//将每次获取到的字符传给组件列表

    }

    QFont defFont;//程序默认的字体(包括字号和字体样式)
    QString sFontSize;
    int defFontSize;//当前应用程序默认的字体字号
    int defFontindex;//当前字号表的序号
    defFont=QApplication::font();//获取当前程序的字体样式
    defFontSize=defFont.pointSize();//返回当前字体字号

    sFontSize=QString::number(defFontSize);//将整形的字号转换为字符串类型的方便列表查找序号
    defFontindex=ui->FontSize_comboBox->findText(sFontSize);//将默认的字号对应的序号返回
    ui->FontSize_comboBox->setCurrentIndex(defFontindex);//设置默认程序下的字号序号


    //添加滚动条
    ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);//设置垂直滚动条,属性设置为当在需要的时候显示
    ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);//设置水平滚动条,属性设置为当需要的时候显示
    //注意要点在设置滚动条的时候碰到一个小bug,就是在你要设置滚动条的时候要留出一点位置给水平滚动条,不然无法显示

    RefreshMenu();//在初始化的时候调用一下首先对工具栏进行初始化、

    //当有活动子窗口的时候,发射信号刷新菜单
    connect(ui->mdiArea,&QMdiArea::subWindowActivated,this,&MainWindow::RefreshMenu);

    addSubWindowListMenu();
    //这里我们需要另外的槽函数相应,窗口点击的时候有新窗口内容添加进去
    connect(ui->menu_W,&QMenu::aboutToShow,this,&MainWindow::addSubWindowListMenu);



    //初始化信号映射器
    m_singnalmapper=new QSignalMapper(this);
    connect(m_singnalmapper,SIGNAL(mapped(QWidget*)),
            this,SLOT(setActivSubWindow(QWidget*)));
}

void MainWindow::DocNew()
{
    ChildWnd* childwnd=new ChildWnd;
    ui->mdiArea->addSubWindow(childwnd);//将新建的子窗口添加到编辑区

    //同时将赋值和剪切的Action建立连接
    connect(childwnd,SIGNAL(copyAvailable(bool)),ui->cutAction,SLOT(setEnabled(bool)));
    connect(childwnd,SIGNAL(copyAvailable(bool)),ui->copyAction,SLOT(setEnabled(bool)));

    childwnd->newDoc();
    childwnd->show();
    formatEnable();//新建窗口后将工具栏设置为可用
}

void MainWindow::DocOpen()
{
   QString docName=QFileDialog::getOpenFileName(this,
                                "打开文件",
                                "",
                                "文本文件(*.txt);;"
                                "HTML文件(*.html *.htm);;"
                                "所有文件(*.*)");

   if(!docName.isEmpty()){
       QMdiSubWindow* existWnd=FindChildWnd(docName);
       if(existWnd){
           //如果找到这个子窗口
           ui->mdiArea->setActiveSubWindow(existWnd);//将这个窗口设置为活动窗口
           return;
       }
       ChildWnd* childWnd=new ChildWnd;
       ui->mdiArea->addSubWindow(childWnd);
       connect(childWnd,SIGNAL(copyAvailable(bool)),
               ui->cutAction,SLOT(setEnabled(bool)));//copyAvailable是在打开文档选中文本的时候触发

       connect(childWnd,SIGNAL(copyAvailable(bool)),
               ui->copyAction,SLOT(setEnabled(bool)));

       if(childWnd->loadDoc(docName)){
         statusBar()->showMessage("文件已打开",3000);
         childWnd->show();
         formatEnable();
       }else{
           childWnd->close();
       }
   }
}

void MainWindow::docSave()
{
    if(activateChildWnd()&&activateChildWnd()->saveDoc()){
        statusBar()->showMessage("保存成功",3000);
    }
}

void MainWindow::docSaveAs()
{
    if(activateChildWnd()&&activateChildWnd()->saveAsDoc()){
        statusBar()->showMessage("保存成功",3000);
    }
}

void MainWindow::formatEnable()
{
    ui->boldAction->setEnabled(true);//将工具栏的功能设置为可用
    ui->italicAction->setEnabled(true);
    ui->underlineAction->setEnabled(true);
    ui->letfAlignAction->setEnabled(true);
    ui->centerAlignAction->setEnabled(true);
    ui->rightAlignAction->setEnabled(true);
    ui->justifyAction->setEnabled(true);
    ui->colorAction->setEnabled(true);
    //qDebug()<<12%1<<endl;
}

ChildWnd *MainWindow::activateChildWnd()
{
    QMdiSubWindow*actWnd=ui->mdiArea->activeSubWindow();//获取文档编辑器的活动窗口
    if(actWnd)
        return qobject_cast<ChildWnd*>(actWnd->widget());//如果是活动窗口那就将这个窗口转换为目标的窗口并且返回
    else
        return 0;
}

QMdiSubWindow *MainWindow::FindChildWnd(const QString &docName)
{
    QString strFile=QFileInfo(docName).canonicalFilePath();
    foreach (QMdiSubWindow* subWnd, ui->mdiArea->subWindowList()) {
        ChildWnd* childWnd=qobject_cast<ChildWnd*>(subWnd->widget());//将每一个子窗口转换成ChildWnd类型
        if(childWnd->m_Currentpath==strFile)return subWnd;


    }
    return 0;
}


void MainWindow::on_newAction_triggered()
{
    DocNew();//点击新建动作的时候执行新建文档操作
}

void MainWindow::RefreshMenu()
{
    bool hasChild;
    hasChild=(activateChildWnd()!=0);
    ui->saveAction->setEnabled(hasChild);
    ui->printAction->setEnabled(hasChild);
    ui->saveAsAction->setEnabled(hasChild);
    ui->printPreviewAction->setEnabled(hasChild);
    ui->pasteAction->setEnabled(hasChild);
    ui->closeAction->setEnabled(hasChild);
    ui->closeAllAction->setEnabled(hasChild);
    ui->titleAction->setEnabled(hasChild);
    ui->cascadeAction->setEnabled(hasChild);
    ui->nextAction->setEnabled(hasChild);
    ui->previousAction->setEnabled(hasChild);

    //除了上面的一些文档操作当然还有文本操作
    bool hasSelect=(activateChildWnd() && activateChildWnd()->textCursor().hasSelection());//textcursor函数会返回选中文本的信息,这里用这个函数就是检测文本有没有被选中
    ui->copyAction->setEnabled(hasSelect);
    ui->cutAction->setEnabled(hasSelect);
    ui->boldAction->setEnabled(hasSelect);
    ui->italicAction->setEnabled(hasSelect);
    ui->underlineAction->setEnabled(hasSelect);
    ui->letfAlignAction->setEnabled(hasSelect);
    ui->centerAlignAction->setEnabled(hasSelect);
    ui->rightAlignAction->setEnabled(hasSelect);
    ui->colorAction->setEnabled(hasSelect);
    ui->justifyAction->setEnabled(hasSelect);

}

void MainWindow::addSubWindowListMenu()
{
    //因为每次点击的时候hi执行这个函数,如果不先清空那就会导致这个菜单栏越来越长
    ui->menu_W->clear();
    ui->menu_W->addAction(ui->closeAction);
    ui->menu_W->addAction(ui->closeAllAction);
    ui->menu_W->addSeparator();
    ui->menu_W->addAction(ui->titleAction);
    ui->menu_W->addAction(ui->cascadeAction);
    ui->menu_W->addSeparator();
    ui->menu_W->addAction(ui->nextAction);
    ui->menu_W->addAction(ui->previousAction);


    //点击窗口时相应的函数就是这个,那么我们就要判断有没有活动子窗口
    QList<QMdiSubWindow*> wnds=ui->mdiArea->subWindowList();//这个函数会将mdiArea内的窗口通过链表的形式返回
    if(!wnds.isEmpty()){
        ui->menu_W->addSeparator();//如果有活动子窗口那么就添加一条分割线
    }
    for(int i=0;i<wnds.size();++i){
        ChildWnd* childwnd=qobject_cast<ChildWnd*>(wnds.at(i)->widget());//将链表中的元素转换为部件,再用类型转化为ChildWnds
        QString menuItem_text;
        menuItem_text=QString("%1 %2")
                .arg(i+1)
                .arg(childwnd->GetCurDocName());
        QAction* menuItem_act=ui->menu_W->addAction(menuItem_text);//将窗口的名字添加进menu_w里面
        menuItem_act->setCheckable(true);
        menuItem_act->setChecked(childwnd==activateChildWnd());//是否选中看是否是活动子窗口,activateChildWnd函数返回窗口是否为活动子窗口的状态

        //将每一个添加进去的Action添加信号
        connect(menuItem_act,SIGNAL(triggered(bool)),
                m_singnalmapper,SLOT(map()));//每一个action菜单当被点击的时候都会触发triggered信号执行map()槽函数
        //这样的话都会执行map()函数,但是又怎么区分开呢?
        m_singnalmapper->setMapping(menuItem_act,wnds.at(i));//通过设定不同Action的区别,利用序号来区别


    }
    formatEnable();
}

void MainWindow::on_closeAction_triggered()
{
    ui->mdiArea->closeActiveSubWindow();//关闭活动子窗口
}

void MainWindow::on_closeAllAction_triggered()
{
    ui->mdiArea->closeAllSubWindows();//关闭所有活动子窗口

}

void MainWindow::on_titleAction_triggered()
{
    ui->mdiArea->tileSubWindows();//平铺
}

void MainWindow::on_cascadeAction_triggered()
{
    ui->mdiArea->cascadeSubWindows();//cengdie
}

void MainWindow::on_nextAction_triggered()
{
    ui->mdiArea->activateNextSubWindow();//下一个窗口
}

void MainWindow::on_previousAction_triggered()
{
    ui->mdiArea->activatePreviousSubWindow();//前一个窗口
}

void MainWindow::setActivSubWindow(QWidget*wnd)
{
    //首先判断一下是否为活动子窗口
    if(!wnd){
        return;

    }else{
        //将文本编辑区的这个窗口设置为活动子窗口
        ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(wnd));
        //设置活动子窗口是传进一个文本编辑区的mdiSubWindow的,所以这里用到类型转换

    }
}
void MainWindow::closeEvent(QCloseEvent *event){
    ui->mdiArea->closeAllSubWindows();//当窗口关闭的时候当然子窗口也必须关闭
    if(ui->mdiArea->currentSubWindow())
        event->ignore();//忽略此事件
    else
        event->accept();//接受此事件
}

void MainWindow::on_openAction_triggered()
{
    DocOpen();//打开文档操作
}

void MainWindow::on_saveAction_triggered()
{
    docSave();//保存
}

void MainWindow::on_saveAsAction_triggered()
{
    docSaveAs();//另存为
}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值