Qt开发专栏:开发技术(点击传送门)
《Qt开发技术:Qt富文本(二)Qt文本光标操作、文档布局、富文本编辑、处理和Demo》
《Qt开发技术:Qt富文本(三)Qt支持的HTML子集(查询手册)以及涉及的类》
前话
红胖子,来也!
Qt的富文本技术介绍,文本光标操作、文档布局、富文本常用操作和处理大文件富文本。
文本光标接口
概述
文档可以通过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);
一旦字符格式与光标一起使用,该格式将成为随光标插入的任何文本的默认格式,直到指定了其他字符格式。
如果使用光标插入文本而不指定字符格式,则将为文本提供文档中该位置使用的字符格式。
文本块:QTextBlock
文本块使用insertBlock()函数插入到文档中。
QTextBlockFormat backgroundFormat = blockFormat; backgroundFormat.setBackground(QColor("lightGray")); cursor.setBlockFormat(backgroundFormat);
文本文本框架:QTextFrame
使用光标插入到文档中,并将放置在当前块之后光标的当前框架内。下面的代码演示如何在文档根框架中的两个文本块之间插入框架。我们首先找到光标的当前文本框架:
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(...);
最后添加的文本将插入文档的子框架之后。由于每个文本框架都用文本块填充,这确保了可以始终用光标插入更多的元素。
表格:QTextTable
表格使用光标插入到文档中,并将放置在当前块之后光标的当前文本框架中:
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); } } }
上面的代码首先检查光标是否在现有列表中,如果是,则为新列表的列表格式提供适当的缩进级别。这允许创建嵌套列表,并增加缩进级别。一个更复杂的实现还将使用不同类型的符号来表示列表中每个级别的要点。
图像:QTextImageFormat
内联图像以通常的方式通过光标添加到文档中。与许多其他元素不同,所有图像属性都由图像的格式指定。这意味着必须先创建QTextImageFormat对象,然后才能插入图像:
QTextImageFormat imageFormat; imageFormat.setName(":/images/advert.png"); cursor.insertImage(imageFormat);
图片名称指的是应用程序资源文件中的一个条目(:/xxxx)。Qt资源系统中描述了用于派生此名称的方法。
示例:createCalendarDemo
效果
关键代码
修改富文本
void CalendarWidget::modifyRichText() { ui->textEdit_dst->setText(ui->textEdit_src->toHtml()); QTextDocument *pDocument = ui->textEdit_dst->document(); // 获取光标 QTextCursor cursor(pDocument); // 光标移动到最开始 cursor.movePosition(QTextCursor::Start); // 光标移动到本行末尾,保持光标原来的位置,这样等于选中了本行 cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); // 设置字体加速 QTextCharFormat format; format.setFontWeight(QFont::Bold); cursor.mergeCharFormat(format); // 再移动到网页链接的(0、1、2,第三行),斜体,加粗,红色,24号字体 // 光标移动到最开始 cursor.movePosition(QTextCursor::Start); // 光标移动2次,第3行,下标为2(0,1,2) cursor.movePosition(QTextCursor::NextBlock); cursor.movePosition(QTextCursor::NextBlock); // 光标移动到本行末尾,保持光标原来的位置,这样等于选中了本行 cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); // 设置字体斜体、加粗、红色 QTextCharFormat format2; QFont font = format2.font(); // 斜体 font.setStyle(QFont::StyleItalic); // 粗体 font.setBold(true); // 字体大小:24号 font.setPixelSize(24); // 前景色:黑色 format2.setForeground(QBrush(Qt::red)); format2.setFont(font); cursor.mergeCharFormat(format2); // 光标移动到最开始 cursor.movePosition(QTextCursor::Start); // 光标移动4次,第5行,下标为2(0,1,2,3,4) cursor.movePosition(QTextCursor::NextBlock); cursor.movePosition(QTextCursor::NextBlock); cursor.movePosition(QTextCursor::NextBlock); cursor.movePosition(QTextCursor::EndOfBlock); cursor.insertBlock(); // 插入图片 QTextImageFormat textImageFormat; textImageFormat.setName(":/images/images/1.jpg"); textImageFormat.setHeight(200); textImageFormat.setWidth(200); cursor.insertImage(textImageFormat); // 插入gif,显示时,没有动画 textImageFormat.setName(":/images/images/1.gif"); cursor.insertImage(textImageFormat); }
生成日历
void CalendarWidget::createCalendar() { QTextEdit *pEditor = ui->textEdit_canlendar; // 获取光标 QTextCursor cursor(pEditor->textCursor()); cursor.movePosition(QTextCursor::Start); // 获取光标的字符属性1,设置了字体 QTextCharFormat format(cursor.charFormat()); format.setFontFamily("Courier"); // 获取光标的字符属性2,设置了字体,加粗了字体 QTextCharFormat boldFormat = format; boldFormat.setFontWeight(QFont::Bold); // 插入空文本块(可理解为插入一个空行),从第0行开始,光标插入空文本块后,第0行为空行,光标到达第1行 cursor.insertBlock(); // 插入空格(带不带字体没什么关系) cursor.insertText(" "); 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(QDate::shortDayName(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); } } }
文档布局
概述
文档的布局仅在要在设备上显示时才相关,或者在请求某些需要文档可视化表示的信息时才相关。在此之前,不需要为设备格式化和准备文档。
每个文档的布局都由QAbstractTextDocumentLayout类的一个子类管理。这个类为布局和呈现引擎提供了一个公共接口。默认呈现行为当前在私有类中实现。
这种方法使创建自定义布局成为可能,并提供了在准备打印页面或导出为可移植文档格式(PDF)文件时使用的机制。
示例:LayoutDemo
关键代码
(在paintEvent中调用该函数)
void LayoutWidget::paintLayout() { QString text = "Hello world!!!"; for(int index = 0; index < 200; index++) { text += "Hello world!!!"; } QFont font = this->font(); QTextLayout textLayout(text, font); qreal margin = 10; qreal radius = qMin(width()/2.0, height()/2.0) - margin; QFontMetrics fm(font); qreal lineHeight = fm.height(); qreal y = 0; textLayout.beginLayout(); while(true) { // 创建一根直线 QTextLine line = textLayout.createLine(); if (!line.isValid()) { break; } // 计算椭圆有边界x的值 qreal x1 = qMax(0.0, pow(pow(radius,2)-pow(radius-y,2), 0.5)); qreal x2 = qMax(0.0, pow(pow(radius,2)-pow(radius-(y+lineHeight),2), 0.5)); qreal x = qMax(x1, x2) + margin; qreal lineWidth = (width() - margin) - x; // 设置每一行textLine的右边的开始坐标 line.setLineWidth(lineWidth); line.setPosition(QPointF(x, margin+y)); y += line.height(); } textLayout.endLayout(); QPainter painter; painter.begin(this); painter.setRenderHint(QPainter::Antialiasing); painter.fillRect(rect(), Qt::white); painter.setBrush(QBrush(Qt::black)); painter.setPen(QPen(Qt::black)); textLayout.draw(&painter, QPoint(0,0)); painter.setBrush(QBrush(QColor("#a6ce39"))); painter.setPen(QPen(Qt::black)); painter.drawEllipse(QRectF(-radius, margin, 2*radius, 2*radius)); painter.end(); }
常见的富文本编辑任务
概述
在使用Qt编辑和处理文本文档时,开发人员经常执行许多任务。其中包括使用显示小部件,如QTextBrowser和QTextEdit,使用QTextDocument创建文档,使用QTextCursor编辑,以及导出文档结构
常见编辑任务
使用QTextEdit
可以构造文本编辑器小部件,并使用它以以下方式显示HTML:
QTextEdit *editor = new QTextEdit(parent); editor->setHtml(aStringContainingHTMLtext); editor->show();
默认情况下,文本编辑器包含一个带有根框架的文档,其中包含一个空文本块。本文件可通过以下方式获取,以便直接由应用程序修改:
QTextDocument *document = editor->document();
文本编辑器的光标也可用于编辑文档:
QTextCursor cursor = editor->textCursor();
虽然可以同时使用多个光标编辑文档,但QTextEdit一次只显示一个光标(闪光标)。因此,如果要更新编辑器以显示特定光标或其选择,则需要在修改文档后设置编辑器光标:
editor->setTextCursor(cursor);
选择文本
通过使用类似于用户在文本编辑器中执行的操作移动光标来选择文本。要在文档中的两点之间选择文本,需要将光标定位在第一点,然后使用带有移动操作(QTextCursor::MoveOperation)的特殊模式(QTextCursor::MoveMode)移动它。
当我们选择文本时,我们将选择锚定保留在旧的光标位置,就像用户在选择文本时按住Shift键一样:
cursor.movePosition(QTextCursor::StartOfWord); cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
在上面的代码中,使用此方法选择整个单词。QTextCursor提供了许多常用的移动操作,用于选择单个字符、单词、行和整个块。
查找文本
QTextDocument提供了一个基于光标的搜索界面,使得在文本编辑器的样式中很容易找到和修改文本。以下代码查找文档中特定单词的所有实例,并更改每个实例的颜色:
QTextCursor newCursor(document); while (!newCursor.isNull() && !newCursor.atEnd()) { newCursor = document->find(searchString, newCursor); if (!newCursor.isNull()) { newCursor.movePosition(QTextCursor::WordRight, QTextCursor::KeepAnchor); newCursor.mergeCharFormat(colorFormat); } }
请注意,在每次搜索和替换操作之后,不必移动光标;它始终位于刚刚替换的单词的末尾。
打印文档
QTextEdit是为显示在屏幕上读取的大型富文本文档而设计的,其呈现方式与web浏览器相同。因此,它不会自动将文档内容分成适合打印的页面大小的部分。
QTextDocument提供了print()函数,允许使用QPrinter类打印文档。下面的代码演示如何在QTextEdit中准备文档以便使用QPrinter打印:
QTextDocument *document = editor->document(); QPrinter printer; QPrintDialog *dlg = new QPrintDialog(&printer, this); if (dlg->exec() != QDialog::Accepted) { return; } document->print(&printer);
从文本编辑器获取文档,然后使用QPrintDialog构造QPrinter并进行配置。如果用户接受打印机的配置,则使用print()函数格式化并打印文档。
高级富文本
处理大文件
Qt不限制用于文本处理的文件的大小。在大多数情况下,这不会造成问题。但是,对于特别大的文件,可能会遇到应用程序将变得无响应或内存不足的情况。可以加载的文件大小取决于硬件、Qt和应用程序的实现。
面临此问题,建议采取以下解决方案:
- 考虑将大段落分解成小段落,因为Qt更好地处理小段落。可以按固定的间隔插入换行符,这看起来与QTextEdit中的一个大段落相同。
- 可以使用maximumBlockCount()减少QTextDocument中的块数量。就QTextEdit而言,文档只与块的数量一样大。
- 将文本添加到文本编辑时,将其添加到编辑块中是一种优势(请参见下面的示例)。结果是文本编辑不需要一次构建整个文档结构。
给出了以上最后一种技术的示例代码,假设文本编辑是可见的:
textEdit.show(); textCursor.beginEditBlock(); for (int i = 0; i < 1000; ++i) { textCursor.insertBlock(); textCursor.insertText(paragraphText.at(i)); } textCursor.endEditBlock();
工程模板v1.0.0
富文本工程模板v1.0.0:附带2个子Demo。
下载工程模板
CSDN:https://download.csdn.net/download/qq21497936/12336683
QQ群:1047134658(点击“文件”搜索“qtRichTextDemo”,群内与博文同步更新所有可开源的源码模板)