如何在 QGraphicsScene 中正确放置小部件

Posted

技术标签:

【中文标题】如何在 QGraphicsScene 中正确放置小部件【英文标题】:How to place widget correctly in QGraphicsScene 【发布时间】:2019-06-20 18:46:01 【问题描述】:

我正在尝试实现将放置在场景右侧的小部件(即兴工具箱)。 它应该是这样的:

在移动和缩放视图时将其正确放置并保持相同的大小和位置存在问题。

小部件本身是一个 QComboBox。我尝试将它作为 QGraphicsWidget 放在场景中,将 ZValue 设置为高数并将其设置为 QGraphicsLinearLayout。不幸的是,我无法将它与左侧对齐,并且我觉得我这样做的方式不对。

这是一个草图:

from PySide2 import QtWidgets, QtCore, QtGui
import sys

def hex2QColor(color):
    r = int(color[0:2], 16)
    g = int(color[2:4], 16)
    b = int(color[4:6], 16)
    return QtGui.QColor(r, g, b)

class rotateGroupBox(QtWidgets.QGroupBox):
    """Create content to fill the widget"""
    def __init__(self, parent=None):
        super(rotateGroupBox, self).__init__(parent)

        self.setTitle("Rotation")

        self.l_rotate = QtWidgets.QSpinBox()
        self.l_rotate.setRange(0, 360)

        self.r_rotate = QtWidgets.QSpinBox()
        self.r_rotate.setRange(0, 360)

        self.l_label = QtWidgets.QLabel("Left: ")
        self.r_label = QtWidgets.QLabel("Right: ")

        layout = QtWidgets.QVBoxLayout()

        l1 = QtWidgets.QHBoxLayout()
        l1.setContentsMargins(0,0,0,0)
        l1.addWidget(self.l_label)
        l1.addWidget(self.l_rotate)

        l2 = QtWidgets.QHBoxLayout()
        l2.setContentsMargins(0, 0, 0, 0)
        l2.addWidget(self.r_label)
        l2.addWidget(self.r_rotate)

        layout.addLayout(l1)
        layout.addLayout(l2)

        self.setLayout(layout)

