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

1,简单介绍

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

运行程序后会出现主界面,顶端的菜单栏包括“文件”“编辑”“格式”“窗口”和“帮助五个主菜单。
菜单栏下面是工具栏,包含了系统常用的功能按钮。工具栏有四个工具条,分别将一组相关功能按钮或控件组织在一起 。

工具栏的第一行有三个工具条: 第一个工具条包括新建、打开、保存、打印等文档管理功能,
第二个工具条包括撤销、重做、剪切、复制和粘贴这些最基本的文本编辑功能,
第三个工具条是各种较高级的文字字体格式设置按钮,包括加粗、倾斜、加下画线,还包括段落对齐及文本颜色设置。

在工具栏的第二行的工具条中有三个组合选择框控件,用于为文档添加段落标号和编号,以及选择特殊字体和更改字号。利用该工具条可以完成更复杂的文档排版和字体美化工作。
此外,在图中还给出了使用该软件制作出的二个文档示例。用Qt版MyWord字处理软件制作出的文档统一以HTML格式存盘,可使用Web浏览器打开观看效果。

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

(1) 界面设计开发
界面设计开发内容包括菜单系统设计、工具栏设计、多窗体MDI程序框架的建立及多个文档子窗口的管理和控制等。
(2) 文本编辑功能实现
文本编辑功能实现主要包括文档的建立、打开和保存,文本的剪切、复制和粘贴,操作撤销与恢复等这些最基本的文档编辑功能。
(3) 排版美化功能实现
排版美化功能实现包括字体选择,字形、字号和文字颜色的设置,文档段落标号和编号的添加,段落对齐方式设置等高级功能实现。

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

2,创建文件

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

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

后面就创建好了

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

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

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

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

#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()

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

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窗口属性 :

//设置为模态框。(如果再设置无边框窗口,那么模态会失效,不会阻塞其他窗口,须重新设置)
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局部变量只初始化一次,在函数退出时它不死亡,下次的运算依据是上一次的结果值。

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。

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()
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()

void MyChild::closeEvent(QCloseEvent *event)
{
    if(MaybeSave()){
        event->accept();
    }else{
        event->ignore();
    }
}

 MaybeSave()

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,则将在标题中设置了“[*]”号的地方显示“*”号,表示该文件已经被修改。

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

3.7设置当前文件:SetCurrentFile

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

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所表示的格式应用到光标所在处的字符上
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

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 主要用于描述文本符号,编号的格式。

//段落编号
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块格式修改当前块(或包含在选择中的所有块)的块格式。

感谢阅读!!!!!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/622352.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入理解C#中的IO操作:File类的详解

