《一》Word文字编辑软件---架构设计分析

1,简单介绍

今天,我们来模拟offic软件中的word文档,运行如图:

运行程序后会出现主界面,顶端的菜单栏包括"文件""编辑""格式""窗口"和"帮助五个主菜单。

菜单栏下面是工具栏,包含了系统常用的功能按钮。工具栏有四个工具条,分别将一组相关功能按钮或控件组织在一起 。

工具栏的第一行有三个工具条: 第一个工具条包括新建、打开、保存、打印等文档管理功能,

第二个工具条包括撤销、重做、剪切、复制和粘贴这些最基本的文本编辑功能,

第三个工具条是各种较高级的文字字体格式设置按钮,包括加粗、倾斜、加下画线,还包括段落对齐及文本颜色设置。

在工具栏的第二行的工具条中有三个组合选择框控件,用于为文档添加段落标号和编号,以及选择特殊字体和更改字号。利用该工具条可以完成更复杂的文档排版和字体美化工作。

此外,在图中还给出了使用该软件制作出的二个文档示例。用Qt版MyWord字处理软件制作出的文档统一以HTML格式存盘,可使用Web浏览器打开观看效果。

开发这个软件主要分为如下三个阶段进行。

(1) 界面设计开发

界面设计开发内容包括菜单系统设计、工具栏设计、多窗体MDI程序框架的建立及多个文档子窗口的管理和控制等。

(2) 文本编辑功能实现

文本编辑功能实现主要包括文档的建立、打开和保存,文本的剪切、复制和粘贴,操作撤销与恢复等这些最基本的文档编辑功能。

(3) 排版美化功能实现

排版美化功能实现包括字体选择,字形、字号和文字颜色的设置,文档段落标号和编号的添加,段落对齐方式设置等高级功能实现。

但是今天,我们首先从架构设计分析,先完成他的文档建立,保存等功能,在一步步去实现其他:

2,创建文件

首先我们新建文件,选择:

名字按自己想法来取,后面这里我们先不要ui文件,选择mainwindow:

后面就创建好了

对于各种新建,保存,另存为等功能,我们需要新建一个C++类。

类的名字自己取,基类选择QTextEdit,因为:QTextEdit是Qt中提供的一个用于文本编辑的控件,支持对富文本进行编辑和格式化,可以用于各种应用程序中,如文本编辑器、笔记应用、电子邮件客户端等。

这样我们就创建好了一个mychild的源文件和头文件。

我们说过,我们建立这个类要完成的就是新建,保存等操作,所以我们要定义一些函数和槽函数来实现:

cpp 复制代码
#ifndef MYCHILD_H
#define MYCHILD_H

#include<QTextEdit>

class MyChild : public QTextEdit
{
    Q_OBJECT
public:
    MyChild();

    void NewFile();//新建文件
    bool LoadFile(const QString &filename);//导入文件
    bool Save();//保存
    bool SaveAs();//另存为
    bool SaveFile(QString filename);//保存文件
    QString userFriendlyCurrentFile();//用户友好型当前文件
    QString currentFile(){return curFile;};//当前文件
    void MergeFormationOnWordOrSelection(const QTextCharFormat &format);//格式字体
    void SetAlign(int align);//对齐
    void SetStyle(int style);//段落标号,编号等风格

protected:
    void closeEvent(QCloseEvent *event);// 可以通过参数event来控制是否让窗体关闭。

private slots:
    void documentWasModified();//修改文件

private:
    QString curFile;// 当前文件
    bool isUntitled;//判断是否命名
    bool MaybeSave();//是否保存
    void SetCurrentFile(const QString &fileName);//设置当前文件
    QString StrippedName(const QString &fullFilename);// 脱离文件名称
};

然后我们就需要在源文件中去实现函数:

3,代码的实现

3.1MyChild()

首先,在构造函数中,我们需要设置如下:

cpp 复制代码
MyChild::MyChild()
{
    setAttribute(Qt::WA_DeleteOnClose,true);//关闭窗口时销毁
    isUntitled=true;
}

setAttribute用来设置窗口属性,我们把它设置为:关闭窗口时销毁。

