【问题标题】:Highlight text that matches a search string in Qt5突出显示与 Qt5 中的搜索字符串匹配的文本
【发布时间】:2015-05-29 03:56:26
【问题描述】:

首先让我解释一下我想要实现的目标:

在您的浏览器中,按 Ctrl + f 并输入“q”。该操作的结果是我想要实现的。看起来这应该是一个已解决的问题,但尽管我花了很多时间研究、阅读文档并在 Qt IRC 中四处询问,但我仍然陷入困境。也许这里有人可以帮我一把。

以下 Qt 类是我目前正在处理的内容,以防您想复习一下: Qt::DisplayRole
QModelIndex::data
QAbstractItemView::setDelegate
QTextEdit::setExtraSelections (only as a reference)
QAbstractDocumentLayout::Selection
QTextDocument::drawContents
QStyledItemDelegate::paint
QStyleOptionViewItem
@987654329 @
QTableView
QAbstractItemModel::match
Learn to Model/View Program with Qt

所以现在,让我解释一下我是如何设置的,以及我的研究结果和与 Qt IRC 的互动让我相信我应该做什么。

我正在使用 QStandardItemModel 和 QTableView。附加到 QStandardItemModel 的每一行都有几列。正如我们所知,这些列中的每一列都在 QStandardItemModel 中表示为 QModelIndex。我们可以通过访问它的数据(Qt::DisplayRole)来拉取它显示的文本。

方便地,给定一个搜索字符串,QStandardItemModel::match 将返回一个 QModelIndexList,其中包含在列中匹配的每个 QModelIndex。当然,如果每一行都有几列,则需要对每一列执行此匹配,但我稍后会担心性能问题。

太棒了,这个问题解决了。我们有每个具有匹配字符串的 QModelIndex 的列表。现在,我想做的是在每一列中突出显示该字符串。例如:

搜索字符串:“col”
这是一个column |这是一个umn |这是一个umn |这是一个合作

突出显示的部分是我上面用粗体显示的部分。为了实现这一点,我通过阅读模型/视图文档知道我需要继承一个 QStyledItemDelegate 并重新实现它的绘制函数。所以我从那里开始。

下一个要解决的问题是,究竟如何在 DisplayRole 中选择一段特定的文本并仅突出显示它?您不能使用 Qt::BackgroundRole,它会设置整个索引的背景颜色。输入 QTextDocument。

我仍然需要做更多的挖掘工作,看看我如何能准确地实现这种行为,但是根据我在 Qt IRC 中被告知的内容,QTextEdit 有一个名为 setExtraSelections 的函数。看看它是如何实现的,它利用了 QAbstractTextDocumentLayout::Selection 和 QTextDocument 中可供我使用的各种光标函数。

但是,遗憾的是,我什至无法开始解决这个问题,因为第一步实际上是确保我可以使用重新实现的自定义委托绘制函数将 QTextDocument 渲染到 QTableView。这是我目前卡住的地方。我看过这篇文章,这里和它引用的那个: Similar question that doesn't solve my issue

这并不是我想要的,但我认为它至少可以帮助我获得一些渲染效果。看起来在他的代码中,他正在绘制控件(这实际上是 QStyledItemDelegate 所做的),然后尝试在其上绘制他的 QTextDocument。也许那不是它正在做的事情,但它确实看起来像。

