警告

本节包含从C++自动翻译到Python的代码片段,可能包含错误。

QTextCursor 接口

文档可以通过QTextCursor类提供的界面进行编辑;光标可以通过构造函数创建或从编辑器小部件中获取。光标用于执行与用户在编辑器中能够进行的操作完全对应的编辑操作。因此,有关文档结构的信息也可以通过光标获得,这使得可以修改文档结构。使用面向光标的界面进行编辑使开发人员编写自定义编辑器的过程更加简单,因为编辑操作可以很容易地可视化。

QTextCursor 类还维护了关于它在文档中选择的任何文本的信息,再次遵循一个概念上类似于用户在编辑器中选择文本的操作的模型。

富文本文档可以关联多个光标,每个光标都包含有关其在文档中的位置以及它们可能持有的任何选择的信息。这种基于光标的范例使得常见的操作,如剪切和粘贴文本,在编程上易于实现,同时也允许在文档上执行更复杂的编辑操作。

本章描述了您将需要使用光标执行的大多数常见编辑操作,从基本的文本和文档元素插入到更复杂的文档结构操作。

基于光标的编辑

在最简单的层面上,文本文档由一串字符组成,以某种方式标记以表示文档中文本的块结构。QTextCursor 提供了一个基于光标的接口,允许在字符级别上操作 QTextDocument 的内容。由于元素(块、框架、表格等)也在字符流中编码,因此文档结构本身可以通过光标进行更改。

光标会跟踪其在父文档中的位置,并且可以报告周围结构的信息,例如包含的文本块、框架、表格或列表。包含结构的格式也可以通过光标直接获取。

使用游标

光标的主要用途是在一个块内插入或修改文本。我们可以使用文本编辑器的光标来完成这个操作:

editor = QTextEdit()
cursor = QTextCursor(editor.textCursor())

或者,我们可以直接从文档中获取游标:

document = QTextDocument(editor)
cursor = QTextCursor(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)

一旦字符格式与光标一起使用,该格式将成为使用该光标插入的任何文本的默认格式,直到指定另一个字符格式为止。

如果使用光标插入文本而不指定字符格式,文本将采用文档中该位置使用的字符格式。

区块

文本块通过insertBlock()函数插入到文档中。

backgroundFormat = blockFormat
backgroundFormat.setBackground(QColor("lightGray"))
cursor.setBlockFormat(backgroundFormat)

光标位于新块的开始位置。

框架

使用光标将框架插入文档中,并将放置在光标当前框架的当前块之后。以下代码展示了如何在文档的根框架中的两个文本块之间插入框架。我们首先找到光标的当前框架:

mainFrame = cursor.currentFrame()
cursor.insertText(...)

我们在该框架中插入一些文本,然后为子框架设置框架格式:

frameFormat = QTextFrameFormat()
frameFormat.setMargin(32)
frameFormat.setPadding(8)
frameFormat.setBorder(4)

框架格式将给框架一个32像素的外边距,8像素的内边距,以及一个4像素宽的边框。有关框架格式的更多信息,请参阅QTextFrameFormat文档。

框架在文档中插入在前面的文本之后:

cursor.insertFrame(frameFormat)
cursor.insertText(...)

我们在插入框架后立即向文档添加一些文本。由于在将框架插入文档时,文本光标位于框架内部,因此此文本也将插入框架内部。

最后,我们通过使用之前记录的帧内最后一个可用的光标位置,将光标定位在帧外:

cursor = mainFrame.lastCursorPosition()
cursor.insertText(...)

我们最后添加的文本被插入到文档中的子框架之后。由于每个框架都用文本块填充,这确保了总是可以使用光标插入更多元素。

表格

表格使用光标插入到文档中,并将放置在光标当前框架内的当前块之后

cursor = QTextCursor(editor.textCursor())
table = cursor.insertTable(rows, columns, tableFormat)

可以创建具有特定格式的表格,该格式定义了表格的整体属性,例如其对齐方式、背景颜色和使用的单元格间距。它还可以确定每列的限制,允许每列具有固定宽度,或根据可用空间调整大小。

 tableFormat = QTextTableFormat()
 tableFormat.setBackground(QColor("#e0e0e0"))
 QList<QTextLength>raints
raints << QTextLength(QTextLength.PercentageLength, 16)
raints << QTextLength(QTextLength.PercentageLength, 28)
raints << QTextLength(QTextLength.PercentageLength, 28)
raints << QTextLength(QTextLength.PercentageLength, 28)
 tableFormat.setColumnWidthConstraints(constraints)
 table = cursor.insertTable(rows, columns, tableFormat)

