如何使项目视图在Qt中呈现富(html)文本

如何使项目视图在Qt中呈现富(html)文本,qt,rendering,richtext,qtreeview,qlistview,Qt,Rendering,Richtext,Qtreeview,Qlistview,假设我的模型中有Qt::DisplayRole具有以下字符串的项 <span>blah-blah <b>some text</b> other blah</span> 诸如此类的废话一些文字其他的废话 我希望QTreeView(实际上是任何项目视图)将其呈现为富文本。相反,默认情况下,项目视图将其呈现为纯文本。如何实现所需的渲染 实际上,这是一个搜索结果模型。用户输入文本,根据该文本搜索某些文档,并向用户显示搜索结果,其中搜索的单词应比周围的

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

<span>blah-blah <b>some text</b> other blah</span>
诸如此类的废话一些文字其他的废话
我希望QTreeView(实际上是任何项目视图)将其呈现为富文本。相反,默认情况下,项目视图将其呈现为纯文本。如何实现所需的渲染


实际上,这是一个搜索结果模型。用户输入文本,根据该文本搜索某些文档,并向用户显示搜索结果,其中搜索的单词应比周围的文本更粗体。

我想您可以使用treeview的方法为treeview项目设置自定义画师。在代理的绘制方法中,可以使用QTextDocument将项的文本作为html加载并呈现。请检查以下示例是否适用于您:

treeview初始化:

...
    // 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);
...
希望这有帮助,谢谢

更新0:更改HtmlLegate以使图标可见,并使选定项目的笔颜色不同

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();
}

我的答案主要是从@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());
}

下面是对上述答案组合的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())

这个在PySide。我将QPaint传递给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)

jbmohler对PyQt5的回答有一个小小的更新:一些类显然已经转移到
qtwidts

这远远超出了我的工资等级(即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())

写下了另一个答案,说明如何在C++中实现这一点。与目前提供的答案不同的是,这是针对Qt5而不是Qt4。然而,最重要的是,前面的回答忽略了项目代表应该能够对齐指定的文本(例如在
QTreeWidget
中)。此外,我还实现了一种删除富文本的方法,以获得与纯文本委托(在ItemView中)一致的感觉

因此,不用多说,下面是我的
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());
}

谢谢你的回复。实际上,我在玩重写委托和QTextDocument。但是,项目的大小存在问题。您的答案指向
initStyleOption
widget->style()->drawControl
。除了两个问题外,您的解决方案非常出色。1.文本正在项目图标2上绘制。所选项目应具有另一种文本颜色。试图找出解决方法。请检查原始帖子的更新;更改发生在HtmlLegate::paint方法中。为了使图标可见,我只是将文本向右移动到图标的宽度。至于文本颜色,我必须更改paint上下文对象的文本颜色的调色板设置。希望这就是你要找的,regards@Anton您知道如何修改选定的文本颜色了吗?适用于tableWidgets,你是个英雄。我如何指示QTextDocument将AppApplications样式表作为默认值继承?请注意,如果optionV4.state的帐户处于非活动状态,则ctx.palette.setcolor部分需要额外嵌套。否则,当您移动到另一个窗口时,文本几乎无法读取。否则效果很好。ThanksText颜色注释:使用
else ctx.palete.setColor(qpalete::Text,optionV4.palete.color(qpalete::Active,qpalete::Text))以确保文本颜色设置正确。通过stylesheet.QTextDocument设置使用非默认文本颜色时需要:如果添加
doc.setDocumentMargin(0);doc.setDefaultFont(optionV4.font)(将其添加到paint和sizeHint中),然后通过样式表更改字体时,字体将是正确的。此外,sizeHint例程中的
doc.setTextWidth
调用似乎没有任何作用。如果同时使用
sizeHint
paint
方法,则当项目的列缩小时,您可以让单词消失,而不是被截断。@Timo在jbmohler答案下面的注释适用于此处,对于
QListView
中的长文本非常重要:我将其复制到此处。在第行:doc.setHtml(optionV4.text)之后,还需要设置doc.setTextWidth(optionV4.rect.width()),否则代理无法正确呈现目标绘图区域的较长内容。例如,不在QListView中包装单词。此版本似乎无法处理为项目视图指定的对齐方式。相反,这将始终与左上角对齐。如果对齐对你来说很重要,你可以看看我的答案:真是个黑客!啊,谢谢。高亮显示:如果选择了options.state和QtGui.QStyle.state:
doc.setHtml(options.Text)
,则还需要设置
doc.setTextWidth(option.rect.width())
,否则,代理将无法针对目标绘图区域正确渲染较长的内容。例如,在QListView中不包装单词。对我来说不起作用,我只看到一小部分随机文本和一部分条目。对于那些需要它的人:我修改了@Pepijn answer一点,以便在Qt API中也包含多行标签,这很可笑。2019年,这应该是内置功能。当每个想要格式化项文本的Qt应用程序(…我们承认,它们中的大多数)需要手动重新实现没有人成功实现的非平凡项委托时
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());
}