Qt::WA_DeleteOnClose

  • (1)调用close()方法,会向widget发送一个关闭事件(QCloseEvent),如果widget接受了关闭事件,窗口将会隐藏(实际上调用hide())。如果widget不接受关闭事件,那么窗口将什么也不做。也就是说close()方法只会隐藏窗口对象而已,并不会销毁该对象。
  • (2)倘若设置了WA_DeleteOnClose属性,它接收到QCloseEvent事件后,除了调用hide()方法将窗口隐藏外,同时会调用deleteLater()方法将窗口释放掉,不会再占用资源。

常用的setAttribute窗口属性 :

cpp 复制代码
//设置为模态框。(如果再设置无边框窗口,那么模态会失效,不会阻塞其他窗口,须重新设置)
setAttribute(Qt::WA_ShowModal, true);
      
//如果部件接收了关闭事件,则删除这个部件,相当于delete 
setAttribute(Qt::WA_DeleteOnClose, true);
  
//意思是显示小部件而不使其处于活动状态,使它不能获得焦点
setAttribute(Qt::WA_ShowWithoutActivating,true);

//使透明效果生效
setAttribute(Qt::WA_TranslucentBackground);

//穿透属性,可以使部件不可点击,只显示外形,适合覆盖中的部件使用
setAttribute(Qt::WA_TransparentForMouseEvents);

//输入法开关,如果一个编辑框不想让用户使用输入法输入字符打字,就可以将该属性设置为false。
setAttribute(Qt::WA_InputMethodEnabled, false);

//使用操作系统原生的本地窗口,可以提高兼容性
//但是在linux下,使用该属性会有问题,因为linux下x11管理的默认原生窗口是白色矩形,就算添加了子部件,也会出现短暂的白色矩形闪烁。
setAttribute(Qt::WA_NativeWindow, true);

3.2 NewFile()

在新建文件中,我们要保证每次新建文件名称都不能和上次一样,就像在电脑上,你多次点击新建文件 ,他会出现新建文件夹1,2,3.。。。等等。

所以我们要使用静态变量去实现:static局部变量只初始化一次,在函数退出时它不死亡,下次的运算依据是上一次的结果值。

cpp 复制代码
void MyChild::NewFile()// 新建word文件
{
    static int sequenceNumber=1;
    isUntitled=true;

    curFile=tr("Word文档-%1").arg(sequenceNumber++);
    setWindowTitle(curFile);//把新建的名称设置到标题
}

3.3导入文件LoadFile()

在导入文件中,我们需要先进行判断,保证导入的不能为空,而且不能为只读文件。创建一个QByteArray数组来获取file读取到的所有文件,然后进行筛选,QTextCodecs 可以用于将一些本地编码的字符串转换为 Unicode。

cpp 复制代码
bool MyChild::LoadFile(const QString &filename)// 导入文件
{
    if(!filename.isEmpty()){
        if(!QFile::exists(filename)){
            return false;
        }
        QFile file(filename);
        if (!file.open(QFile::ReadOnly))
            return false;
         // 提供一个字节数组,QByteArray可用于存储原始字节(包括"\ 0" )和传统的8位 "\ 0" 端接字符串 .
        QByteArray data=file.readAll();
        // Qt 使用 Unicode 来存储、绘制、操作字符串。 在许多情况下,可能希望处理使用不同编码的数据。
        QTextCodec *codec=Qt::codecForHtml(data);
        QString str=codec->toUnicode(data);

        if(Qt::mightBeRichText(str)){//如果是富文本就设置为HTML
            this->setHtml(str);
        }else{//否则不是富文本设置为简单字符串格式
            str=QString::fromLocal8Bit(data);
            this->setPlainText(str);
        }
        SetCurrentFile(filename);
        connect(document(),SIGNAL(contentsChanged()),this,SLOT(documentWasModified()));
    }
}

对转化而来的字符串str进行判断,如果是富文本,就设置为HTML,如果不是,就设置为简单格式。

然后把打开的文件设置为当前文件。

再连接个槽函数,当问将发生改变时,调用修改文件函数。

