如何修复QFocusEvent行为

时间:2019-07-01 11:20:58

标签: python python-3.x pyside2

我正在制作一个小部件来访问和修改QGraphicsRectItem 属性(比例,旋转度等)。主要思想是在单击项目时显示一个小部件。当您单击另一个项目时,该小部件应删除并替换为另一个(显示另一个项目的属性。)

我已经使用 QFocusEvent 实现了这一点,很不幸,它给了我一个 SIGSEGV 错误。我完全理解为什么会发生这种情况,但无法找出另一种方法。

这是一个python草图:

from PySide2 import QtWidgets, QtCore, QtGui
import sys, shiboken2


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

        self.setScene(self.scene)

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

class boxItem(QtWidgets.QGraphicsRectItem):
    def __init__(self, parent):
        super(boxItem, self).__init__()

        self.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable
                      | QtWidgets.QGraphicsItem.ItemIsMovable
                      | QtWidgets.QGraphicsItem.ItemIsFocusable)

        self.rect = QtCore.QRectF(0, 0, 200, 200)
        self.setRect(self.rect)

        self.parent = parent


    def focusInEvent(self, event:QtGui.QFocusEvent):
        self.pBox = propBox()
        self.parent.layout.addWidget(self.pBox)
        self.pBox.r_box.setValue(self.rotation())
        self.pBox.r_box.valueChanged.connect(self.setRotationAngle)


    def focusOutEvent(self, event:QtGui.QFocusEvent):
        # Here's a shiboken(equivalent to sip in PyQt) deletes c++ object and a python wrapper
        # It works as expected but because of focusOutEvent it deletes the widget when i click on it
        # There's when the error appears.

        self.parent.layout.removeWidget(self.pBox)
        shiboken2.delete(self.pBox)

    def setRotationAngle(self, degrees):
        br = self.boundingRect()
        self.setTransformOriginPoint(QtCore.QPointF(br.width() / 2, br.height() / 2))
        self.setRotation(-degrees)
        self.update()



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

        self.layout = QtWidgets.QVBoxLayout()
        self.layout.setAlignment(QtCore.Qt.AlignTop)
        self.setLayout(self.layout)

        self.r_label = QtWidgets.QLabel("Rotation:", self)
        self.r_box = QtWidgets.QSpinBox(self)
        self.r_layout = QtWidgets.QHBoxLayout()
        self.r_layout.addWidget(self.r_label)
        self.r_layout.addWidget(self.r_box)

        self.s_label = QtWidgets.QLabel("Scale:")
        self.s_box = QtWidgets.QSpinBox(self)
        self.s_layout = QtWidgets.QHBoxLayout()
        self.s_layout.addWidget(self.s_label)
        self.s_layout.addWidget(self.s_box)

        self.layout.addLayout(self.r_layout)
        self.layout.addLayout(self.s_layout)

class mainWidget(QtWidgets.QWidget):
    def __init__(self):
        super(mainWidget, self).__init__()

        self.layout = QtWidgets.QHBoxLayout()
        self.setLayout(self.layout)

        box1 = boxItem(self)
        box1.setRotation(45)
        box2 = boxItem(self)

        self.scene = QtWidgets.QGraphicsScene(self)
        self.scene.setSceneRect(0, 0, 500, 300)
        self.scene.addItem(box1)
        self.scene.addItem(box2)

        self.view = graphicsView(self.scene)

        self.pBox = propBox(self)

        self.layout.addWidget(self.view)



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = mainWidget()
    window.show()
    sys.exit(app.exec_())

1 个答案:

答案 0 :(得分:1)

我没有分析过,因为您的程序崩溃了,所以我的答案将不再关注它,而是将其关注于潜在的问题。

您只需要在需要时隐藏或显示的PropertyBox。我看到尝试使用focusInEvent和focusOutEvent方法的问题,因为当您按下PropertyBox时,该项目将失去焦点,因此PropertyBox本身将被移除(也许这是崩溃的原因)。因此,应该使用mousePressEvent并检查是否单击了某个项目,而不是使用焦点。

