0

I am a bit lost with the QTreeWidget and I were not able to suck the relevant information from the found topics (like: how to set QTreeView background image with QStyle::StandardPixmap in stylesheet method? or Python: PyQt QTreeview example - selection or Styling Qt QTreeView with CSS or Displaying tabular data in Qt5 ModelViews).

I have two files, one is the gui, one is the working class:

gui:

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")

        self.tree = QtWidgets.QTreeWidget(Dialog)
        self.tree.setGeometry(QtCore.QRect(10, 60, 760, 480))
        self.tree.setHeaderLabels(['circ', 'state', 'test'])
        self.tree.setSortingEnabled(True)

worker:

class AppWindow(QDialog):   
    def __init__(self, fullscreen=False):          
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()  


        self.timer = QTimer()
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.refresh_gui) 
        self.timer.start()


    def refresh_gui(self):
        self.painter = QPainter(self)
        tmp = {0: {"state": 1, "info": "hello"}, 1: {"state": 0, "info": "world"}}          
        for i in tmp:
            if tmp[i]["state"] == 0:
                painter.setPen(QPen(Qt.red,  8, Qt.SolidLine))
            else:
                painter.setPen(QPen(Qt.green,  8, Qt.SolidLine))
            circ = painter.drawEllipse(2,2,20,20)

            item = QtWidgets.QTreeWidgetItem(self.ui.tree, [circ, tmp[i]["state"], "empty"])
            item.setText(2, "circ painted")

I want to achive, that if state == 0 that a red circle is shown in the first column anf if state == 1 a green one. I do not know how to hand the QTreeWidgetItem a PyQt5.QtGui.QPainter object instead of a string.

Also, I do get the error:

QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
QPainter::end: Painter not active, aborted

and some lines lower (because of it): QPainter::setPen: Painter not active because I call self.painter = QPainter(self) which is discussed in this git issue from matplotlib but I fail to fix it in my code. I found this QPainter tutorial which paints on a QPixmap, which also works for me but is not what I am looking for here.

xtlc
  • 1,070
  • 1
  • 15
  • 41
  • The painter error you get has nothing to do with the matplotlib issue: you can't call a QPainter on a widget whenever you want, as it can only be done within a `paintEvent` created by Qt (triggered either by system request to "show" the widget, or after a call to `update()` or `repaint()`). If you want to display something else in an item view, you have to subclass a [QStyledItemDelegate](https://doc.qt.io/qt-5/qstyleditemdelegate.html) and overwrite its [`paint()`](https://doc.qt.io/qt-5/qstyleditemdelegate.html#paint) method. – musicamante Apr 04 '20 at 14:28

1 Answers1

2

The painting of a widget is not done in any method, but the paintEvent method must be overridden, which is called whenever it is necessary by Qt or using update or repaint by the developer. But in the case of classes that handle a model (that is, a lot of organized information) that inherit from QAbstractItemView, a delegate must be used if an item is going to be painted.

Considering the above, the solution is:

class Delegate(QtWidgets.QStyledItemDelegate):
    def paint(self, painter, option, index):
        state = index.data(QtCore.Qt.UserRole)
        color = (
            QtGui.QColor(QtCore.Qt.red) if state == 0 else QtGui.QColor(QtCore.Qt.green)
        )
        painter.setPen(QtGui.QPen(color, 4, QtCore.Qt.SolidLine))
        diameter = min(option.rect.width(), option.rect.height())
        rect = QtCore.QRect(0, 0, diameter // 2, diameter // 2)
        rect.moveCenter(option.rect.center())
        painter.drawEllipse(rect)


class AppWindow(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()

        delegate = Delegate(self.ui.tree)
        self.ui.tree.setItemDelegateForColumn(0, delegate)

        self.timer = QtCore.QTimer(interval=500, timeout=self.refresh_gui)
        self.timer.start()

    @QtCore.pyqtSlot()
    def refresh_gui(self):

        tmp = [{"state": 1, "info": "hello"}, {"state": 0, "info": "world"}]

        for d in tmp:
            item = QtWidgets.QTreeWidgetItem(self.ui.tree, ["", str(d["state"]), "empty"])
            item.setData(0, QtCore.Qt.UserRole, d["state"])
            item.setText(2, "circ painted")

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Works. Thank you. I have two questions now: (1) paint is not called, but because Delegate is of Type QStyledItemDelegate it is re-called, correct? (2) Why do I need the decorator? – xtlc Apr 04 '20 at 19:43