[PyQt] [PYQT5]Highlight search results in qtablewidget(select and highlight that text or character not all of the row or column)

Maziar Parsijani maziar.parsijani at gmail.com
Mon Aug 20 17:58:06 BST 2018


Here I think Maurizio code will functioning like this :

from PyQt5 import QtCore, QtGui, QtWidgets
import random
import html

words = ["Hello", "world", "Stack", "Overflow", "Hello world",
"""<font color="red">Hello world</font>"""]


class HTMLDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(HTMLDelegate, self).__init__(parent)
        self.doc = QtGui.QTextDocument(self)

    def paint(self, painter, option, index):
        substring = index.data(QtCore.Qt.UserRole)
        painter.save()
        options = QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options, index)
        res = ""
        color = QtGui.QColor("orange")
        if substring:
            substrings = options.text.split(substring)
            res = """<font
color="{}">{}</font>""".format(color.name(QtGui.QColor.HexRgb),
substring).join(list(map(html.escape, substrings)))
        else:
            res = html.escape(options.text)
        self.doc.setHtml(res)

        options.text = ""
        style = QtWidgets.QApplication.style() if options.widget is None \
            else options.widget.style()
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)

        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        if option.state & QtWidgets.QStyle.State_Selected:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
        else:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.Text))

        textRect = style.subElementRect(
            QtWidgets.QStyle.SE_ItemViewItemText, options)

        if index.column() != 0:
            textRect.adjust(5, 0, 0, 0)

        thefuckyourshitup_constant = 4
        margin = (option.rect.height() - options.fontMetrics.height()) // 2
        margin = margin - thefuckyourshitup_constant
        textRect.setTop(textRect.top() + margin)

        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        self.doc.documentLayout().draw(painter, ctx)

        painter.restore()


    def sizeHint(self, option, index):
        return QSize(self.doc.idealWidth(), self.doc.size().height())

class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        hlay = QtWidgets.QHBoxLayout()
        lay = QtWidgets.QVBoxLayout(self)

        self.le = QtWidgets.QLineEdit()
        self.button = QtWidgets.QPushButton("filter")
        self.table = QtWidgets.QTableWidget(5, 5)
        hlay.addWidget(self.le)
        hlay.addWidget(self.button)
        lay.addLayout(hlay)
        lay.addWidget(self.table)
        self.button.clicked.connect(self.find_items)
        self.table.setItemDelegate(HTMLDelegate(self.table))

        for i in range(self.table.rowCount()):
            for j in range(self.table.columnCount()):
                it = QtWidgets.QTableWidgetItem(random.choice(words))
                self.table.setItem(i, j, it)

    def find_items(self):
        text = self.le.text()
        # clear
        allitems = self.table.findItems("", QtCore.Qt.MatchContains)
        selected_items = self.table.findItems(self.le.text(),
QtCore.Qt.MatchContains)
        for item in allitems:
            item.setData(QtCore.Qt.UserRole, text if item in
selected_items else None)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

But the main problem of this method as you said is changing in font
size and aligning because my data's are Arabic and English.


On Mon, Aug 20, 2018 at 7:15 PM, Maurizio Berti <maurizio.berti at gmail.com>
wrote:

