QGraphicsAnchorLayout не привязывается в QGraphicsScene - PullRequest
1 голос
/ 04 октября 2019

В настоящее время у меня возникают проблемы с пониманием поведения QGraphicsAnchorLayout в QGraphicsScene. Я создаю 4 поля и привязываю углы каждого из них, но якоря, кажется, не применяются должным образом, или, по крайней мере, так, как я полагал, что они будут.

Желтое поле должно быть в верхнем левом углу QGraphicsScene ввсе время, даже когда GraphicsView расширяется. Синяя рамка прикреплена так, чтобы она появлялась рядом с желтой рамкой справа, а ее верхняя часть совпадает с вершиной QGraphicsScene / viewport.

Верхний левый угол зеленой рамки привязывается к нижней правой частисиней коробки и аналогично для красной коробки к зеленой коробке. Но это то, что я получаю:

enter image description here

Я ожидаю, что желтый прямоугольник всегда будет в верхней части графической сцены / окна просмотра. И я бы хотел, чтобы он всегда оставался видимым даже при правильной прокрутке, но я считаю, что это, вероятно, будет отдельной проблемой. Тем не менее, когда я расширяю окно по вертикали, все поля располагаются по центру, включая желтый прямоугольник, который, как я ожидал, останется сверху.

Синие, зеленые и красные рамки, похоже, не похожи на якоря, которые я применял.

Ниже приведен код, который я использовал для создания этого. Как работают эти якоря и что я могу сделать, чтобы исправить это?

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. Но, конечно, в дополнение к тому, чтобы заставить это работать, я надеюсь понять, как использовать якоря в более общем смысле.

enter image description here

РЕДАКТИРОВАТЬ 2: Еще раз спасибо за обновление. Это не совсем то, что я ожидал, и при прокрутке возникает странный артефакт. Просто чтобы уточнить,

  1. Я ожидаю, что желтый виджет будет виден всегда, в верхнем левом углу окна просмотра с самым высоким z-порядком. Я думаю, что вы предоставили это.
  2. Все остальные виджеты должны прокручиваться, сохраняя свои относительные привязки. Я удалил привязку синего поля к желтому, но затем все поля выровнялись по левому краю.
  3. В текущей реализации не-желтые поля не прокручиваются, но когда я изменяю размер или перемещаю полосу прокрутки вправо и назадслева я вижу этот артефакт:

enter image description here

1 Ответ

2 голосов
/ 04 октября 2019

У вас есть 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()

enter image description here

...