Search code examples
pythonpyqtpyqt5qpainterpython-3.7

Why is the widget with a custom painting not visible?


So I'm trying to build a simple PyQt application with a custom widget. However, the painter does not paint anything. If I comment out line 44-45 (label = QLabel('Map');box.addWidget(label)), I can see a big colored rectangle. However, when I try to add a label above the rectangle, the rectangle doesn't show up anymore.

I think I may be using the painter wrong, but I'm not sure.

I'm new to PyQt and any comments on my coding style or logic will also be appreciated.

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import (QMainWindow,
                               QWidget,
                               QFrame,
                               QDesktopWidget,
                               QGridLayout,
                               QLabel,
                               QTextEdit,
                               QSplitter,
                               QVBoxLayout,
                               QApplication)


class Simulator(QMainWindow):
    def __init__(self):
        super().__init__()

        self.stdout = QTextEdit()
        self.stderr = QTextEdit()
        self.exec = QTextEdit()

        self.frame = QFrame()
        self.setCentralWidget(self.frame)
        self.screen = QDesktopWidget().screenGeometry()
        self.setGeometry(self.screen)
        self.grid = QGridLayout()
        self.frame.setLayout(self.grid)
        self.map = SimulatedFieldMap()

        # -- setting splitters
        splitter_r = QSplitter(Qt.Vertical)
        splitter_l = QSplitter(Qt.Vertical)
        splitter_h = QSplitter(Qt.Horizontal)
        splitter_h.addWidget(splitter_l)
        splitter_h.addWidget(splitter_r)
        # --------------

        # -- top left --
        frame = QFrame()
        box = QVBoxLayout()
        frame.setLayout(box)
        splitter_l.addWidget(frame)
        label = QLabel('Map')
        box.addWidget(label)
        box.addWidget(self.map)
        # ------

        # -- bottom left --
        box = QVBoxLayout()
        frame = QFrame()
        frame.setLayout(box)
        box.addWidget(QLabel('Exec'))
        box.addWidget(self.exec)
        splitter_l.addWidget(frame)
        # -------

        # -- top right --
        box = QVBoxLayout()
        frame = QFrame()
        frame.setLayout(box)
        splitter_r.addWidget(frame)
        box.addWidget(QLabel('STDOUT'))
        box.addWidget(self.stdout)
        # -------

        # -- bottom right --
        box = QVBoxLayout()
        frame = QFrame()
        frame.setLayout(box)
        splitter_r.addWidget(frame)
        box.addWidget(QLabel('STDERR'))
        box.addWidget(self.stderr)
        # -------

        self.grid.addWidget(splitter_h, 0, 0)
        splitter_h.setSizes((self.screen.width() * 0.7, self.screen.width() * 0.3))
        splitter_l.setSizes((self.screen.height() * 0.7, self.screen.height() * 0.3))
        splitter_r.setSizes((self.screen.height() * 0.7, self.screen.height() * 0.3))


class SimulatedFieldMap(QWidget):
    def __init__(self):
        super().__init__()

    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        self.paintMap(qp)
        qp.end()

    def paintMap(self, qp):
        qp.setBrush(QColor(200, 162, 200))  # lilac
        qp.setPen(QColor(200, 162, 200))
        geo = self.geometry()
        qp.drawRect(geo)


if __name__ == '__main__':
    app = QApplication([])
    app.setStyle('Fusion')
    sim = Simulator()
    sim.show()
    status = app.exec_()
    exit(status)

There is no colored rectangle on this

I'm using Python3.7 on macOS 10.13.5.


Solution

  • When a widget is painted, the internal coordinates are used, but geometry() is coordinates with respect to the parent, so you should not use it, instead you should use rect().

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Simulator(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
    
            self.stdout = QtWidgets.QTextEdit()
            self.stderr = QtWidgets.QTextEdit()
            self.exec = QtWidgets.QTextEdit()
    
            self.frame = QtWidgets.QFrame()
            self.setCentralWidget(self.frame)
    
            self.grid = QtWidgets.QGridLayout(self.frame)
            self.map = SimulatedFieldMap()
    
            # -- setting splitters
            splitter_r = QtWidgets.QSplitter(QtCore.Qt.Vertical)
            splitter_l = QtWidgets.QSplitter(QtCore.Qt.Vertical)
            splitter_h = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
            splitter_h.addWidget(splitter_l)
            splitter_h.addWidget(splitter_r)
            # --------------
    
            # -- top left --
            frame = QtWidgets.QFrame()
            box = QtWidgets.QVBoxLayout(frame)
            splitter_l.addWidget(frame)
            label = QtWidgets.QLabel('Map')
            box.addWidget(label)
            box.addWidget(self.map)
            # ------
    
            # -- bottom left --
            frame = QtWidgets.QFrame()
            box = QtWidgets.QVBoxLayout(frame)
            box.addWidget(QtWidgets.QLabel('Exec'))
            box.addWidget(self.exec)
            splitter_l.addWidget(frame)
            # -------
    
            # -- top right --
            frame = QtWidgets.QFrame()
            box = QtWidgets.QVBoxLayout(frame)
            splitter_r.addWidget(frame)
            box.addWidget(QtWidgets.QLabel('STDOUT'))
            box.addWidget(self.stdout)
            # -------
    
            # -- bottom right --
            frame = QtWidgets.QFrame()
            box = QtWidgets.QVBoxLayout(frame)
            splitter_r.addWidget(frame)
            box.addWidget(QtWidgets.QLabel('STDERR'))
            box.addWidget(self.stderr)
            # -------
    
            screen = QtWidgets.QDesktopWidget().screenGeometry()
    
            self.grid.addWidget(splitter_h, 0, 0)
            splitter_h.setSizes((screen.width() * 0.7, screen.width() * 0.3))
            splitter_l.setSizes((screen.height() * 0.7, screen.height() * 0.3))
            splitter_r.setSizes((screen.height() * 0.7, screen.height() * 0.3))
    
    
    class SimulatedFieldMap(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
    
        def paintEvent(self, event):
            qp = QtGui.QPainter(self)
            self.paintMap(qp)
    
        def paintMap(self, qp):
            qp.setBrush(QtGui.QColor(200, 162, 200))  # lilac
            qp.setPen(QtGui.QColor(200, 162, 200))
            qp.drawRect(self.rect())
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle('Fusion')
        sim = Simulator()
        sim.showMaximized()
        sys.exit(app.exec_())
    

    Update: If you want it to be placed in a certain position you should not place it in the layout but the label must be a child of the map and use move() to establish the position with respect to the map top-left.

    # -- top left --
    frame = QtWidgets.QFrame()
    box = QtWidgets.QVBoxLayout(frame)
    box.addWidget(self.map)
    splitter_l.addWidget(frame)
    label = QtWidgets.QLabel('Map', self.map)
    label.move(0, 100)    
    # ------
    

    enter image description here

    Update: The problem is caused by the vertical sizePolicy of the QLabel which is QSizePolicy::Preferred making it expand, a simple solution is to change it to QSizePolicy::Maximum, so the correct height is calculated according to the font.

    # -- top left --
    frame = QtWidgets.QFrame()
    box = QtWidgets.QVBoxLayout(frame)
    splitter_l.addWidget(frame)
    label = QtWidgets.QLabel('Map')
    sp = label.sizePolicy()
    sp.setVerticalPolicy(QtWidgets.QSizePolicy.Maximum)
    label.setSizePolicy(sp)
    box.addWidget(label)
    box.addWidget(self.map)
    # ------
    

    enter image description here