Reputation: 165
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
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
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