3.4 保存文件

保存文件的时候需要判断是直接保存,还是需要另存为保存。

另存为:

复制代码
 从文件对话框获取文件路径。
复制代码
 如果路径不为空,则保存文件saveFile()
cpp 复制代码
bool MyChild::Save()// 保存
{
    if(isUntitled){
        return SaveAs();
    }else{
        return SaveFile(curFile);
    }
}

bool MyChild::SaveAs()// 另存为
{
    QString filename=QFileDialog::getSaveFileName(this,tr("另存为"),curFile,tr("HTML 文档(*.html *html);;所有文件(*.*)"));

    if(filename.isEmpty()){
        return false;
    }
    return SaveFile(filename);
}

bool MyChild::SaveFile(QString filename)// 保存文件
{
    if(!(filename.endsWith(".htm",Qt::CaseInsensitive)||filename.endsWith(".html",Qt::CaseInsensitive))){
        //默认保存为 HTML 文档
        filename+=".html";
    }
    QTextDocumentWriter writer(filename);
    bool success=writer.write(this->document());
    if(success){
        SetCurrentFile(filename);
    }
    return success;
}

在保存文件这个SaveFile()函数里,我们首先就是判断,文件结尾是否带有.htm或者.html后缀,如果字符串的结尾引用.htm或者.html则返回true,否则返回false。忽略大小写

CaseInsensitive:不区分大小写,没有后缀的话,我们需要加上。

QTextDocumentWriter:用于将QTextDocument写入文件或其他设备的与格式无关的接口。

保存文件对话框(对于某些格式QTextDocumentWriter可直接保存,其他不支持的格式就用QTextStream以流的形式保存 )

3.5 关闭文件closeEvent()

cpp 复制代码
void MyChild::closeEvent(QCloseEvent *event)
{
    if(MaybeSave()){
        event->accept();
    }else{
        event->ignore();
    }
}

MaybeSave()

cpp 复制代码
bool MyChild::MaybeSave()//判断是否修改且保存文档
{
    if(!document()->isModified()){
        return true;
    }

    QMessageBox::StandardButton ret;
    ret=QMessageBox::warning(this,tr("Qt Word"),tr("文件'%1'已经被修改,是否保存?").arg(userFriendlyCurrentFile()),
                             QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel);
    if(ret=QMessageBox::Save){
        return Save();
    }else if(ret==QMessageBox::Discard){
        return false;
    }
    return true;
}

我们先进行判断文件是否被修改过, 是的话返回true;

3.6 修改文件documentWasModified()

文件更改标签

编辑器内容是否被更改,可以使用QTextDocument类的isModified()函数获知,这里使用了QTextEdit类,document()函数来获取

它的QTextDocument类对象。然后使用setWindowModified()函数设置窗口的更改状态标志"*",如果参数为true,则将在标题中设置了"\*"号的地方显示"*"号,表示该文件已经被修改。

cpp 复制代码
void MyChild::documentWasModified()
{
    //在设置改变的时候,设置窗口已修改
    setWindowModified(document()->isModified());
}
cpp 复制代码
QString MyChild::userFriendlyCurrentFile()
{
    return StrippedName(curFile);
}
cpp 复制代码
QString MyChild::StrippedName(const QString &fullFilename)
{
    return QFileInfo(fullFilename).fileName();
}

3.7设置当前文件:SetCurrentFile

canonicalFilePath ()可以除去路径中符号链接,如"."和".."等符号。这个函数只是将加载文件的路径首先保存到curFile中,然后再进行一些状态的设置

cpp 复制代码
void MyChild::SetCurrentFile(const QString &fileName)// 设置当前文件
{
    curFile=QFileInfo(fileName).canonicalFilePath();
    isUntitled=false; //文件已经被保存过
    document()->setModified(false);//文档没有被更改过
    setWindowModified(false);//窗口不显示被更改标志
    setWindowTitle(userFriendlyCurrentFile()+"[*]");//设置窗口标题,userFriendlyCurrentFile ()返回文件名
}

3.8 格式字体设置MergeFormationOnWordOrSelection

