Search code examples
pythonpyqtqpainterqheaderview

Painting after hiding a widget get clipped


I am trying to add a QLineEdit to QHeaderView so that I can filter words from QSortFilterProxyModel.

At first, header may only show title and a search icon. When user clicks the icon, the QLineEdit opens at the right side, covering the search button and title moves to the left side of the header.

As editingFinished signal is emitted, QLineEdit saves current text, and paintSection paints title and QLineEdit on header, like delegates do. If QLineEdit is empty, searching ends and paintSection just goes back to the first layout, only title with a search icon.

However, in my code, as searching ends paintSection only refreshes part where was covered with QLineEdit, and the part not covered with it stays the same until some kind of event triggers repainting.

Behavior

I am not quite friendly with Qt's painter system, so I am really frustrated with this problem.

This is my current code for HeaderView. Function responsible for opening QLineEdit is not included. HeaderData is just a simple class that has information of Header.

class HeaderView(QHeaderView):
    def __init__(self, orientation=Qt.Horizontal):
        super().__init__(orientation)
        self.setSectionsMovable(True)
        self.setSectionsClickable(True)
        self.editIndex = -1
        self.headers = []
        self.searchImage = QImage('search.png')
        h = HeaderData()
        self.headers.append(h)
        self.line = QLineEdit(self.viewport())
        self.line.hide()
        self.line.editingFinished.connect(self.editingFinished)
        self.line.textChanged.connect(self.textChanged)

    def paintSection(self, painter, rect, logicalIndex):

        if self.headers[logicalIndex].search is True:  # currently searching
            # Left half is filled with title
            text_rect = QRect(rect.x(), rect.y(), rect.width()//2, rect.height())
            painter.drawText(text_rect, Qt.AlignCenter, self.model().headerData(logicalIndex, self.orientation(), Qt.DisplayRole))

            # Right half is area for QLineEdit
            rel_pos = painter.deviceTransform().map(QPoint(rect.x(), rect.y()))
            line_rect = QRect(rect.width() // 2, 0, rect.width() // 2, rect.height())
            line_pos = rel_pos + line_rect.topLeft()
            device = painter.device()
            w = QLineEdit()
            w.resize(line_rect.size())
            w.setText(self.headers[logicalIndex].line_text)
            w.render(device, line_pos, QRegion(0, 0, w.width(), w.height()), QWidget.RenderFlag.DrawChildren)
            self.headers[logicalIndex].line_rect = line_rect

        else:  # not searching
            painter.drawText(rect, Qt.AlignCenter, self.model().headerData(logicalIndex, self.orientation(), Qt.DisplayRole))
            point = rect.topLeft()
            offset = QPoint(rect.width() - rect.height(), rect.height()//4)
            icon_size = QSize(rect.height()//2, rect.height()//2))
            painter.drawImage(point + offset, self.searchImage.scaled(icon_size)
            self.headers[logicalIndex].search_rect = QRect(offset, icon_size)

    def openEditor(self, index):
        self.editIndex = index
        line_rect = self.headers[index].line_rect.translated(self.sectionViewportPosition(index), 0)
        self.line.setGeometry(line_rect)
        self.line.setText(self.headers[index].line_text)
        self.line.setVisible(True)

    def textChanged(self, text):
        self.headers[self.editIndex].line_text = text

    def editingFinished(self):
        if self.line.text() == '':
            self.headers[self.editIndex].search = False
        self.line.hide()
        self.headers[self.editIndex].line_text = self.line.text()
        self.editIndex = -1

Solution

  • Ok, I've done some event filtering and stuff, and found out that when editingFinished is called, only layoutRequest event raises because of hiding the editor widget and this event certainly didn't repaint the background.

    So I tried to add repaint or update at the end of editingFinished function, but they did not call any events at all. When I called them before widget gets hidden, now they triggered the paintEvent, but only region where my widget was placed.

    After that, I found a slot updateSection in Qt for Python documentation, and it did work! I'm not sure whether it will work on C++ Qt because I couldn't find same slot on C++ Qt documentation.

    I'm still curious why the header did not update when I used 'repaint' or 'update', but that's another question.