LittleFoxyFox
LittleFoxyFox

Reputation: 165

paintEngine Should no longer be called on widget's paintEvent

I'm trying to repaint a list widget item upon clicked, but I'm having some trouble with QPainter. The code "works", but on every GUI repaint it lags considerably and outputs the warnings below and have no idea about what could be causing it since it's all done inside paintEvent (maybe I'm not swapping QPainter objects properly?)

On every repaint the GUI freezes and outputs:

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::fillPath: Painter not active
QPainter::setPen: Painter not active
QPainter::drawPath: Painter not active
QPainter::setClipPath: Painter not active
QPainter::end: Painter not active, aborted

pyqt

import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtGui
from PyQt5.QtGui import QColor, QFont, QImage, QPainter, QPainterPath, QPixmap


class QCustomQWidget(QtWidgets.QWidget):
    def __init__(self, parent=None, ref_parent=None):
        super(QCustomQWidget, self).__init__(parent)
        self.textQVBoxLayout = QtWidgets.QVBoxLayout()
        self.ref_parent = ref_parent
        self.shadow_effects = {}
        self.shadow_effects_counter = 0
        self.textUpQLabel = QtWidgets.QLabel()
        font = QFont()
        font.setPointSize(12)
        self.textUpQLabel.setFont(font)
        self.textQVBoxLayout.addWidget(self.textUpQLabel)
        self.allQGrid = QtWidgets.QGridLayout()
        self.thumbnailQLabel = QtWidgets.QLabel()
        self.allQGrid.addWidget(self.thumbnailQLabel, 0, 0, 2, 1, QtCore.Qt.AlignLeft)
        self.allQGrid.addLayout(self.textQVBoxLayout, 0, 1, 2, 1, QtCore.Qt.AlignLeft)
        self.setLayout(self.allQGrid)

    def paintEvent(self, event):
        target = self
        painter = QPainter(target)
        painter = self.set_painter_color(painter, target, QColor(224, 224, 224))
        painter.end()

        if self.ref_parent.item_widget_to_repaint is not None:
            # change color for the clicked list item
            target = self.ref_parent.item_widget_to_repaint
            painter = QPainter(target)
            painter = self.set_painter_color(painter, target, QColor(129, 173, 244))
            painter.end()

    def set_painter_color(self, painter, target, color: QColor):
        painter.setRenderHint(QPainter.Antialiasing, True)
        painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)

        rect = QtCore.QRectF(target.rect())

        painter_path = QPainterPath()
        painter_path.addRoundedRect(rect, 20, 20)
        painter.fillPath(painter_path, QtGui.QBrush(color))
        painter.setPen(QtCore.Qt.NoPen)  # remove border when clipping
        painter.drawPath(painter_path)
        painter.setClipPath(painter_path)
        return painter

    def setTextUp(self, text):
        self.textUpQLabel.setText(text)
        self.textUpQLabel.setSizePolicy(
            QtWidgets.QSizePolicy(
                QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding
            )
        )


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setLayout(QtWidgets.QVBoxLayout())
        self.checkbox_dict = dict()
        self.button_dict = dict()
        self.listWidget = QtWidgets.QListWidget()
        self.layout().addWidget(self.listWidget)
        self.item_widget_to_repaint = None
        text = ["ITEM 1", "ITEM 2", "ITEM 3"]
        for i in text:
            myQCustomQWidget = QCustomQWidget(ref_parent=self)
            myQCustomQWidget.setTextUp(i)
            myQListWidgetItem = QtWidgets.QListWidgetItem(self.listWidget)
            myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
            self.listWidget.addItem(myQListWidgetItem)
            self.listWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)

        self.listWidget.itemClicked.connect(lambda item: self.on_list_item_click(item))

        self.resize(800, 300)
        self.show()

    def on_list_item_click(self, item: QtWidgets.QListWidgetItem):
        widget = item.listWidget().itemWidget(item)
        # set target for paintevent in QCustomQWidget
        self.item_widget_to_repaint = widget


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec())

Upvotes: 1

Views: 3297

Answers (1)

eyllanesc
eyllanesc

Reputation: 243887

The painting of a widget should only be done in its own paintEvent, not in the other widget as you are doing and that is what Qt warns with these error messages.

In this case the logic is to create a property that contains the color and that invokes to repaint the widget.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class QCustomQWidget(QtWidgets.QWidget):
    def __init__(self, parent=None, ref_parent=None):
        super(QCustomQWidget, self).__init__(parent)
        self.textQVBoxLayout = QtWidgets.QVBoxLayout()
        self.ref_parent = ref_parent
        self.shadow_effects = {}
        self.shadow_effects_counter = 0
        self.textUpQLabel = QtWidgets.QLabel()
        font = QtGui.QFont()
        font.setPointSize(12)
        self.textUpQLabel.setFont(font)
        self.textQVBoxLayout.addWidget(self.textUpQLabel)
        self.allQGrid = QtWidgets.QGridLayout(self)
        self.thumbnailQLabel = QtWidgets.QLabel()
        self.allQGrid.addWidget(self.thumbnailQLabel, 0, 0, 2, 1, QtCore.Qt.AlignLeft)
        self.allQGrid.addLayout(self.textQVBoxLayout, 0, 1, 2, 1, QtCore.Qt.AlignLeft)

        self._color = QtGui.QColor(224, 224, 224)

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, color):
        if self.color == color:
            return
        self._color = color
        self.update()

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
        rect = QtCore.QRectF(self.rect())
        painter_path = QtGui.QPainterPath()
        painter_path.addRoundedRect(rect, 20, 20)
        painter.fillPath(painter_path, QtGui.QBrush(self.color))
        painter.setPen(QtCore.Qt.NoPen)
        painter.drawPath(painter_path)
        painter.setClipPath(painter_path)

    def setTextUp(self, text):
        self.textUpQLabel.setText(text)
        self.textUpQLabel.setSizePolicy(
            QtWidgets.QSizePolicy(
                QtWidgets.QSizePolicy.MinimumExpanding,
                QtWidgets.QSizePolicy.MinimumExpanding,
            )
        )


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setLayout(QtWidgets.QVBoxLayout())
        self.checkbox_dict = dict()
        self.button_dict = dict()
        self.listWidget = QtWidgets.QListWidget()
        self.layout().addWidget(self.listWidget)
        self.item_widget_to_repaint = None
        text = ["ITEM 1", "ITEM 2", "ITEM 3"]
        for i in text:
            myQCustomQWidget = QCustomQWidget(ref_parent=self)
            myQCustomQWidget.setTextUp(i)
            myQListWidgetItem = QtWidgets.QListWidgetItem(self.listWidget)
            myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
            self.listWidget.addItem(myQListWidgetItem)
            self.listWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)

        self.listWidget.itemClicked.connect(self.on_list_item_click)

        self.resize(800, 300)
    def on_list_item_click(self, item: QtWidgets.QListWidgetItem):
        widget = item.listWidget().itemWidget(item)
        widget.color = QtGui.QColor(129, 173, 244)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())

Upvotes: 2

Related Questions