考虑上述解决方案是:

import random
from PySide2 import QtCore, QtGui, QtWidgets


class BoxItem(QtWidgets.QGraphicsRectItem):
    def __init__(self, parent=None):
        super(BoxItem, self).__init__(QtCore.QRectF(0, 0, 200, 200), parent)

        self.setFlags(
            QtWidgets.QGraphicsItem.ItemIsSelectable
            | QtWidgets.QGraphicsItem.ItemIsMovable
            | QtWidgets.QGraphicsItem.ItemIsFocusable
        )
        br = self.boundingRect()
        self.setTransformOriginPoint(
            QtCore.QPointF(br.width() / 2, br.height() / 2)
        )


class PropertyBox(QtWidgets.QWidget):
    rotationChanged = QtCore.Signal(float)
    scaleChanged = QtCore.Signal(float)

    def __init__(self, parent=None):
        super(PropertyBox, self).__init__(parent)
        self.m_rotation_spinbox = QtWidgets.QDoubleSpinBox(
            minimum=-360, maximum=360
        )
        self.m_rotation_spinbox.valueChanged.connect(self.rotationChanged)
        self.m_scale_spinbox = QtWidgets.QDoubleSpinBox(
            minimum=0, maximum=100, singleStep=0.1
        )
        self.m_scale_spinbox.valueChanged.connect(self.scaleChanged)

        lay = QtWidgets.QFormLayout(self)
        lay.addRow("Rotation:", self.m_rotation_spinbox)
        lay.addRow("Scale:", self.m_scale_spinbox)

    @property
    def rotation(self):
        return self.m_rotation_spinbox.value()

    @rotation.setter
    def rotation(self, value):
        self.m_rotation_spinbox.setValue(value)

    @property
    def scale(self):
        return self.m_scale_spinbox.value()

    @scale.setter
    def scale(self, value):
        self.m_scale_spinbox.setValue(value)


class GraphicsView(QtWidgets.QGraphicsView):
    currentItemChanged = QtCore.Signal(QtWidgets.QGraphicsItem)

    def mousePressEvent(self, event):
        super(GraphicsView, self).mousePressEvent(event)
        it = self.itemAt(event.pos())
        self.currentItem = it

    @property
    def currentItem(self):
        if not hasattr(self, "_currentItem"):
            self._currentItem = None
        return self._currentItem

    @currentItem.setter
    def currentItem(self, it):
        if self.currentItem != it:
            self._currentItem = it
            self.currentItemChanged.emit(it)


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

        self.m_scene = QtWidgets.QGraphicsScene()
        self.m_view = GraphicsView(self.m_scene)
        self.m_view.currentItemChanged.connect(self.onCurrentItemChanged)

        for i in range(4):
            it = BoxItem()
            self.m_scene.addItem(it)
            it.setPos(QtCore.QPointF(100 * i, 100 * i))
            it.setBrush(QtGui.QColor(*random.sample(range(255), 3)))

        self.m_property_box = PropertyBox()
        self.m_property_box.rotationChanged.connect(self.onRotationChanged)
        self.m_property_box.scaleChanged.connect(self.onScaleChanged)
        self.m_property_box.hide()

        lay = QtWidgets.QHBoxLayout(self)
        lay.addWidget(self.m_view)
        lay.addWidget(self.m_property_box)

    @QtCore.Slot(QtWidgets.QGraphicsItem)
    def onCurrentItemChanged(self, item):
        self.m_property_box.setVisible(item is not None)
        if item is not None:
            self.m_property_box.rotation = item.rotation()
            self.m_property_box.scale = item.scale()

    @QtCore.Slot(float)
    def onRotationChanged(self, rotation):
        it = self.m_view.currentItem
        if it is not None:
            it.setRotation(rotation)

    @QtCore.Slot(float)
    def onScaleChanged(self, scale):
        it = self.m_view.currentItem
        if it is not None:
            it.setScale(scale)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())