【问题标题】:QGraphicsAnchorLayout not anchoring in QGraphicsSceneQGraphicsAnchorLayout 不在 QGraphicsScene 中锚定
【发布时间】:2020-02-03 01:52:53
【问题描述】:

我目前在理解 QGraphicsScene 中 QGraphicsAnchorLayout 的行为时遇到问题。我创建了 4 个框并锚定了每个框的角,但似乎没有正确应用锚点,或者至少没有像我认为的那样。

黄色框应始终位于 QGraphicsScene 的左上角,即使在 GraphicsView 展开时也是如此。蓝色框被锚定在右侧的黄色框旁边,其顶部与 QGraphicsScene/viewport 的顶部重合。

绿色框的左上角固定在蓝色框的右下角,红色框也固定在绿色框的右下角。但这就是我得到的:

我希望黄色框始终位于图形场景/视口的顶部。而且我希望它即使在向右滚动时也始终保持可见,但我相信这可能是一个单独的问题。但是,当我垂直展开窗口时,所有框都居中,包括我希望保留在顶部的黄色框。

蓝色、绿色和红色框似乎与我应用的锚点没有相似之处。

以下是我用来生成它的代码。这些锚是如何工作的,我可以做些什么来纠正这个问题?

import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from debug_utils import *
from PyQt5.QtWidgets import QGraphicsAnchorLayout, QGraphicsWidget, QGraphicsLayoutItem

def qp(p):
    return "({}, {})".format(p.x(), p.y())

class box(QtWidgets.QGraphicsWidget):
    pressed = QtCore.pyqtSignal()

    def __init__(self, rect, color, parent=None):
        super(box, self).__init__(parent)
        self.raw_rect = rect
        self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
        self.color = color

    def boundingRect(self):
        pen_adj = 0
        return self.rect.normalized().adjusted(-pen_adj, -pen_adj, pen_adj, pen_adj)

    def paint(self, painter, option, widget):
        r = self.boundingRect()

        brush = QtGui.QBrush()
        brush.setColor(QtGui.QColor(self.color))
        brush.setStyle(Qt.SolidPattern)

        #rect = QtCore.QRect(0, 0, painter.device().width(), painter.device().height())
        painter.fillRect(self.boundingRect(), brush)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setPen(Qt.darkGray)

        painter.drawRect(self.boundingRect())
        #painter.drawRect(0, 0, max_time*char_spacing, self.bar_height)

    def mousePressEvent(self, ev):
        self.pressed.emit()
        self.update()

    def mouseReleaseEvent(self, ev):
        self.update()

class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super(GraphicsView, self).__init__(parent)
        scene = QtWidgets.QGraphicsScene(self)
        self.setScene(scene)
        self.numbers = []
        self.setMouseTracking(True)

        l = QGraphicsAnchorLayout()
        l.setSpacing(0)
        w = QGraphicsWidget()
        #painter = QtGui.QPainter(self)
        w.setPos(0, 0)
        w.setLayout(l)
        scene.addItem(w)
        self.main_widget = w
        self.main_layout = l

        self.makeBoxs()

    def makeBoxs(self):
        rect = [0, 0, 600, 250]
        blue_box = box(rect, QtGui.QColor(0, 0, 255, 128))
        green_box = box(rect, QtGui.QColor(0, 255, 0, 128))
        red_box = box([0, 0, 200, 50], QtGui.QColor(255, 0, 0, 128))
        yellow_box_left = box([0, 0, 75, 600], QtGui.QColor(255, 255, 0, 128))
        #self.scene().setSceneRect(blue_box.rect)
        #self.scene().setSceneRect(bar_green.rect)

        # Adding anchors adds the item to the layout which is part of the scene
        self.main_layout.addCornerAnchors(yellow_box_left, Qt.TopLeftCorner, self.main_layout, Qt.TopLeftCorner)
        self.main_layout.addCornerAnchors(blue_box, Qt.TopLeftCorner, yellow_box_left, Qt.TopRightCorner)
        self.main_layout.addCornerAnchors(green_box, Qt.TopLeftCorner, blue_box, Qt.BottomRightCorner)
        self.main_layout.addCornerAnchors(red_box, Qt.TopLeftCorner, green_box, Qt.BottomRightCorner)

        #self.main_layout.addAnchor(bar_green, Qt.AnchorTop, blue_box, Qt.AnchorBottom)
        #self.main_layout.addAnchor(bar_green, Qt.AnchorLeft, blue_box, Qt.AnchorRight)

    def printStatus(self, pos):
        msg = "Viewport Position: " + str(qp(pos))
        v = self.mapToScene(pos)
        v = QtCore.QPoint(v.x(), v.y())
        msg = msg + ",  Mapped to Scene: " + str(qp(v))
        v = self.mapToScene(self.viewport().rect()).boundingRect()
        msg = msg + ",  viewport Mapped to Scene: " + str(qp(v))
        v2 = self.mapToScene(QtCore.QPoint(0, 0))
        msg = msg + ",  (0, 0) to scene: " + qp(v2)
        self.parent().statusBar().showMessage(msg)

    def mouseMoveEvent(self, event):
        pos = event.pos()
        self.printStatus(pos)
        super(GraphicsView, self).mouseMoveEvent(event)

    def resizeEvent(self, event):
        self.printStatus(QtGui.QCursor().pos())
        h = self.mapToScene(self.viewport().rect()).boundingRect().height()
        r = self.sceneRect()
        r.setHeight(h)

        height = self.viewport().height()
        for item in self.items():
            item_height = item.boundingRect().height()

        super(GraphicsView, self).resizeEvent(event)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        gv = GraphicsView()
        self.setCentralWidget(gv)
        self.setGeometry(475, 250, 600, 480)
        scene = self.scene = gv.scene()
        sb = self.statusBar()

