【问题标题】:How to make item view render rich (html) text in Qt如何使项目视图在 Qt 中呈现富 (html) 文本
【发布时间】:2010-12-29 17:12:52
【问题描述】:

假设我的模型有带有以下 Qt::DisplayRole 字符串的项目

<span>blah-blah <b>some text</b> other blah</span>

我希望 QTreeView(实际上是任何项目视图)将其呈现为富文本。相反,默认情况下,项目视图将其呈现为纯文本。如何达到想要的渲染效果?


其实这是一个搜索结果模型。用户输入文本,针对该文本搜索一些文档,然后向用户显示搜索结果,其中搜索的单词应该比周围的文本更粗。

【问题讨论】:

  • Qt API 很可笑。 2019 年,这应该是内置功能。当每个想要格式化项目文本的 Qt 应用程序(...让我们面对现实,其中大部分)都需要手动重新实现似乎没有人成功实现的重要项目委托时一种通用的方式,发生了严重的错误。
  • 注意这个问题是在Qt4时期提出的。 Raven's answer 是 Qt5(及更高版本)用户的最佳匹配。

标签: qt rendering richtext qtreeview qlistview


【解决方案1】:

我猜你可以使用树视图的setItemDelegate 方法为你的树视图项目设置自定义画家。在委托的绘制方法中,您可以使用 QTextDocument 将项目的文本加载为 html 并呈现它。请检查以下示例是否适合您:

树视图初始化:

...
    // create simple model for a tree view
    QStandardItemModel *model = new QStandardItemModel();
    QModelIndex parentItem;
    for (int i = 0; i < 4; ++i)
    {
        parentItem = model->index(0, 0, parentItem);
        model->insertRows(0, 1, parentItem);
        model->insertColumns(0, 1, parentItem);
        QModelIndex index = model->index(0, 0, parentItem);
        model->setData(index, "<span>blah-blah <b>some text</b> other blah</span>");
    }
    // create custom delegate
    HTMLDelegate* delegate = new HTMLDelegate();
    // set model and delegate to the treeview object
    ui->treeView->setModel(model);
    ui->treeView->setItemDelegate(delegate);
...

自定义委托实现

class HTMLDelegate : public QStyledItemDelegate
{
protected:
    void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
    QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};

void HTMLDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 options = option;
    initStyleOption(&options, index);

    painter->save();

    QTextDocument doc;
    doc.setHtml(options.text);

    options.text = "";
    options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);

    painter->translate(options.rect.left(), options.rect.top());
    QRect clip(0, 0, options.rect.width(), options.rect.height());
    doc.drawContents(painter, clip);

    painter->restore();
}

QSize HTMLDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
    QStyleOptionViewItemV4 options = option;
    initStyleOption(&options, index);

    QTextDocument doc;
    doc.setHtml(options.text);
    doc.setTextWidth(options.rect.width());
    return QSize(doc.idealWidth(), doc.size().height());
}

希望这会有所帮助,问候

update0:更改 HTMLDelegate 以使图标可见并且所选项目的笔颜色不同

void HTMLDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 options = option;
    initStyleOption(&options, index);

    painter->save();

    QTextDocument doc;
    doc.setHtml(options.text);

    options.text = "";
    options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);

    // shift text right to make icon visible
    QSize iconSize = options.icon.actualSize(options.rect.size());
    painter->translate(options.rect.left()+iconSize.width(), options.rect.top());
    QRect clip(0, 0, options.rect.width()+iconSize.width(), options.rect.height());

    //doc.drawContents(painter, clip);

    painter->setClipRect(clip);
    QAbstractTextDocumentLayout::PaintContext ctx;
    // set text color to red for selected item
    if (option.state & QStyle::State_Selected)
        ctx.palette.setColor(QPalette::Text, QColor("red"));
    ctx.clip = clip;
    doc.documentLayout()->draw(painter, ctx);

    painter->restore();
}

【讨论】:

  • 感谢您的回复。实际上,我在玩压倒一切的委托和 QTextDocument。但是,项目的大小存在问题。您的回答将我指向initStyleOptionwidget-&gt;style()-&gt;drawControl。您的解决方案非常好,除了两个问题。 1. 文本被绘制在项目图标上 2. 所选项目应该有另一种文本颜色。试图弄清楚如何解决它们。
  • 请检查原始帖子的更新0;更改在 HTMLDelegate::paint 方法中。为了使图标可见,我只是将文本右移到图标的宽度。至于文本颜色,我必须更改绘制上下文对象的文本颜色的调色板设置。希望这是您正在寻找的,问候
  • @Anton 你知道如何修改选中的文本颜色吗?
  • 非常适合 tableWidgets,你是英雄。
  • 如何指示 QTextDocument 默认继承应用程序样式表?
【解决方案2】:

我的回答主要受@serge_gubenko 的启发。但是,进行了一些改进,以便代码最终在我的应用程序中有用。

class HtmlDelegate : public QStyledItemDelegate
{
protected:
    void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
    QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};