文章目录 一、File类的概述二、File类的常用方法2.1 File.Exists(string path)2.2 File.Create(string path)2.3 File.WriteAllText(string path, string contents)2.4 File.ReadAllText(string path)2.5 File.Copy(string sourceFilePath, string destFilePath, bool overwrit…

ONVIF系列一:ONVIF介绍

感谢博主OceanStar的学习笔记&#xff0c;ONVIF系列二和系列三中安装操作过程及代码实现参考了这位博主的博客。 ONVIF系列&#xff1a; ONVIF系列一&#xff1a;ONVIF介绍 ONVIF系列二&#xff1a;Ubuntu安装gSOAP、生成ONVIF代码框架 ONVIF系列三&#xff1a;ONVIF客户端实现…

【QT】QT环境搭建

本专栏内容为&#xff1a;QT学习专栏 通过本专栏的深入学习&#xff0c;你可以了解并掌握QT。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;QT &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f…

无刷电机中对霍尔同步电角度的理解

在霍尔无刷电机驱动时需要进行初始电角度定位&#xff0c;根据ST电机库提供的方法首先我们要测量同步电角度。 在自己的驱动程序中也使用了ST的方法来测量同步电角度&#xff0c;然后根据当前霍尔的状态再确定启动时的电角度 之前用上面的方法测量同步电角度&#xff0c;程序…

Web 安全漏洞之文件上传

目录 文件上传漏洞及危害 文件名 HTML 和 SVG 软链 服务器磁盘 防御方法 网络安全学习路线 &#xff08;2024最新整理&#xff09; 学习资料的推荐 1.视频教程 2.SRC技术文档&PDF书籍 3.大厂面试题 特别声明&#xff1a; 文件上传漏洞及危害 文件上传漏洞…

JDK的串行收集器介绍与优化指南-02

对象的生命周期 对象的生命周期 在Java中,对象的生命周期通常包括以下几个阶段,这些阶段与JVM的内存管理和垃圾收集机制密切相关。 创建阶段 (1)为对象分配存储空间:当使用new关键字或其他方式(如反射、克隆、反序列化等)创建一个对象时,JVM首先会在堆内存中为其分配…

基于Springboot的大学生平时成绩量化管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的大学生平时成绩量化管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三…

在ComfyUI中通过IC-Light实现画面重新打光

&#x1f30c;背景 上一篇文章我们简单介绍了下IC-Light这个新的工程&#xff0c;可以实现为画面重新打光&#xff0c;让前景更好的融入到新的背景&#xff0c;这一篇我们就来一起简单了解下如何实现插件的安装和使用。 首先&#xff0c;我们看下最终能实现的效果&#xff1a…

5.nginx常用命令和日志定时切割

一. nginx常用的相关命令介绍 1.强制关闭nginx: ./nginx -s stop 2.优雅的关闭nginx: ./nginx -s quit 3.检查配置文件是否正确&#xff1a; ./nginx -t 4.查看nginx版本&#xff1a; ./nginx -v 5.查看nginx版本相关的配置环境信息&#xff1a;./nginx -V 6.nginx帮助信…

SpringCloud微服务01-MybatisPlus-Docker

https://b11et3un53m.feishu.cn/wiki/MWQIw4Zvhil0I5ktPHwcoqZdnec 一、微服务介绍 单体架构所有功能集群在一个架构中&#xff0c;难以维护复杂需求 微服务之间是不同的TomCat要跨服务查询&#xff0c; 学习是如何拆分单体架构为微服务 二、MybatisPlus 1.快速入门 ①入门…

老黄终于不穿皮衣了,分享一个AI换装AI试衣软件!

用AI实现在线试衣&#xff0c;或者在线换装&#xff0c;这不是一个新概念&#xff0c;肯定有人这么想过&#xff0c;但并不是所有人能都能轻松做到啊&#xff01; 今天就来分享一个人人都可以实现的方法&#xff0c;而且是那种傻瓜式的不用付钱的那种&#xff0c;甚至可以把软件…

Tkinter组件:Scrollbar-滚动条

Tkinter组件&#xff1a;Scrollbar Scrollbar&#xff08;滚动条&#xff09;组件用于滚动一些组件的可见范围&#xff0c;根据方向可分为垂直滚动条和水平滚动条。Scrollbar 组件常常被用于实现文本、画布和列表框的滚动。 1. 何时使用 Scrollbar 组件&#xff1f; Scrollb…

2000-2022年上市公司供应链效率数据(含原始数据+结果)

2000-2022年上市公司供应链效率数据&#xff08;含原始数据结果&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;年份、股票代码、省份、城市、区县、省份代码、城市代码、区县代码、首次上市年份、上市状态、股票简称、行业名称、行业代码、库存周转率、供…

【qt】数值的输入与输出

数值的输入与输出 一.与c中的输入与输出的区别二.QString转数值三.数值转QString1.number()2.asprintf() 四.小项目1.总价和进制2.QSpinBox代替3.QSlider滑动块4.QScrollBar滚动条5.QDial表盘6.QLcdnumber lcd显示 五.总结一下下 一.与c中的输入与输出的区别 在c中我们一般通过…

使用LangChain和Neo4j快速创建RAG应用

大家好&#xff0c;Neo4j 通过集成原生的向量搜索功能&#xff0c;增强了其对检索增强生成&#xff08;RAG&#xff09;应用的支持&#xff0c;这标志着一个重要的里程碑。这项新功能通过向量索引搜索处理非结构化文本&#xff0c;增强了 Neo4j 在存储和分析结构化数据方面的现…

【MYSQL】一颗B+树可以保存多少条数据

引言 事万物都有自己的单元体系&#xff0c;若干个小单体组成一个个大的个体。就像拼乐高一样&#xff0c;可以自由组合。所以说&#xff0c;如果能熟悉最小单元&#xff0c;就意味着我们抓住了事物的本事&#xff0c;再复杂的问题也会迎刃而解。 存储单元 存储器范围比较大…

虚拟资源在线交易服务平台源码 线上虚拟商品交易平台搭建

在信息爆炸的时代&#xff0c;虚拟资源、素材、源码系统等等以其独特的魅力&#xff0c;逐渐成为人们日常生活和工作中不可或缺的一部分。如何高效地获取、管理和交易这些虚拟资源&#xff0c;分享一款虚拟资源在线交易服务平台源码&#xff0c;轻松搭建线上虚拟商品交易平台&a…

本来还挺喜欢……

前阵子买了个天空星开发板&#xff0c;到手之后发觉不对劲。 之前我们玩玩开发板都是用的面包板的&#xff0c;就算是ESP那种比较宽的板子用两个面包板拼一下也勉强可以用。 但是天空星它的引脚是分为两组&#xff0c;每组有两排&#xff0c;如果我们还是直接使用面包板的话&a…

【JVM】从可达性分析,到JVM垃圾回收算法,再到垃圾收集器

《深入理解Java虚拟机》[1]中&#xff0c;有下面这么一段话&#xff1a; 在JVM的各个区域中&#xff0c;如虚拟机栈中&#xff0c;栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的&#xff08;尽管在…

C++--String类

系列文章目录 文章目录 目录 系列文章目录 文章目录 前言 一、为什么要学习string 1.c语言的字符串 2.OJ上的使用 二、string类的接口介绍 1.string简介 2.string构造成员函数 3.operator函数 4.string容器size和length 5.重载operator[]和引用返回的意义 5.1 oper…