上面创建的表格中的每一列将占用可用宽度的一定百分比。请注意,表格格式是可选的;如果您插入一个没有格式的表格,将会使用一些合理的默认值作为表格的属性。

由于单元格可以包含其他文档元素,它们也可以根据需要被格式化和样式化。

可以通过将光标导航到每个单元格并插入文本来向表格中添加文本。

cell = table.cellAt(0, 0)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(tr("Week"), charFormat)

我们可以通过以下方法创建一个简单的时间表:

for column in range(1, columns):
    cell = table.cellAt(0, column)
    cellCursor = cell.firstCursorPosition()
    cellCursor.insertText(tr("Team %1").arg(column), charFormat)

for row in range(1, rows):
    cell = table.cellAt(row, 0)
    cellCursor = cell.firstCursorPosition()
    cellCursor.insertText(tr("%1").arg(row), charFormat)
    for column in range(1, columns):
        if (row-1) % 3 == column-1:
cell = table.cellAt(row, column)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(tr("On duty"), charFormat)

列表

块元素列表可以自动创建并插入到文档中的当前光标位置。以这种方式创建的每个列表都需要指定一个列表格式:

listFormat = QTextListFormat()
if list:
    listFormat = list.format()
    listFormat.setIndent(listFormat.indent() + 1)

listFormat.setStyle(QTextListFormat.ListDisc)
cursor.insertList(listFormat)

上述代码首先检查光标是否位于现有列表中,如果是,则为新列表提供适当级别的缩进格式。这允许创建具有递增缩进级别的嵌套列表。更复杂的实现还会为列表的每个级别使用不同类型的符号作为项目符号。

图片

内联图像通过光标以通常的方式添加到文档中。与许多其他元素不同,所有图像属性都由图像的格式指定。这意味着在插入图像之前,必须创建一个QTextImageFormat对象:

imageFormat = QTextImageFormat()
imageFormat.setName(":/images/advert.png")
cursor.insertImage(imageFormat)

图像名称指的是应用程序资源文件中的一个条目。用于派生此名称的方法在Qt资源系统中有所描述。

示例

富文本存储在文本文档中,这些文档可以通过从外部源导入HTML创建,或者使用QTextCursor生成。

操作富文本

使用富文本文档的最简单方法是通过QTextEdit类,它提供了一个可编辑的文档视图。下面的代码将HTML导入到文档中,并使用文本编辑小部件显示文档。

editor = QTextEdit(parent)
editor.setHtml(aStringContainingHTMLtext)
editor.show()

您可以使用document()函数从文本编辑器中检索文档。然后可以使用QTextCursor类以编程方式编辑文档。该类模拟了屏幕光标,编辑操作遵循相同的语义。以下代码将文档的第一行更改为粗体字体,同时保留所有其他字体属性不变。编辑器将自动更新以反映对底层文档数据所做的更改。

document = edit.document()
cursor = QTextCursor(document)
cursor.movePosition(QTextCursor.Start)
cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
format = QTextCharFormat()
format.setFontWeight(QFont.Bold)
cursor.mergeCharFormat(format)

请注意,光标从第一行的开头移动到了末尾,但它保留了行开头的锚点。这展示了QTextCursor类基于光标的选择功能。

生成日历

使用基于光标的方法可以非常快速地生成富文本。以下示例展示了在QTextEdit小部件中的一个简单日历,其中星期几的标题为粗体:

editor = QTextEdit(self)

cursor = QTextCursor(editor.textCursor())
cursor.movePosition(QTextCursor.Start)
format = QTextCharFormat(cursor.charFormat())
format.setFontFamily("Courier")
boldFormat = format
boldFormat.setFontWeight(QFont.Bold)
cursor.insertBlock()
cursor.insertText(" ", boldFormat)
date = QDate.currentDate()
year = date.year(), month = date.month()
for weekDay in range(1, 8):
    cursor.insertText(QString("%1 ").arg(QLocale.system().dayName(weekDay), 3),
        boldFormat)

cursor.insertBlock()
cursor.insertText(" ", format)
for column in range(1, QDate(year, month, 1).dayOfWeek()):
    cursor.insertText(" ", format)

for day in range(1, date.daysInMonth() + 1):
        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提供了更复杂的布局和格式化功能。