《一》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 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块格式修改当前块(或包含在选择中的所有块)的块格式。
感谢阅读!!!!!
-
还没有评论,来说两句吧...