前言
官方文档 Rich Text Processing - The QTextCursor Interface
文档可以通过 QTextCursor 类提供的接口进行编辑;游标是使用构造函数创建的,也可以是从编辑器小部件获取的。游标用于执行与用户能够在编辑器中创建自己的操作完全对应的编辑操作。因此,有关文档结构的信息也可以通过游标获得,这允许修改结构。使用面向游标的界面进行编辑,使开发人员编写自定义编辑器的过程更简单,因为编辑操作可以轻松可视化。
QTextCursor类还维护有关它在文档中选择的任何文本的信息,再次遵循一个在概念上类似于用户在编辑器中选择文本的操作的模型。
富文本文档可以有多个与之关联的游标,并且每个游标都包含有关它们在文档中的位置以及它们可能持有的任何选择的信息。这种基于游标的范例使常见操作(如切割和粘贴文本)易于以编程方式实现,但也允许在文档上执行更复杂的编辑操作。
本章介绍使用游标需要执行的大多数常见编辑操作,从文本和文档元素的基本插入到更复杂的文档结构操作。
基于游标的编辑
在最简单的级别上,文本文档由一串字符组成,该字符串在某种程度上标记以表示文档中文本的块结构。QTextCursor提供了一个基于游标的接口,允许在字符级别操作 QTextDocument 的内容。由于元素(块、框架、表等)也在字符流中编码,因此文档结构本身可以通过光标更改。
光标跟踪其父文档中的位置,并可以报告有关周围结构的信息,如封闭文本块、框架、表或列表。封闭结构的格式也可以直接通过光标获得。
使用游标
游标的主要用途是插入或修改块中的文本。我们可以使用文本编辑器的游标来做到这一点:
QTextEdit *editor = new QTextEdit();
QTextCursor cursor(editor->textCursor());
或者,我们可以直接从文档获取游标:
QTextDocument *document = new QTextDocument(editor);
QTextCursor cursor(document);
光标位于文档的开始位置,以便我们可以写入文档中的第一个(空)块。
分组游标操作
一系列编辑操作可以打包在一起,以便可以重播,或在单个操作中撤消。这是通过使用 和 函数以以下方式实现的,如下面的示例中,我们选择包含游标的单词:beginEditBlock()
endEditBlock()
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::StartOfWord);
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
cursor.endEditBlock();
如果未对编辑操作进行分组,则文档会自动记录各个操作,以便以后可以撤消这些操作。将操作分组到较大的包中可以使用户和应用程序的编辑更加高效,但必须注意不要将太多的操作分组在一起,因为用户可能希望对撤消过程进行查找粒度控制。
多个游标
多个游标可用于同时编辑同一文档,但QTextEdit 小部件中的用户只能看到一个游标。QTextDocument可确保每个光标正确写入文本,并且不会干扰任何其他光标。
插入文档元素
QTextCursor提供了多个函数,可用于更改富文本文档的结构。通常,这些函数允许使用相关的格式信息创建文档元素,并在光标的位置将它们插入到文档中。
第一组函数插入块级元素,并更新光标位置,但它们不返回插入的元素:
- insertBlock() 将新文本块(段落)插入光标位于光标位置的文档,并将光标移动到新块的开头。
- insertFragment() 将现有文本片段插入到光标位置的文档中。
- insertImage() 将图像插入到光标位置的文档中。
- insertText() 在光标位置将文本插入到文档中。
您可以检查通过游标接口插入的元素的内容。
第二组函数插入向文档提供结构的元素,并返回插入的结构:
- insertFrame() 在光标的当前块之后将框架插入到文档中,并将光标移动到新框架中空块的开始。
- insertList() 在光标位置将列表插入到文档中,并将光标移动到列表中第一个项目的开始。
- insertTable() 在光标的当前块之后将表插入到文档中,并将光标移动到表后块的开始。
这些元素包含或分组在一起文档中的其他元素。
文本和文本片段
文本可以以当前字符格式插入到当前块中,也可以以使用文本指定的自定义格式插入:
cursor.insertText(tr("Character formats"),headingFormat);
cursor.insertBlock();
cursor.insertText(tr("Text can be displayed in a variety of "
"different character formats. "), plainFormat);
cursor.insertText(tr("We can emphasize text by "));
cursor.insertText(tr("making it italic"), emphasisFormat);
与光标一起使用字符格式后,该格式将成为使用该光标插入的任何文本的默认格式,直到指定其他字符格式。
如果使用光标插入文本而不指定字符格式,则将为文本提供在文档中该位置使用的字符格式。
块
使用插入块() 功能将文本块插入到文档中。
QTextBlockFormat backgroundFormat = blockFormat;
backgroundFormat.setBackground(QColor("lightGray"));
cursor.setBlockFormat(backgroundFormat);
光标位于新块的开始位置。
框架
框架使用光标插入到文档中,并将在当前块之后放置在光标的当前帧中。以下代码演示如何在文档根框架中的两个文本块之间插入框架。我们首先查找光标的当前帧:
QTextFrame *mainFrame = cursor.currentFrame();
cursor.insertText(...);
我们在此框架中插入一些文本,然后为子框架设置框架格式:
QTextFrameFormat frameFormat;
frameFormat.setMargin(32);
frameFormat.setPadding(8);
frameFormat.setBorder(4);
帧格式将为帧提供 32 像素的外部边距、8 像素的内部填充和 4 像素宽的边框。有关框架格式的详细信息,请参阅 QTextFrameFormat文档。
框架在上一段文本之后插入到文档中:
cursor.insertFrame(frameFormat);
cursor.insertText(...);
插入框架后,我们会立即向文档中添加一些文本。由于文本光标在插入文档时位于框架内,因此此文本也将插入到框架中。
最后,我们将光标放在帧外部,在之前录制的帧内采取最后一个可用光标位置:
cursor = mainFrame->lastCursorPosition();
cursor.insertText(...);
我们上次添加的文本插入到文档中的子框架之后。由于每个框架都用文本块填充,因此可确保始终可以使用光标插入更多元素。
表
使用光标将表插入到文档中,并将在当前块之后放置在光标的当前帧中:
QTextCursor cursor(editor->textCursor());
QTextTable *table = cursor.insertTable(rows, columns, tableFormat);
可以使用定义表的整体属性的特定格式创建表,例如其对齐方式、背景颜色和使用的单元格间距。它还可以确定每列的约束,允许每一列具有固定宽度,或根据可用空间调整大小。
QTextTableFormat tableFormat;
tableFormat.setBackground(QColor("#e0e0e0"));
QVector<QTextLength> constraints;
constraints << QTextLength(QTextLength::PercentageLength, 16);
constraints << QTextLength(QTextLength::PercentageLength, 28);
constraints << QTextLength(QTextLength::PercentageLength, 28);
constraints << QTextLength(QTextLength::PercentageLength, 28);
tableFormat.setColumnWidthConstraints(constraints);
QTextTable *table = cursor.insertTable(rows, columns, tableFormat);
上面创建的表中的列将各占可用宽度的一定百分比。请注意,表格式是可选的;如果插入没有格式的表,则表的属性将使用一些合理的默认值。
由于单元格可以包含其他文档元素,因此也可以对它们进行格式化和样式设置。
可以通过使用光标导航到每个单元格并插入文本,将文本添加到表中。
cell = table->cellAt(0, 0);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("Week"), charFormat);
我们可以通过以下方法创建一个简单的时间表:
for (column = 1; column < columns; ++column) {
cell = table->cellAt(0, column);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("Team %1").arg(column), charFormat);
}
for (row = 1; row < rows; ++row) {
cell = table->cellAt(row, 0);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("%1").arg(row), charFormat);
for (column = 1; column < columns; ++column) {
if ((row-1) % 3 == column-1) {
cell = table->cellAt(row, column);
QTextCursor cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("On duty"), charFormat);
}
}
}
列表
块元素列表可以自动创建并插入到当前光标位置的文档中。以这种方式创建的每个列表都需要指定列表格式:
QTextListFormat listFormat;
if (list) {
listFormat = list->format();
listFormat.setIndent(listFormat.indent() + 1);
}
listFormat.setStyle(QTextListFormat::ListDisc);
cursor.insertList(listFormat);
上述代码首先检查游标是否在现有列表中,如果是,则为新列表的列表格式提供适当的缩进级别。这允许创建嵌套列表,增加缩进级别。更复杂的实现还将对列表每个级别的项目符号使用不同的符号。
图像
内联图像以通常的方式通过光标添加到文档中。与许多其他元素不同,所有图像属性都由图像的格式指定。这意味着必须先创建 QTextImageFormat对象,然后才能插入图像:
QTextImageFormat imageFormat;
imageFormat.setName(":/images/advert.png");
cursor.insertImage(imageFormat);
映像名称是指应用程序资源文件中的条目。用于派生此名称的方法在Qt 资源系统中描述。
例子
富文本存储在文本文档中,这些文档可以通过从外部源导入 HTML 创建,或者使用QTextCursor 生成。
操作富文本
使用富文本文档的最简单方法是通过QTextEdit类,为文档提供可编辑的视图。下面的代码将 HTML 导入到文档中,并使用文本编辑小部件显示文档。
QTextEdit *editor = new QTextEdit(parent);
editor->setHtml(aStringContainingHTMLtext);
editor->show();
您可以使用文档()函数从文本编辑中检索文档。然后可以使用 QTextCursor 类以编程方式编辑文档。此类根据屏幕光标进行建模,编辑操作遵循相同的语义。以下代码将文档的第一行更改为粗体,使所有其他字体属性保持不变。编辑器将自动更新,以反映对基础文档数据所做的更改。
QTextDocument *document = edit->document();
QTextCursor cursor(document);
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
QTextCharFormat format;
format.setFontWeight(QFont::Bold);
cursor.mergeCharFormat(format);
请注意,光标从第一行的开始移动到末尾,但它在行的开始保留了一个锚点。这演示了 QTextCursor 类的基于游标的选择工具。
生成日历
可以使用基于游标的方法非常快速地生成富文本。下面的示例在QTextEdit 小部件中显示一个简单的日历,该小部件包含一周中的最近几天的粗体标题:
editor = new QTextEdit(this);
QTextCursor cursor(editor->textCursor());
cursor.movePosition(QTextCursor::Start);
QTextCharFormat format(cursor.charFormat());
format.setFontFamily("Courier");
QTextCharFormat boldFormat = format;
boldFormat.setFontWeight(QFont::Bold);
cursor.insertBlock();
cursor.insertText(" ", boldFormat);
QDate date = QDate::currentDate();
int year = date.year(), month = date.month();
for (int weekDay = 1; weekDay <= 7; ++weekDay) {
cursor.insertText(QString("%1 ").arg(QLocale::system().dayName(weekDay), 3),
boldFormat);
}
cursor.insertBlock();
cursor.insertText(" ", format);
for (int column = 1; column < QDate(year, month, 1).dayOfWeek(); ++column) {
cursor.insertText(" ", format);
}
for (int day = 1; day <= date.daysInMonth(); ++day) {
int weekDay = QDate(year, month, day).dayOfWeek();
if (QDate(year, month, day) == date)
cursor.insertText(QString("%1 ").arg(day, 3), boldFormat);
else
cursor.insertText(QString("%1 ").arg(day, 3), format);
if (weekDay == 7) {
cursor.insertBlock();
cursor.insertText(" ", format);
}
}
上面的示例演示了使用最少数量的代码快速生成新的富文本文档是多么简单。尽管我们生成了粗糙的固定间距日历,以避免引用太多的代码,但 Scribe 提供了更复杂的布局和格式设置功能。
总结
来自官方文档 Rich Text Processing ,讲得不细致啊