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)
I'm using Python3.7 on macOS 10.13.5.
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)
# ------
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)
# ------