class propertyBox(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(propertyBox, self).__init__(parent)

        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        self.backgroundColor = hex2QColor("efefef")
        self.foregroundColor = hex2QColor("333333")
        self.borderRadius = 10
        self.__mousePressPos = None
        self.__mouseMovePos = None

        self.draggable = True
        self.dragging_threshould = 5
        self.__mousePressPos = None
        self.__mouseMovePos = None

        gr1 = rotateGroupBox(self)
        gr2 = rotateGroupBox(self)
        gr3 = rotateGroupBox(self)
        gr4 = rotateGroupBox(self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(gr1)
        layout.addWidget(gr2)
        layout.addWidget(gr3)
        layout.addWidget(gr4)
        self.setLayout(layout)

    def paintEvent(self, event):
        # get current window size
        s = self.size()
        qp = QtGui.QPainter()
        qp.begin(self)
        #qp.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        qp.setPen(self.foregroundColor)
        qp.setBrush(self.backgroundColor)
        qp.drawRoundedRect(0, 0, s.width(), s.height(),
                           self.borderRadius, self.borderRadius)
        qp.end()

class graphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super(graphicsView, self).__init__(parent)

    def wheelEvent(self, event):
        factor = 1.41 ** (-event.delta() / 240)
        self.scale(factor, factor)
        global RAW
        RAW = True

    def mousePressEvent(self, event):
        if event.button() & QtCore.Qt.RightButton:
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
        else:
            super(graphicsView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() & QtCore.Qt.RightButton:
            self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
        else:
            super(graphicsView, self).mouseReleaseEvent(event)

app = QtWidgets.QApplication(sys.argv)

window = graphicsView()
window.resize(1000, 500)

scene = QtWidgets.QGraphicsScene()
window.setScene(scene)

box = scene.addWidget(propertyBox())

layout = QtWidgets.QGraphicsLinearLayout()
layout.setContentsMargins(0,0,0,0)
#layout.setAlignment(box, QtCore.Qt.AlignRight) # doesn't work
layout.addItem(box)

form = QtWidgets.QGraphicsWidget()
#form.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
form.setZValue(100000000)
form.setLayout(layout)
scene.addItem(form)

# Adding an item that should be overlapped by the property box
rect = QtWidgets.QGraphicsRectItem()
rect.setRect(0,0,200,200)
rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
scene.addItem(rect)

window.show()
sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

一种可能的解决方案是使小部件成为 viewport() 的子项,而不是项目,这样场景的变换就不会影响它。并在 viewport() 改变大小时调整你的位置:

from PySide2 import QtCore, QtGui, QtWidgets


class RotateGroupBox(QtWidgets.QGroupBox):
    """Create content to fill the widget"""

    def __init__(self, parent=None):
        super(RotateGroupBox, self).__init__(parent)

        self.setTitle("Rotation")

        self.l_rotate = QtWidgets.QSpinBox()
        self.l_rotate.setRange(0, 360)

        self.r_rotate = QtWidgets.QSpinBox()
        self.r_rotate.setRange(0, 360)

        self.l_label = QtWidgets.QLabel("Left: ")
        self.r_label = QtWidgets.QLabel("Right: ")

        layout = QtWidgets.QVBoxLayout(self)

        l1 = QtWidgets.QHBoxLayout()
        l1.setContentsMargins(0, 0, 0, 0)
        l1.addWidget(self.l_label)
        l1.addWidget(self.l_rotate)

        l2 = QtWidgets.QHBoxLayout()
        l2.setContentsMargins(0, 0, 0, 0)
        l2.addWidget(self.r_label)
        l2.addWidget(self.r_rotate)

        layout.addLayout(l1)
        layout.addLayout(l2)


class PropertyBox(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(PropertyBox, self).__init__(parent)

        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        self.backgroundColor = QtGui.QColor("salmon")
        self.foregroundColor = QtGui.QColor("red")
        self.borderRadius = 10

        gr1 = RotateGroupBox()
        gr2 = RotateGroupBox()
        gr3 = RotateGroupBox()
        gr4 = RotateGroupBox()

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(gr1)
        layout.addWidget(gr2)
        layout.addWidget(gr3)
        layout.addWidget(gr4)

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setPen(self.foregroundColor)
        painter.setBrush(self.backgroundColor)
        rect = QtCore.QRectF(
            QtCore.QPoint(),
            self.size() - 0.5 * painter.pen().width() * QtCore.QSize(1, 1),
        )
        painter.drawRoundedRect(rect, self.borderRadius, self.borderRadius)


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super(GraphicsView, self).__init__(parent)
        self.m_widgets = dict()

    def wheelEvent(self, event):
        factor = 1.41 ** (-event.delta() / 240)
        self.scale(factor, factor)

    def mousePressEvent(self, event):
        if event.button() & QtCore.Qt.RightButton:
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
        else:
            super(GraphicsView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() & QtCore.Qt.RightButton:
            self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
        else:
            super(GraphicsView, self).mouseReleaseEvent(event)

    def addFixedWidget(self, widget, alignment):
        widget.setParent(self.viewport())
        self.m_widgets[widget] = alignment

    def showEvent(self, event):
        self._update_fixed_widgets()
        super(GraphicsView, self).showEvent(event)

    def resizeEvent(self, event):
        self._update_fixed_widgets()
        super(GraphicsView, self).resizeEvent(event)

    def _update_fixed_widgets(self):
        r = self.viewport().rect()
        for w, a in self.m_widgets.items():
            p = QtCore.QPoint()

            if a & QtCore.Qt.AlignHCenter:
                p.setX((r.width() - w.width()) / 2)
            elif a & QtCore.Qt.AlignRight:
                p.setX(r.width() - w.width())

            if a & QtCore.Qt.AlignVCenter:
                p.setY((r.height() - w.height()) / 2)
            elif a & QtCore.Qt.AlignBottom:
                p.setY(r.height() - w.height())
            w.move(p)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    scene = QtWidgets.QGraphicsScene()
    window = GraphicsView()
    window.resize(1000, 500)
    window.setScene(scene)
    window.addFixedWidget(
        PropertyBox(), QtCore.Qt.AlignRight | QtCore.Qt.AlignTop
    )
    rect = QtWidgets.QGraphicsRectItem()
    rect.setRect(0, 0, 200, 200)
    rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
    scene.addItem(rect)
    window.show()
    sys.exit(app.exec_())

【讨论】:

以上是关于如何在 QGraphicsScene 中正确放置小部件的主要内容,如果未能解决你的问题,请参考以下文章

如何在场景 QGraphicsScene 上居中小部件

如何正确使用 QGraphicsLinearLayout 和 QGraphicsScene 来定位 QGraphicsItems?

如何在 QGraphicsScene 中缩放图像? (Qt 5.11)

如何获取 QGraphicsScene 的小部件(QGraphicsProxyWidget)?

如何在鼠标正下方的 QGraphicsView 上正确放置小部件?

从 QGraphicsScene/QgraphicsItemGroup/QGraphicsView 中正确删除项目