Как исправить поведение QFocusEvent - PullRequest
1 голос
/ 01 июля 2019

Я делаю виджет для доступа и изменения QGraphicsRectItem свойства (масштаб, вращение и т. д.). Основная идея - показать виджет при нажатии на элемент. Когда вы нажимаете на другой элемент, этот виджет должен быть удален и заменен другим (показывая свойства другого элемента.)

Я реализовал это с помощью QFocusEvent и, к сожалению, это дает мне ошибку SIGSEGV . Я полностью понимаю, почему это происходит, но не могу понять, как это сделать по-другому.

Вот набросок питона:

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 Ответ

1 голос
/ 01 июля 2019

Я не проанализировал, потому что ваша программа не работает, поэтому мой ответ не будет сосредоточен на ней, вместо этого я сосредоточусь на основной проблеме.

Вам просто нужно иметь 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_())
...