如何编写基础自定义QAbstractTextDocumentLayout子类并修复文本不显示问题?
问题描述
我正在做一个简易记事本风格的Qt应用,因为想要完全控制文本渲染,写了个自定义的QAbstractTextDocumentLayout类。代码能编译运行,但不管输入啥,文本区域都是空白的。draw()里的调试语句能正确输出输入的文本,说明输入已经传到draw()里了,但就是没渲染出来。
我想尽量少改代码,至少能显示部分文本就行,哪怕功能不全。另外,我找不到自定义QAbstractTextDocumentLayout的相关文档,要是有参考资料麻烦告知。
原代码
main.cpp
#include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow> #include <QtWidgets/QTextEdit> #include <QKeyEvent> #include "./DocumentLayout.cpp" class MainContent : public QWidget { Q_OBJECT QTextEdit textEdit; DocumentLayout layout; public: MainContent() : textEdit(this), layout(textEdit.document()) { int innerPadding = 20; textEdit.document()->setDocumentMargin(innerPadding); int textColor = 0xD8DEE9; int backgroundColor = 0x2E3440; int selectionColor = 0x2E3440; int selectionBackgroundColor = 0x81A1C1; QPalette palette = textEdit.palette(); palette.setColor(QPalette::Base, QColor(backgroundColor)); palette.setColor(QPalette::Text, QColor(textColor)); palette.setColor(QPalette::Highlight, QColor(selectionBackgroundColor)); palette.setColor(QPalette::HighlightedText, QColor(selectionColor)); textEdit.setPalette(palette); textEdit.setFrameStyle(QFrame::NoFrame); textEdit.document()->setDocumentLayout(&layout); }; void resizeEvent(QResizeEvent *event) { textEdit.setGeometry(0, 0, this->width(), this->height()); } void keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_W && event->modifiers() == Qt::ControlModifier) { qApp->quit(); } } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow window; window.setWindowTitle("Hello, world!"); window.resize(400, 500); MainContent content; window.setCentralWidget(&content); window.show(); return app.exec(); } #include "main.moc"
DocumentLayout.cpp
#include <QAbstractTextDocumentLayout> #include <QPainter> #include <QTextBlock> #include <QDebug> class DocumentLayout : public QAbstractTextDocumentLayout { public: DocumentLayout(QTextDocument *document) : QAbstractTextDocumentLayout(document) { } void draw(QPainter *painter, const PaintContext &context) { QTextDocument *document = this->document(); QTextBlock block = document->begin(); QPointF position(20, 20); painter->setPen(context.palette.color(QPalette::Text)); QTextLayout *layout = block.layout(); layout->draw(painter, position); position.ry() += layout->boundingRect().height(); qDebug() << "drew" << block.text() << "at" << position; } int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { return 0; } int pageCount() const { return 1; } QSizeF documentSize() const { return QSizeF(400, 800); } QRectF frameBoundingRect(QTextFrame *frame) const { return QRectF(0, 0, 400, 800); } QRectF blockBoundingRect(const QTextBlock &block) const { return QRectF(0, 0, 400, 800); } void documentChanged(int from, int charsRemoved, int charsAdded) { } };
解决方法
你的自定义布局有几个核心问题导致文本无法渲染:
- 未初始化
QTextLayout:直接调用block.layout()得到的是未配置的空布局,没有文本、字体、换行规则,自然画不出内容。 - 只处理第一个文本块:输入多行时,后续的文本块完全被忽略。
- 边界计算错误:
blockBoundingRect和documentSize返回固定大矩形,导致文档无法正确触发重绘和滚动逻辑。
以下是最小修改后的DocumentLayout.cpp,能实现基础文本显示:
#include <QAbstractTextDocumentLayout> #include <QPainter> #include <QTextBlock> #include <QDebug> #include <QFontMetrics> class DocumentLayout : public QAbstractTextDocumentLayout { public: DocumentLayout(QTextDocument *document) : QAbstractTextDocumentLayout(document) { } void draw(QPainter *painter, const PaintContext &context) { QTextDocument *document = this->document(); QTextBlock block = document->begin(); // 用文档边距确定起始位置,避免硬编码 QPointF position(document->documentMargin(), document->documentMargin()); painter->setPen(context.palette.color(QPalette::Text)); // 遍历所有有效文本块 while (block.isValid()) { if (!block.layout()) { // 初始化当前块的文本布局 QTextLayout *layout = new QTextLayout(block.text(), block.charFormat().font()); layout->setTextOption(document->defaultTextOption()); // 设置换行宽度为文档可用宽度(减去左右边距) qreal lineWidth = document->pageSize().width() - 2 * document->documentMargin(); layout->beginLayout(); while (true) { QTextLine line = layout->createLine(); if (!line.isValid()) break; line.setLineWidth(lineWidth); } layout->endLayout(); block.setLayout(layout); } QTextLayout *layout = block.layout(); // 绘制当前文本块 layout->draw(painter, position); // 计算下一个块的Y坐标(加上行间距) position.ry() += layout->boundingRect().height() + document->lineHeight(); block = block.next(); } } int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { return 0; } int pageCount() const { return 1; } QSizeF documentSize() const { // 动态计算文档总高度 int blockCount = document()->blockCount(); QFontMetrics fm(document()->defaultFont()); qreal totalHeight = blockCount * (fm.height() + document()->lineHeight()) + 2 * document()->documentMargin(); return QSizeF(document()->pageSize().width(), totalHeight); } QRectF frameBoundingRect(QTextFrame *frame) const { return QRectF(0, 0, document()->pageSize().width(), documentSize().height()); } QRectF blockBoundingRect(const QTextBlock &block) const { if (!block.layout()) { // 临时计算未初始化块的边界 QFontMetrics fm(block.charFormat().font()); qreal width = document()->pageSize().width() - 2 * document()->documentMargin(); qreal height = fm.height() + document()->lineHeight(); return QRectF(document()->documentMargin(), 0, width, height); } // 返回当前块的实际位置和大小 qreal yPos = block.blockNumber() * (block.layout()->boundingRect().height() + document()->lineHeight()) + document()->documentMargin(); return block.layout()->boundingRect().translated(document()->documentMargin(), yPos); } void documentChanged(int from, int charsRemoved, int charsAdded) { // 文档内容变化时触发重绘 update(); } };
关键修改点
- 初始化QTextLayout:为每个文本块创建并配置布局,设置字体、换行宽度,确保文本能正确排版。
- 遍历所有文本块:不再只处理第一个块,支持多行文本显示。
- 动态计算文档尺寸:根据文本块数量和行高计算文档总高度,确保滚动和重绘逻辑正常。
- 触发重绘:在
documentChanged中调用update(),内容变化时自动刷新界面。
参考资料
Qt官方文档中QAbstractTextDocumentLayout的类参考是基础,但自定义实现的示例极少。最权威的参考是Qt源码中的默认布局实现:
QPlainTextDocumentLayout:纯文本布局,逻辑相对简单,适合入门学习自定义布局的核心流程。QTextDocumentLayout:富文本布局的完整实现,虽然复杂,但能学习到完整的文本排版、样式处理逻辑。
内容的提问来源于stack exchange,提问作者Jack M
相关产品推荐
相关产品推荐