void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 optionV4 = option;
    initStyleOption(&optionV4, index);

    QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style();

    QTextDocument doc;
    doc.setHtml(optionV4.text);

    /// Painting item without text
    optionV4.text = QString();
    style->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter);

    QAbstractTextDocumentLayout::PaintContext ctx;

    // Highlighting text if item is selected
    if (optionV4.state & QStyle::State_Selected)
        ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));

    QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4);
    painter->save();
    painter->translate(textRect.topLeft());
    painter->setClipRect(textRect.translated(-textRect.topLeft()));
    doc.documentLayout()->draw(painter, ctx);
    painter->restore();
}

QSize HtmlDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 optionV4 = option;
    initStyleOption(&optionV4, index);

    QTextDocument doc;
    doc.setHtml(optionV4.text);
    doc.setTextWidth(optionV4.rect.width());
    return QSize(doc.idealWidth(), doc.size().height());
}

【讨论】:

  • 请注意,如果 optionV4.state 的帐户处于非活动状态,则 ctx.palette.setcolor 部分需要额外的嵌套。否则,当您移动到另一个窗口时,文本变得几乎无法阅读。否则效果很好。谢谢
  • 文本颜色注意:使用else ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::Text)); 确保正确设置文本颜色。通过样式表使用非默认文本颜色时需要。
  • QTextDocument 设置:如果您添加doc.setDocumentMargin(0); doc.setDefaultFont(optionV4.font);(在paint 和sizeHint 中都添加它),那么当您通过样式表更改字体时,字体将是正确的。此外,sizeHint 例程中的doc.setTextWidth 调用似乎没有做任何事情。如果你把它放在sizeHintpaint 方法中,那么当项目的列缩小时,你可以让单词消失而不是被截断。
  • @Timo 在 jbmohler 答案下方的评论适用于此处,并且对于 QListView 中的长文本很重要:我将在此处复制它。在行:doc.setHtml(optionV4.text) 之后,您还需要设置 doc.setTextWidth(optionV4.rect.width()),否​​则代理将无法正确呈现相对于目标绘图区域的较长内容。例如不在 QListView 中换行。
  • 此版本似乎无法处理为项目视图指定的对齐方式。相反,这将始终对齐左上角。如果对齐对您很重要,您可以看看我的回答:stackoverflow.com/a/66412883/3907364
【解决方案3】:

这是对我有用的上述答案组合的 PyQt 转换。我希望这对 PySide 也几乎相同。

from PyQt4 import QtCore, QtGui

class HTMLDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        options = QtGui.QStyleOptionViewItemV4(option)
        self.initStyleOption(options,index)

        style = QtGui.QApplication.style() if options.widget is None else options.widget.style()

        doc = QtGui.QTextDocument()
        doc.setHtml(options.text)

        options.text = ""
        style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter);

        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()

        # Highlighting text if item is selected
        #if (optionV4.state & QStyle::State_Selected)
            #ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));

        textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options)
        painter.save()
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        doc.documentLayout().draw(painter, ctx)

        painter.restore()

    def sizeHint(self, option, index):
        options = QtGui.QStyleOptionViewItemV4(option)
        self.initStyleOption(options,index)

        doc = QtGui.QTextDocument()
        doc.setHtml(options.text)
        doc.setTextWidth(options.rect.width())
        return QtCore.QSize(doc.idealWidth(), doc.size().height())

【讨论】:

  • 真是个黑客!呃,不过谢谢。高亮:如果 options.state & QtGui.QStyle.State_Selected: ctx.palette.setColor(QtGui.QPalette.Text, options.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
  • 行后:doc.setHtml(options.text),还需要设置doc.setTextWidth(option.rect.width()),否则代理将无法正确渲染目标绘图区域的较长内容。例如不在 QListView 中换行。
【解决方案4】:

这个在 PySide 中。我没有做很多自定义绘图,而是将 QPainter 传递给 QLabel 并让它自己绘制。突出显示从其他答案中借用的代码。

from PySide import QtGui

class TaskDelegate(QtGui.QItemDelegate):
    #https://doc.qt.io/archives/qt-4.7/qitemdelegate.html#drawDisplay
    #https://doc.qt.io/archives/qt-4.7/qwidget.html#render
    def drawDisplay(self, painter, option, rect, text):
        label = QtGui.QLabel(text)

        if option.state & QtGui.QStyle.State_Selected:
            p = option.palette
            p.setColor(QtGui.QPalette.WindowText, p.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))

            label.setPalette(p)

        label.render(painter, rect.topLeft(), renderFlags=QtGui.QWidget.DrawChildren)

【讨论】:

  • 对我不起作用,我只看到一小部分文字,随机的,还有一些条目。
  • 对于那些需要它的人:我修改了@Pepijn answer 以涵盖stackoverflow.com/a/38028318/1504082中的多行标签
【解决方案5】:

为如何在 C++ 中完成此任务编写另一个答案。到目前为止提供的答案的不同之处在于这是针对 Qt5 而不是 Qt4。最重要的是,之前的答案忽略了项目委托应该能够对齐指定的文本(例如在QTreeWidget中)。此外,我还实现了一种省略富文本的方法,以便与纯文本委托(在 ItemViews 中)获得一致的感觉。