def main():
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

编辑:添加预期输出 根据锚点的定义方式,我希望输出如下所示。因为我还不能真正创建我需要的东西,所以我在 PowerPoint 中创建了这个。但是,当然,除了让它工作之外,我还希望了解如何在更一般的意义上使用锚点。

编辑 2: 再次感谢您的更新。这与我所期望的不太一样,滚动时出现了一个奇怪的神器。澄清一下,

  1. 我希望黄色小部件始终可见,位于视口左上角,z 顺序最高。我想你已经提供了。
  2. 所有其他小部件都应该滚动,保持它们的相对锚点。我将蓝色框的锚点移至黄色框,但所有框都向左对齐。
  3. 在您当前的实现中,非黄色框不会滚动,但是当我调整滚动条的大小或左右移动滚动条时,我看到了这个工件:

【问题讨论】:

    标签: python pyqt pyqt5 qgraphicsview qgraphicsscene


    【解决方案1】:

    您有 2 个错误:

    • 您的 Box 类构建不佳,它没有覆盖 boundingRect 它只设置大小,因为位置将由布局处理。

    • 布局的位置总是相对于设置它的小部件。在您的情况下,您希望 main_layout 的左上角与视口的左上角匹配,因此您必须修改 main_widget 的左上角以匹配。

    综合以上,解决办法是:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    def qp(p):
        return "({}, {})".format(p.x(), p.y())
    
    
    class Box(QtWidgets.QGraphicsWidget):
        pressed = QtCore.pyqtSignal()
    
        def __init__(self, size, color, parent=None):
            super(Box, self).__init__(parent)
            self.setMinimumSize(size)
            self.setMaximumSize(size)
            self.color = color
    
        def paint(self, painter, option, widget):
            brush = QtGui.QBrush()
            brush.setColor(QtGui.QColor(self.color))
            brush.setStyle(QtCore.Qt.SolidPattern)
    
            painter.fillRect(self.boundingRect(), brush)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            painter.setPen(QtCore.Qt.darkGray)
    
            painter.drawRect(self.boundingRect())
    
        def mousePressEvent(self, event):
            self.pressed.emit()
            super().mousePressEvent(event)
    
    
    class GraphicsView(QtWidgets.QGraphicsView):
        messageChanged = QtCore.pyqtSignal(str)
    
        def __init__(self, parent=None):
            super(GraphicsView, self).__init__(parent)
            self.setMouseTracking(True)
            self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
            scene = QtWidgets.QGraphicsScene(self)
            self.setScene(scene)
    
            l = QtWidgets.QGraphicsAnchorLayout()
            l.setSpacing(0)
            w = QtWidgets.QGraphicsWidget()
            self.scene().sceneRectChanged.connect(self.update_widget)
            self.horizontalScrollBar().valueChanged.connect(self.update_widget)
            self.verticalScrollBar().valueChanged.connect(self.update_widget)
            w.setLayout(l)
            scene.addItem(w)
            self.main_widget = w
            self.main_layout = l
    
            self.makeBoxs()
    
        def makeBoxs(self):
            blue_box = Box(QtCore.QSizeF(300, 125), QtGui.QColor(0, 0, 255, 128))
            green_box = Box(QtCore.QSizeF(300, 125), QtGui.QColor(0, 255, 0, 128))
            red_box = Box(QtCore.QSizeF(100, 25), QtGui.QColor(255, 0, 0, 128))
            yellow_box = Box(QtCore.QSizeF(37.5, 300), QtGui.QColor(255, 255, 0, 128))
    
            # yellow_box_left top-left
            self.main_layout.addCornerAnchors(
                yellow_box,
                QtCore.Qt.TopLeftCorner,
                self.main_layout,
                QtCore.Qt.TopLeftCorner,
            )
            self.main_layout.addCornerAnchors(
                blue_box, QtCore.Qt.TopLeftCorner, yellow_box, QtCore.Qt.TopRightCorner
            )
            self.main_layout.addCornerAnchors(
                green_box, QtCore.Qt.TopLeftCorner, blue_box, QtCore.Qt.BottomRightCorner
            )
            self.main_layout.addCornerAnchors(
                red_box, QtCore.Qt.TopLeftCorner, green_box, QtCore.Qt.BottomRightCorner
            )
    
            # self.setSceneRect(self.scene().itemsBoundingRect())
    
        def update_widget(self):
            vp = self.viewport().mapFromParent(QtCore.QPoint())
            tl = self.mapToScene(vp)
            geo = self.main_widget.geometry()
            geo.setTopLeft(tl)
            self.main_widget.setGeometry(geo)
    
        def resizeEvent(self, event):
            self.update_widget()
            super().resizeEvent(event)
    
        def mouseMoveEvent(self, event):
            pos = event.pos()
            self.printStatus(pos)
            super(GraphicsView, self).mouseMoveEvent(event)
    
        def printStatus(self, pos):
            msg = "Viewport Position: " + str(qp(pos))
            v = self.mapToScene(pos)
            v = QtCore.QPoint(v.x(), v.y())
            msg = msg + ",  Mapped to Scene: " + str(qp(v))
            v = self.mapToScene(self.viewport().rect()).boundingRect()
            msg = msg + ",  viewport Mapped to Scene: " + str(qp(v))
            v2 = self.mapToScene(QtCore.QPoint(0, 0))
            msg = msg + ",  (0, 0) to scene: " + qp(v2)
            self.messageChanged.emit(msg)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            gv = GraphicsView()
            self.setCentralWidget(gv)
    
            self.setGeometry(475, 250, 600, 480)
    
            gv.messageChanged.connect(self.statusBar().showMessage)
    
    
    def main():
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    

    【讨论】:

    • 太棒了,非常感谢!出现两个问题: 1、在你的update_widget中,如果你把黄色框的左上角设置为视口的左上角,为什么向右滚动时黄色框会消失? 2.当我向右滚动直到滚动条消失时,为什么会有这么多额外的空白? GraphicsScene 不应该只覆盖彩色框的边界框吗?最终我的目标是修复视口中的黄色框,让其他框在下方滚动到左侧,但现在我需要了解为什么设置左上角只设置顶部而不设置左侧。
    • @HonestMath 1) 我没有意识到这个错误。 2)GraphicsScene 就像世界,QGraphicsView 是相机,所以世界不仅覆盖了项目,它还可以覆盖更多。你只是想让我覆盖这些项目吗?如果是这样,那与您之前的要求相矛盾。
    • 如果我说的内容令人困惑,我深表歉意。我将蓝色盒子固定在黄色盒子上作为测试,因为我无法让它工作。我实际上希望黄色框保持在视口的左上角,无论水平滚动如何。所有其他框应该正常运行,因为它们将在 GraphicsScene 中以固定位置左右滚动。我希望一旦我将场景放大到足以包含所有框,滚动条就会消失。这说明清楚了吗?
    • 谢谢,我尝试了您更新的解决方案,但我看到了一些奇怪的行为。我用更多的 cmets 更新了这个问题。
    • @HonestMath conda, ...., mmm, 最新版本的 conda 在 PyQt5 上有几个错误,最近几周发布的问题适用于 pypi 的 PyQt5,但不适用于提供conda,解决方法是使用pypi版本,所以我建议你不要使用conda提供的PyQt5。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-17
    • 2020-05-12
    • 2013-07-30
    相关资源
    最近更新 更多