为什么带有自定义绘画的小部件不可见?

Why is the widget with a custom painting not visible?

所以我正在尝试使用自定义小部件构建一个简单的 PyQt 应用程序。然而,画家什么也没画。如果我注释掉第 44-45 行 (label = QLabel('Map');box.addWidget(label)),我会看到一个大的彩色矩形。但是,当我尝试在矩形上方添加标签时,矩形不再显示。

我想我可能用错了画家,但我不确定。

我是 PyQt 的新手,任何对我的编码风格或逻辑的评论也将不胜感激。

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)

我在 macOS 10.13.5 上使用 Python3.7。

绘制小部件时,使用内部坐标,但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_())

更新:如果你想把它放在某个位置,你不应该把它放在布局中,但标签必须是地图的子项并使用move() 建立相对于地图左上角的位置。

# -- 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)    
# ------

更新: 问题是QLabel的vertical sizePolicy QSizePolicy::Preferred 导致展开,一个简单的解决办法是改成QSizePolicy::Maximum,所以根据字体计算出正确的高度。

# -- 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)
# ------

原因

当你在布局中添加一个QLabel时,它的大小策略默认设置为QSizePolicy.Policy(Preferred),它将占据整个space,不留space 为您的小部件 SimulatedFieldMap。基本上,您的自定义小部件在那里,但它的高度为 0,因此不可见。

灵魂

一种解决方案是通过将标签设置为固定值来限制标签​​的高度,例如14。为此,在 label = QLabel('Map') 之后添加 label.setFixedHeight(14).

结果

这是这个解决方案的结果:

注意:出于演示目的,我已手动将左侧垂直分隔线向下移动。