> I'm not an expert on regular expressions, maybe there's a better and
> faster approach than the one I used in the example.
> I initially tried using re.sub, but works correctly only if you want to
> match case, as the substitution string is fixed.
> Anyway, it seems fast enough to me, as the viewport update() takes care of
> repainting only visible elements.
>
> Another (derivate) approach could be subclassing the model too (the data()
> method), adding a custom user role for the html filtered text that can be
> read by the delegate, maybe with some sort of caching in the model to speed
> up reading. It may be slightly faster and more "elegant" (the model takes
> care of the string match, not the delegate), and could be useful for
> further implementation.
>
> I forgot to say: my example obviously doesn't consider spacings, grid
> lines pen width and icon decoration.
>
> Maurizio
>
> Il giorno lun 20 ago 2018 alle ore 16:01 Maziar Parsijani <
> maziar.parsijani at gmail.com> ha scritto:
>
>> Really thanks to Maurizio
>> I will try this.I agree with you and was thinking if someone had a
>> solution with re library and regix could be better for me .
>>
>>
>> On Mon, Aug 20, 2018 at 6:09 PM, Maurizio Berti <maurizio.berti at gmail.com
>> > wrote:
>>
>>> I know you already found a solution, but I decided to give it a try
>>> anyway, just out of curiosity.
>>> I didn't like the idea of using another QWidget, which might involve
>>> QStyle issues, then I found out that it's possible to use QTextDocument.
>>> This is a simple implementation that just matches the text in all items
>>> (no actual search in the model).
>>>
>>>
>>> class MatchDelegate(QtWidgets.QStyledItemDelegate):
>>>     regex = re.compile('')
>>>     fakeIndex = QtCore.QModelIndex()
>>>
>>>     def paint(self, qp, option, index):
>>>         self.initStyleOption(option, index)
>>>         td = QtGui.QTextDocument()
>>>         if self.regex.pattern:
>>>             splitted = self.regex.split(option.text)
>>>             matches = iter(self.regex.findall(option.text))
>>>             text = ''
>>>             for words in splitted[:-1]:
>>>                 text += words + '<b>{}</b>'.format(matches.next())
>>>             text += splitted[-1]
>>>         else:
>>>             text = option.text
>>>         td.setHtml(text)
>>>         option.text = ''
>>>         # Using an invalid index avoids painting of the text
>>>         QtWidgets.QStyledItemDelegate.paint(self, qp, option,
>>> self.fakeIndex)
>>>         qp.save()
>>>         qp.translate(option.rect.topLeft())
>>>         td.drawContents(qp, QtCore.QRectF(0, 0, option.rect.width(),
>>> option.rect.height()))
>>>         qp.restore()
>>>
>>>
>>> class Widget(QtWidgets.QWidget):
>>>     def __init__(self):
>>>         [...]
>>>         self.delegate = MatchDelegate()
>>>         self.table.setItemDelegate(self.delegate)
>>>         self.search = QtWidgets.QLineEdit()
>>>         self.search.textChanged.connect(self.findText)
>>>         [...]
>>>
>>>     def findText(self, text):
>>>         self.delegate.regex = re.compile(text, re.I)
>>>         self.table.viewport().update()
>>>
>>>
>>> Of course it doesn't take the text size change into account whenever
>>> bold characters are in place, so the sizeHint is not perfect. Also, it
>>> might be possible to implement QFontMetrics' elidedText, again with some
>>> small issues about font size changes.
>>>
>>> Maurizio
>>>
>>>
>>> Il giorno lun 20 ago 2018 alle ore 11:31 Maziar Parsijani <
>>> maziar.parsijani at gmail.com> ha scritto:
>>>
>>>> Hi Denis Rouzaud
>>>>
>>>> This question was for 19days ago.But you are correct the best method is
>>>> html delegate and I did it but I couldn't fix that with my table and the
>>>> table was changed but it worked nice.
>>>>
>>>> On Mon, Aug 20, 2018 at 12:28 PM, Denis Rouzaud <
>>>> denis.rouzaud at gmail.com> wrote:
>>>>
>>>>> This is not easily done.
>>>>> You'd have to create a custom delegate using a QLabbel and use html in
>>>>> there.
>>>>>
>>>>> I have been creating a search tool for tables and ending up
>>>>> highlighting the whole cell.
>>>>> The effort and the risk of bad results is just not worth the effort
>>>>> IMHO.
>>>>>
>>>>> Denis
>>>>>
>>>>> Le mar. 31 juil. 2018 à 23:05, Maziar Parsijani <
>>>>> maziar.parsijani at gmail.com> a écrit :
>>>>>
>>>>>> I use method1 to find some text in qtablewidget rows.
>>>>>>
>>>>>> method1 :
>>>>>>
>>>>>> def FindItem(self):
>>>>>>     items = self.SuraBRS.findItems(
>>>>>>         self.SearchTbox.text(), QtCore.Qt.MatchContains)
>>>>>>     if items:
>>>>>>         results = '\n'.join(
>>>>>>             'row %d column %d' % (item.row() + 1, item.column() + 1)
>>>>>>             for item in items)
>>>>>>     else:
>>>>>>         results = 'Found Nothing'
>>>>>>     print(results)
>>>>>>
>>>>>> Now I want to know how to highlight results or change their color.*I
>>>>>> want to select and highlight that text or character not all of the row or
>>>>>> column*.
>>>>>> _______________________________________________
>>>>>> PyQt mailing list    PyQt at riverbankcomputing.com
>>>>>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>>>>
>>>>> --
>>>>>
>>>>> Denis Rouzaud
>>>>> denis at opengis.ch  <denis at opengis.ch>
>>>>> +41 76 370 21 22
>>>>>
>>>>>
>>>>>
>>>> _______________________________________________
>>>> PyQt mailing list    PyQt at riverbankcomputing.com
>>>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>>
>>>
>>>
>>> --
>>> È difficile avere una convinzione precisa quando si parla delle ragioni
>>> del cuore. - "Sostiene Pereira", Antonio Tabucchi
>>> http://www.jidesk.net
>>>
>>
>>
>
> --
> È difficile avere una convinzione precisa quando si parla delle ragioni
> del cuore. - "Sostiene Pereira", Antonio Tabucchi
> http://www.jidesk.net
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20180820/44ec6091/attachment-0001.html>


More information about the PyQt mailing list