无论如何,当我尝试这样做时,看起来 QTextDocument 没有任何效果。如果我注释掉 drawControl 调用,则根本不会呈现任何文本。这是我的代码:

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

    if (index.column() == contentColumn)
    {
        painter->save();

        QTextDocument contentDocument;
        //opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);

        opt.text = "Things";
        painter->setBrush(QBrush(Qt::darkCyan));
        contentDocument.drawContents(painter, opt.rect);

        painter->restore();
    }
    else
    {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

您可以看到我刚刚输入了文本“事物”以查看是否可以将其渲染。无济于事。

在总结我的问题之前,我想提一下,不,我不能使用 QTextEdit。有些特定的列我需要自动换行,而其他列我不想自动换行。 QTableView 完全按照我的预期显示数据。

所以我的问题是: 通过子类 QStyledItemDelegate 的绘制事件呈现 QTextDocument 是否是处理此问题的首选方式?如果没有,我应该怎么处理?

如果是这样,我该如何让它按预期工作?我的代码有什么问题?

奖金 2 合作伙伴问题 一旦可以渲染,我如何才能真正利用 QTextDocument API 仅突出显示特定部分,因为包含要突出显示的文本的模型索引列表是在完全不同的函数中发现的,并且在与绘制函数不同的时间发现执行?

更新 0 使用 QTextDocument API 进行多项选择看起来是不可能的壮举。我将要绘制内容的列需要自动换行和展开。

据我所知,我手动调用 drawContents 意味着手动处理调整大小、自动换行和许多其他事情,如果确实如此,我不想走这条路。

我想到了另一种方法,如果可行,我会更新。

更新 1 我以另一种方式完成了我想要的。不幸的是,我不能将 Qt 用于我想要的。 Qt 的模型/视图系统效率太低了,让它做我需要的事情会导致它非常慢且无法接受。我是如何完成突出显示的?

我将在答案中描述。如果没有人给我更好的答案,我会选择最好的。

【问题讨论】:

    标签: c++ qt search


    【解决方案1】:

    我相信你需要在你的 QItemDelegate 子类中重写这个方法:

    void QItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const
    

    QItemDelegate 实现这个方法的问题在最后,我们看到这个方法调用(它实际上在两个地方调用,但由于它们是相同的调用,我只显示一次):

    d->textLayout.draw(painter, layoutRect.topLeft(), QVector<QTextLayout::FormatRange>(), layoutRect);
    

    这是您想要更改的第三个参数——在 QItemDelegate::drawDisplay() 中,它被硬编码为始终为空 QVector,这意味着绘制的文本字符串中的所有字符都将是始终具有相同的格式。如果您能以某种方式使用包含您的每个子字符串格式首选项的 QVectorQTextLayout::FormatRange> 来调用它,那么您将获得您想要的效果。

    当然,魔鬼在细节中。如果您不介意修改 Qt 源代码以符合您的目的,您可以这样做;但这将是一个丑陋的解决方案,因为这意味着您的程序在使用非自定义 Qt 版本编译时将不再正常工作(并且每次升级到新的 Qt 版本时都必须重新打补丁)。

    因此,您可能希望首先将 QItemDelegate::drawDisplay() 方法的内容复制到子类的方法中,然后根据需要修改复制的版本。 (当然,这有其自身的问题,因为 QItemDelegate::drawDisplay() 引用了您的子类无权访问的私有成员变量,但您可能可以解决这些问题。您可能还想与 Qt 人员核实一下如果复制这样的方法体有任何法律问题;我不确定是否存在。如果您对复制它不满意,您至少可以查看它以获得有关此类事物的灵感您的 drawDisplay() 实现可能还需要这样做)

    【讨论】:

    • 我明白了,非常感谢您抽出宝贵时间回复!抱歉,我应该更清楚我的 CustomDelegate 是什么的子类。文档提到,当为自定义委托子类化时,应该使用 QStyledItemDelegate,这就是我要子类化的内容。 QStyledItemDelegate 及其基类 QAbstractitemDelegate 没有函数 drawDisplay。虽然,我正在寻找相同的缺陷,但我没有找到与你在任何一个中描述的相同的缺陷。似乎没有调用 layout->draw。
    • 嗯,看起来 QStyledItemDelegate::paint() 调用 drawControl(Style::CE_ItemViewItem) 来绘制文本(以及其他内容)。 QCommonStyle::drawControl(CE_ItemViewItem) 然后调用 viewItemDrawText(),然后为单元格中的每一行文本调用 p->drawText()。假设我遵循正确的代码路径,我认为没有一种简单的方法可以让 drawText() 调用以不同的颜色绘制不同的字母。对于 QStyledItemDelegate 你可能只需要重写 QStyledItemDelegate::paint() 来直接调用 QTextLayout::draw() 。
    • 是的,这是我见过的一种方法。虽然这样做,但我会渲染原始文本,然后在其顶部覆盖突出显示的文本。除了效率低下,这似乎是一种黑客行为。 QStyledItemDelegate::paint 确实调用了 QStyle::drawControl,但由于我正在重新实现该函数,所以我根本不需要使用 QStyle::drawControl。事实上,我认为这样做不会产生预期的结果,因为 QTableView 默认使用 QStyledItemDelegate 。也许可以利用 QStyle 中的其他东西?但这就是我尝试 QTextDocument::drawContent 的原因。
    • 另外,我应该指出,目标不是用不同的颜色绘制不同的字母。如果您在浏览器中按 Crtl+f 并搜索内容,您将看到文本变得突出显示,而不是其字体颜色发生变化。这相当于 Qt::BackgroundRole 颜色。似乎没有任何方法可以部分设置背景角色,但是,使用 QTextDocument API,我可以以编程方式“选择”文本并更改突出显示颜色。所以我可以通过选择来给出突出显示的错觉,就像你将鼠标光标拖到编辑框中的文本上一样。
    【解决方案2】:

    基本上,我发现让 QTableView 显示富文本是论坛上的人们试图完成的常见用例。由于这是一个已解决的问题,因此我尝试了解如何利用 HTML。

    首先,我设置了我的自定义委托来处理富文本。然后我有一个有点像这样的算法:

       clear all html tags from the display role in every row of the specified column
    
    for every row in the specified column
        populate a list of QModelIndex with matching text in the Display Role
    
    for every QModelIndex with matching text in the display role
        while there is another occurance of the matching string in the display role
            inject html highlight (span) around the matching word
    

    当然,这是非常、痛苦、令人无法接受的缓慢。它确实有效,与按 ctrl + f 的效果完全相同。但是,我不能使用它。遗憾的是 Qt 不支持像这样基本的东西。哦,好吧。

    【讨论】:

      【解决方案3】:

      我解决这个问题的方法是使用委托的绘制函数从 QTextDocument 中的光标呈现一个或多个位置。

      void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
      {
          if ( !index.isValid() )
              return;
      
      
          // Higlight some text
          {
              QString dataHighlight QString("col"); // The text to highlight.    
              QString value = index.model()->data(index, Qt::DisplayRole).toString();
      
              QTextDocument *doc = new QTextDocument(value);
              QTextCharFormat selection;
      
              int position = 0;
              QTextCursor cur;
      
              // We have to iterate through the QTextDocument to find ALL matching places
              do {
      
                  cur = doc->find(dataHighlight,position);            
                  cur.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
                  cur.selectionStart();
                  cur.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
                  cur.selectionEnd();
                  position = cur.position();
                  selection.setBackground(Qt::yellow);
                  cur.setCharFormat(selection);
      
              } while (!cur.isNull());
      
              painter->save();
              painter->translate(option.rect.x(), option.rect.y());
              doc->drawContents(painter);
              painter->restore();
      
              delete doc;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-14
        • 1970-01-01
        • 2015-10-17
        • 1970-01-01
        • 2019-07-28
        • 1970-01-01
        • 2020-06-17
        • 2015-01-18
        相关资源
        最近更新 更多