Qt开发专栏:开发技术(点击传送门)
《Qt开发技术:Qt富文本(二)Qt文本光标操作、文档布局、富文本编辑、处理和Demo》
《Qt开发技术:Qt富文本(三)Qt支持的HTML子集(查询手册)以及涉及的类》
前话
红胖子,来也!
Qt的富文本技术介绍,富文本的文档结构。
富文本处理
概述
Scribe框架提供了一组用于读取和操作结构化富文本文档的类。与Qt中以前的富文本支持不同,新类以QTextDocument类为中心,而不是以原始文本信息为中心。这使开发人员能够创建和修改结构化富文本文档,而无需准备中间标记格式的内容。
文档中的信息可以通过两个相互补充的接口访问:
- 基于光标的接口用于编辑:可以使用模拟用户与编辑器交互的操作编辑文本,而不会丢失文档的底层结构。
- 只读层次接口提供文档结构的高级描述:在执行搜索和文档导出等操作时,只读分层界面最有用。
富文本文档结构
概述了QTextDocument中的各种元素,并描述了它们在文档结构中的排列方式。
文本光标接口
QTextCursor接口解释了如何使用基于光标(光标)的接口编辑富文本文档。
文档布局
简要说明文档布局的作用。
常见的富文本编辑任务
检查一些涉及阅读或操作富文本文档的常见任务。
高级富文本
高级富文本处理检查高级富文本编辑任务。
支持的HTML子集
列出QTextDocument支持的HTML标记。
富文本文档结构
概述
文本文档由QTextDocument类表示,该类包含有关文档的内部表示、其结构的信息,并跟踪修改以提供撤消/重做功能。
文本文档的结构化表示将其内容表示为文本块、框架、表和其他对象的层次结构。它们为文档提供了逻辑结构,并描述了它们的内容将如何显示。通常,框架和表用于将其他结构分组,而文本块包含实际的文本信息。
使用QTextCursor或使用编辑器小部件(如QTextEdit)以编程方式创建新元素并将其插入到文档中。创建元素时,可以为其指定特定格式;否则,它们将采用元素的光标当前格式。
基本结构
文档的“顶层”可能按所示的方式填充。每个文档总是包含一个根框架,并且它总是至少包含一个文本块。
对于具有某些文本内容的文档,根框架通常包含一系列块和其他元素。
即使文本块不包含任何信息,文档中的框架和表序列始终由文本块分隔。这样可以确保新元素始终可以插入现有结构之间。
富文本文档
QTextDocument对象包含构造富文本文档所需的所有信息。文本文档可以通过两种互补的方式访问:作为编辑器使用的线性缓冲区,以及作为对布局引擎有用的对象层次结构。在分层文档模型中,对象通常对应于可视元素,如框架、表和列表。在较低的级别上,这些元素描述文本样式和对齐方式等属性。文档的线性表示用于编辑和操作文档内容。
尽管QTextEdit使显示和编辑富文本变得容易,但文档也可以独立于任何编辑器小部件使用,例如:
QTextDocument *pTextDocument = new QTextDocument;
或者,可以从现有编辑器中提取它们:
QTextEdit *pTextEdit = new QTextEdit; QTextDocument *pTextDocument = pTextEdit->document();
这种灵活性使应用程序能够处理多个富文本文档,而无需多个编辑器小部件的开销,或要求文档以某种中间格式存储。
空文档包含根框架,根框架本身包含单个空文本块。框架提供了文档各部分之间的逻辑分隔,但也具有确定它们在呈现时的显示方式的属性。表格是一种特殊的框架类型,它由多个单元格组成,按行和列排列,每个单元格都可以包含进一步的结构和文本。表提供了管理和布局功能,允许创建灵活的单元格配置。
文本块包含文本片段,每个片段指定文本和字符格式信息。文本属性是在字符级和块级定义的。在字符级别,可以指定字体系列、文本颜色和字体粗细等属性。块级特性控制文本的高级外观和行为,例如文本流的方向、对齐方式和背景颜色。
文档结构不是直接操作的。通过基于光标的界面执行编辑。文本光标界面自动将新的文档元素插入到根框架中,并确保在必要时用空块填充。
通过以下方式获得根框架:
QTextDocument *textDocument; QTextFrame *root = textDocument->rootFrame();
在导航文档结构时,从根框架开始很有用,因为它提供了对整个文档结构的访问。
文档元素
富文本文档通常由常见元素组成,如段落、框架、表和列表。这些在QTextDocument中由QTextBlock、QTextFrame、QTextTable和QTextList类表示。与文档中的其他元素不同,图像由特殊格式的文本片段表示。这使它们能够与周围的文本以内联格式放置。
文档中的基本构造块是QTextBlock和QTextFrame。块本身包含富文本片段(QTextFragment),但这些片段不会直接影响文档的高级结构。
可以将其他文档元素组合在一起的元素通常是QTextObject的子类,分为两类:
- 将文本块组合在一起的元素是QTextBlockGroup的子类;
- 将框架和其他元素组合在一起的元素是QTextFrame的子类。
文本块:QTextBlock
文本块由QTextBlock类提供。
文本块将不同字符格式的文本片段组合在一起,用于表示文档中的段落。每个块通常包含许多具有不同样式的文本片段。在将文本插入文档时创建片段,而在编辑文档时添加更多片段。
文档将拆分、合并和移除片段,以便有效地表示块中不同样式的文本。
通过使用QTextBlock::iterator迭代器遍历块的内部结构,可以检查给定块中的片段:
QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { processFragment(currentFragment); // processFragment处理片段 } }
文本块还用于表示列表项。因此,块可以定义自己的字符格式,其中包含有关块级描述的信息,例如用于列表项的项目符号类型。
文本块本身的格式由QTextBlockFormat类描述,并描述文本对齐、缩进和背景色等属性。
尽管给定的文档可能包含复杂的结构,但一旦我们引用了文档中的有效块,我们就可以按照它们的写入顺序在每个文本块之间导航:
QTextBlock currentBlock = textDocument->begin(); while (currentBlock.isValid()) { processBlock(currentBlock); // processBlock处理块 currentBlock = currentBlock.next(); }
当只想从文档中提取富文本时,此方法非常有用,因为它忽略框架、表和其他类型的结构。
QTextBlock提供比较运算符,使操作块更容易:operator==()和operator!=()用于测试两个块是否相同,而运算符operator<()用于确定文档中哪个块最先出现。
文本框架:QTextFrame
文本框架由QTextFrame类提供。
文本框架将文本块和子框架组合在一起,创建大于段落的文档结构。框架的格式指定如何在页面上呈现和定位它。框架要么插入到文本流中,要么浮在页面的左侧或右侧。每个文档都包含一个根框架,其中包含所有其他文档元素。因此,除了根文本框架之外的所有文本框架都有父文本框架。
由于文本块用于分隔其他文档元素,因此每个框架将始终至少包含一个文本块和零个或多个子框架。我们可以使用QTextFrame::iterator遍历文本框架的子元素来检查文本框架的内容:
QDomElement frameElement = ... QTextFrame::iterator it; for (it = frame->begin(); !(it.atEnd()); ++it) { QTextFrame *childFrame = it.currentFrame(); QTextBlock childBlock = it.currentBlock(); if (childFrame) { processFrame(frameElement, childFrame); }else if (childBlock.isValid()) { processBlock(frameElement, childBlock); } }
注意迭代器同时选择文本框架和块,所以有必要检查它引用的是哪个。这允许逐文本框架导航文档结构,但如果需要,仍然可以访问文本块。QTextBlock::iterator和QTextFrame::iterator类都可以互补地用于从文档中提取所需的结构。
表格:QTextTable
表由QTextTable类提供。
表是按行和列排列的单元格集合。每个表单元格都是具有自己字符格式的文档元素,但也可以包含其他元素,如框架和文本块。在构造表或添加额外的行或列时,将自动创建表单元格。它们也可以在桌子之间移动。
QTextTable是QTextFrame的一个子类,因此在文档结构中将表视为框架。对于我们在文档中遇到的每个文本框架,我们可以测试它是否表示一个表,并以不同的方式处理它:
QDomElement frameElement = ... QTextFrame::iterator it; for (it = frame->begin(); !(it.atEnd()); ++it) { QTextFrame *childFrame = it.currentFrame(); QTextBlock childBlock = it.currentBlock(); if (childFrame) { QTextTable *childTable = qobject_cast<QTextTable*>(childFrame); if (childTable) { processTable(frameElement, childTable); } else { processFrame(frameElement, childFrame); } } else if (childBlock.isValid()) { processBlock(frameElement, childBlock); } }
列表:QTextList
列表由QTextList类提供。
列表是按常规方式格式化的文本块序列,但也提供了标准的列表修饰,如项目符号和枚举项。列表可以嵌套,如果列表的格式指定了非零缩进,则列表将缩进。
们可以通过列表中的索引引用每个列表项:
for (int index = 0; index < list->count(); ++index) { QTextBlock listItem = list->item(index); processListItem(listItem); }
由于QTextList是QTextBlockGroup的一个子类,因此它不将列表项分组为子元素,而是提供各种功能来管理它们。这意味着我们在遍历文档时发现的任何文本块实际上都可能是一个列表项。我们可以使用以下代码确保正确识别列表项:
QTextFrame::iterator it; for (it = frame->begin(); !(it.atEnd()); ++it) { QTextBlock block = it.currentBlock(); if (block.isValid()) { QTextList *list = block.textList(); if (list) { int index = list->itemNumber(block); processListItem(list, index); } } }
图像:QTextFragment
QTextDocument中的图像由文本片段表示,文本片段通过资源机制引用外部图像。图像是使用光标界面创建的,以后可以通过更改图像文本片段的字符格式进行修改:
if (fragment.isValid()) { QTextImageFormat newImageFormat = fragment.charFormat().toImageFormat(); if (newImageFormat.isValid()) { newImageFormat.setName(":/images/newimage.png"); QTextCursor helper = cursor; helper.setPosition(fragment.position()); helper.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); helper.setCharFormat(newImageFormat); } }
通过遍历包含图像的文本块中的片段,可以找到表示图像的片段。