所以事不宜迟,这是我的 RichTextDelegate 代码:

void RichTextItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &inOption,
                                 const QModelIndex &index) const {
    QStyleOptionViewItem option = inOption;
    initStyleOption(&option, index);

    if (option.text.isEmpty()) {
        // This is nothing this function is supposed to handle
        QStyledItemDelegate::paint(painter, inOption, index);

        return;
    }

    QStyle *style = option.widget ? option.widget->style() : QApplication::style();

    QTextOption textOption;
    textOption.setWrapMode(option.features & QStyleOptionViewItem::WrapText ? QTextOption::WordWrap
                                                                            : QTextOption::ManualWrap);
    textOption.setTextDirection(option.direction);

    QTextDocument doc;
    doc.setDefaultTextOption(textOption);
    doc.setHtml(option.text);
    doc.setDefaultFont(option.font);
    doc.setDocumentMargin(0);
    doc.setTextWidth(option.rect.width());
    doc.adjustSize();

    if (doc.size().width() > option.rect.width()) {
        // Elide text
        QTextCursor cursor(&doc);
        cursor.movePosition(QTextCursor::End);

        const QString elidedPostfix = "...";
        QFontMetrics metric(option.font);
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
        int postfixWidth = metric.horizontalAdvance(elidedPostfix);
#else
        int postfixWidth = metric.width(elidedPostfix);
#endif
        while (doc.size().width() > option.rect.width() - postfixWidth) {
            cursor.deletePreviousChar();
            doc.adjustSize();
        }

        cursor.insertText(elidedPostfix);
    }

    // Painting item without text (this takes care of painting e.g. the highlighted for selected
    // or hovered over items in an ItemView)
    option.text = QString();
    style->drawControl(QStyle::CE_ItemViewItem, &option, painter, inOption.widget);

    // Figure out where to render the text in order to follow the requested alignment
    QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option);
    QSize documentSize(doc.size().width(), doc.size().height()); // Convert QSizeF to QSize
    QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, option.displayAlignment, documentSize, textRect);

    painter->save();

    // Translate the painter to the origin of the layout rectangle in order for the text to be
    // rendered at the correct position
    painter->translate(layoutRect.topLeft());
    doc.drawContents(painter, textRect.translated(-textRect.topLeft()));

    painter->restore();
}

QSize RichTextItemDelegate::sizeHint(const QStyleOptionViewItem &inOption, const QModelIndex &index) const {
    QStyleOptionViewItem option = inOption;
    initStyleOption(&option, index);

    if (option.text.isEmpty()) {
        // This is nothing this function is supposed to handle
        return QStyledItemDelegate::sizeHint(inOption, index);
    }

    QTextDocument doc;
    doc.setHtml(option.text);
    doc.setTextWidth(option.rect.width());
    doc.setDefaultFont(option.font);
    doc.setDocumentMargin(0);

    return QSize(doc.idealWidth(), doc.size().height());
}

【讨论】:

  • 请注意,doc.setDocumentMargin(1); 更像是项目之间的默认间距。
  • 请注意,大树的文本省略很慢。
  • 奇怪...在我的情况下(这是 PyQt5)option.text 始终是一个空字符串,要显示的(标记的)文本在 index.data() 中找到,所以我有在此处省略 if 子句。
【解决方案6】:

对于 PyQt5,jbmohler 的回答稍作更新:某些类显然已转移到 QtWidgets

这远远超出了我的薪酬等级(即了解 PyQt5 背后的具体细节)。

我赞同塞西尔库里对该问题的评论中表达的观点。现在是 2021 年,我们似乎仍然不得不与这种黑客行为作斗争。荒谬的。迄今为止,与 JavaFX 相比,Qt5 给我留下了深刻的印象。这种缺陷令人失望。

    class HTMLDelegate( QtWidgets.QStyledItemDelegate ):
        def __init__( self ):
            super().__init__()
            # probably better not to create new QTextDocuments every ms
            self.doc = QtGui.QTextDocument()
    
        def paint(self, painter, option, index):
            options = QtWidgets.QStyleOptionViewItem(option)
            self.initStyleOption(options, index)
            painter.save()
            self.doc.setTextWidth(options.rect.width())                
            self.doc.setHtml(options.text)
            self.doc.setDefaultFont(options.font)
            options.text = ''
            options.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
            painter.translate(options.rect.left(), options.rect.top())
            clip = QtCore.QRectF(0, 0, options.rect.width(), options.rect.height())
            painter.setClipRect(clip)
            ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
            ctx.clip = clip
            self.doc.documentLayout().draw(painter, ctx)
            painter.restore()
    
        def sizeHint( self, option, index ):
            options = QtWidgets.QStyleOptionViewItem(option)
            self.initStyleOption(option, index)
            self.doc.setHtml(option.text)
            self.doc.setTextWidth(option.rect.width())
            return QtCore.QSize(self.doc.idealWidth(), self.doc.size().height())

【讨论】:

    猜你喜欢
    • 2011-02-26
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多