先定位光标就是当前选中文本光标位置。
WordUnderCursor:选择光标下的单词。如果光标不在可选字符的字符串中,则不选择任何文本。

复制代码
设置字体格式,调用QTextCursor的mergeCharFormat()函数,将参数format所表示的格式应用到光标所在处的字符上
cpp 复制代码
void MyChild::MergeFormationOnWordOrSelection(const QTextCharFormat &format)
{
    QTextCursor cursor=this->textCursor();

    if(!(cursor.hasSelection())){
        cursor.select(QTextCursor::WordUnderCursor);
    }
    cursor.mergeCharFormat(format);
    this->mergeCurrentCharFormat(format);
}

3.9设置段落对齐格式SetAlign

cpp 复制代码
void MyChild::SetAlign(int align)
{
    if(align==1){
        this->setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);//水平靠左
    }else if(align==2){
        this->setAlignment(Qt::AlignCenter);//水平方向居中
    }else if(align==3){
        this->setAlignment(Qt::AlignRight|Qt::AlignAbsolute);//
    }else if(align==4){
        this->setAlignment(Qt::AlignJustify);//水平方向两端对齐
    }
}

设置文本光标,执行文本首部,QTextListFormat 主要用于描述文本符号,编号的格式。

cpp 复制代码
//段落编号
void MyChild::SetStyle(int style)
{
    //多行文本框光标插入文本
    QTextCursor cursor=this->textCursor();
    if(style!=0){
        QTextListFormat::Style stylename=QTextListFormat::ListDisc;//样式为圆圈

        switch(style){
        default:
        case 1:
            stylename=QTextListFormat::ListDisc;
            break;
        case 2:
            stylename=QTextListFormat::ListCircle;//空心圆
            break;
        case 3:
            stylename=QTextListFormat::ListSquare;//方块
            break;
        case 4:
            stylename=QTextListFormat::ListDecimal;//阿拉伯数字
            break;
        case 5:
            stylename=QTextListFormat::ListLowerAlpha;//拉丁字符小写
            break;
        case 6:
            stylename=QTextListFormat::ListUpperAlpha;//拉丁字符大写
            break;
        case 7:
            stylename=QTextListFormat::ListLowerRoman;//小写罗马数字
            break;
        case 8:
            stylename=QTextListFormat::ListUpperRoman;//大写罗马
            break;
        }
        cursor.beginEditBlock();

        QTextBlockFormat blockFmt=cursor.blockFormat();
        QTextListFormat listFmt;
        if(cursor.currentList()){
            listFmt=cursor.currentList()->format();
        }else{
            listFmt.setIndent(blockFmt.indent()+1);
            blockFmt.setIndent(0);
            cursor.setBlockFormat(blockFmt);
        }
        listFmt.setStyle(stylename);
        cursor.createList(listFmt);
        cursor.endEditBlock();
    }else{
        QTextBlockFormat bfmt;
        bfmt.setObjectIndex(-1);
        cursor.mergeBlockFormat(bfmt);
    }
}

cursor.beginEditBlock()是Qt中的一个函数,用于开始编辑操作的分组,以便在多个编辑操作之间进行撤销和重做。调用此函数后,任何对文本的更改都将被视为一组操作。只有在调用了cursor.endEditBlock()函数之后,才会结束此组操作,之后才能进行下一个操作分组。

此代码段中,

  • 首先通过cursor.blockFormat()获取当前光标所在文本块的格式,并通过cursor.currentList()判断当前光标所在的文本块是否是列表。
  • 如果是,则获取列表格式并赋值给listFmt变量;
  • 如果不是,则设置listFmt的缩进为当前文本块缩进加1,并将文本块缩进设置为0。
  • 然后通过cursor.createList(listFmt)创建一个新的列表,并将样式设置为stylename
  • 最后调用cursor.endEditBlock()函数结束此次编辑分组。

如果style=0:

创建一个QTextBlockFormat对象,bfmt的对象索引设置为-1,

用bfmt块格式修改当前块(或包含在选择中的所有块)的块格式。

感谢阅读!!!!!

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能15 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G15 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt