突出显示与Qt5中的搜索字符串匹配的文本

Highlight text that matches a search string in Qt5

本文关键字:搜索 字符 串匹配 文本 字符串 显示 Qt5      更新时间:2023-10-16

首先让我解释一下我试图实现的目标:

在浏览器中,按Ctrl+f并键入"q"。那次手术的结果就是我想要达到的。这似乎应该是一个已经解决的问题,但尽管我花了很多时间研究、阅读文档和在Qt IRC中四处询问,我仍然停滞不前。也许这里有人能帮我一把。

以下Qt类是我目前正在处理的,以防你想复习一下Qt::DisplayRole
QModelIndex::data
QAbstractItemView::setDelegate
QTextEdit::setExtraSelections

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

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

方便的是,给定一个搜索字符串,QStandardItemModel::match将返回列中匹配的每个QModelIndex的QModelIndexList。当然,如果每行中有几列,则需要在每列上预先进行匹配,但我稍后可能会担心性能问题。

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

搜索字符串:"col">
这是colum|这是colum|这不是col

突出显示的部分是我上面用粗体显示的部分。为了实现这一点,我从阅读Model/View文档中知道,我需要对QStyledItemDelegate进行子类化,并重新实现其绘制功能。所以我从那里开始。

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

我仍然需要做更多的挖掘,看看我如何准确地实现这种行为,但从Qt IRC中告诉我的情况来看,QTextEdit有一个名为setExtraSelections的函数。看看它是如何实现的,它利用了QAbstractTextDocumentLayout::Selection和QTextDocument中我可以使用的各种光标函数。

然而,遗憾的是,我甚至无法开始解决这个问题,因为第一步是确保我可以通过重新实现的自定义委托绘制功能将QTextDocument呈现到QTableView。这就是我目前的困境。我看过这篇文章,这里和它引用的那篇:类似的问题不能解决我的问题

这并不完全是我想要的,但我认为它至少会帮助我获得一些渲染效果。看起来就像在他的代码中,他正在绘制控件(这就是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);
}
}

你可以看到我刚刚输入了文本"Things",看看我是否可以将其渲染。没有用。

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

所以我的问题是:通过子类QStyledItemDelegate的绘制事件渲染QTextDocument是处理此问题的首选方式吗?如果没有,我该如何处理?

如果是,我该如何使它按我的预期工作?我的代码出了什么问题?

奖金2部分问题一旦我可以渲染,考虑到包含要突出显示的文本的模型索引列表是在一个完全不同的函数中发现的,并且与绘制函数执行时不同,我如何实际利用QTextDocument API仅突出显示特定部分?

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

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

我想到了另一种方法,如果它有效,我会更新。

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

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

我相信您在这里需要做的是在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,这意味着绘制的文本字符串中的所有字符都将始终具有相同的格式。如果你能以某种方式用QVector<QTextLayout::FormatRange>,它包含您的每个子字符串格式设置首选项,您将获得您想要的效果。

当然,细节是魔鬼。如果你不介意破解Qt源代码以满足你的目的,你可以这么做;但这将是一个丑陋的解决方案,因为这意味着当使用非定制的Qt版本编译时,您的程序将不再正常工作(每次升级到新的Qt发布时,您都必须重新进行补丁)。

因此,您可能希望从将QItemDelegate::drawDisplay()方法的内容复制到子类的方法开始,然后根据需要修改复制的版本。(当然,这也有其自身的问题,因为QItemDelegate::drawDisplay()引用了子类无法访问的私有成员变量,但您可能可以解决这些问题。你可能还想和Qt的人核实一下,复制这样的方法体是否有任何法律问题;我不确定是否有。如果你对复制它感到不舒服,你至少可以看看它,了解你的drawDisplay()实现可能也需要做的事情。)

从本质上讲,我发现让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不支持像这样基本的东西。哦,好吧。

我解决这个问题的方法是使用委托的paint函数来渲染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;
}
}