使QGraphicsItem只能在一个图形视图中选择

时间:2014-07-07 17:45:13

标签: python qt pyside qgraphicsview qgraphicsscene

我有一个设置,其中两个QGraphicView显示一个QGraphicsScene。其中一个视图是另一个细节的概述。想象一下:

enter image description here

标记详细视图当前边界的矩形是场景的一部分。上部视图中的白色矩形,我将在下面的文本中称为“边界框”

我想要的是能够点击概览 - QGraphicsView并拖动边界框以触发细节滚动 - QGraphicsView。显然,边界框必须只能在概览中QGraphicsView点击,否则我将无法在细节中进行操作 - QGraphicsView,因为边界框覆盖了整个细节视图

那么如何才能使QGraphicsItem只能从单个QGraphicsView中选择,或者如何仅将QGraphicsItem“插入”QGraphicsView QGraphicsScenes ?我可以嵌套{{1}},以便一个是另一个的副本加上一些额外的项目吗?

4 个答案:

答案 0 :(得分:1)

扩展我的另一个答案,只关注可移动的QGraphicsItem我专门为你的任务做了一个例子。

from PySide import QtGui, QtCore

# special GraphicsRectItem that is aware of its position and does something if the position is changed
class MovableGraphicsRectItem(QtGui.QGraphicsRectItem):

    def __init__(self, callback=None):
        super().__init__()
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.PointingHandCursor)
        self.callback = callback

    def itemChange(self, change, value):
        if change == QtGui.QGraphicsItem.ItemPositionChange and self.callback:
            self.callback(value)

        return super().itemChange(change, value)

app = QtGui.QApplication([])

# the scene with some rectangles
scene = QtGui.QGraphicsScene()
scene.addRect(30, 30, 100, 50, pen=QtGui.QPen(QtCore.Qt.darkGreen))
scene.addRect(150, 0, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkYellow))
scene.addRect(80, 80, 100, 20, pen=QtGui.QPen(QtCore.Qt.darkMagenta))
scene.addRect(200, 10, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkRed))

window = QtGui.QWidget()

# put two graphicsviews into the window with different scaling for each
layout = QtGui.QVBoxLayout(window)
v1 = QtGui.QGraphicsView(scene)
v1.setFixedSize(500, 100)
v1.scale(0.5, 0.5)
v1.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
v1.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(v1)
v2 = QtGui.QGraphicsView(scene)
v2.setFixedSize(500, 500)
v2.scale(5, 5)
v2.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
v2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(v2)

# the tracker rectangle
tracker = MovableGraphicsRectItem(lambda pos: v2.setSceneRect(pos.x(), pos.y(), 100, 100))
tracker.setRect(0, 0, 100, 100)
v2.setSceneRect(0, 0, 100, 100)
tracker.setPen(QtGui.QPen(QtCore.Qt.darkCyan))
scene.addItem(tracker)

window.show()
app.exec_()

您不需要拥有仅在一个视图或另一个视图中可见的项目,您只需将一个视图的场景矩形限制在可在另一个视图中可见和可拖动的场景中的可拖动矩形内部。看图像。

enter image description here

答案 1 :(得分:1)

我非常喜欢这个想法,并且我试图将其概括为创建一个小部件,您将“主视图”传递给它,并创建一个可用于平移和放大的概述。不幸的是我没有工作但现在还没有时间研究它,但我想我会分享到目前为止的进展。

这是小部件代码:

fopen()

这是测试脚本代码:

"""
Overview widget
"""
from PyQt4 import QtGui, QtCore

class MovableGraphicsRectItem(QtGui.QGraphicsRectItem):
    '''special GraphicsRectItem that is aware of its position and does
    something if the position is changed'''
    def __init__(self, callback=None):
        super(MovableGraphicsRectItem, self).__init__()
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable |
                      QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.PointingHandCursor)
        self.callback = callback

    def itemChange(self, change, value):
        if change == QtGui.QGraphicsItem.ItemPositionChange and self.callback:
            self.callback(value)

        return super(MovableGraphicsRectItem, self).itemChange(change, value)

    def activate(self):
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable |
                      QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.PointingHandCursor)

    def deactivate(self):
        self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, False)
        self.setFlag(QtGui.QGraphicsItem.ItemSendsScenePositionChanges, False)
        self.setCursor(QtCore.Qt.ArrowCursor)


class MouseInsideFilterObj(QtCore.QObject):
    def __init__(self, enterCallback, leaveCallback):
        QtCore.QObject.__init__(self)
        self.enterCallback = enterCallback
        self.leaveCallback = leaveCallback

    def eventFilter(self, obj, event):
        if event.type() == 10:  # QtCore.QEvent.Type.Enter:
            self.enterCallback(obj)
            print('Enter event')

        if event.type() == 11:  # QtCore.QEvent.Type.Leave:
            self.leaveCallback(obj)
            print('Leave event')

        return False


class Overview(QtGui.QGraphicsView):
    '''provides a view that shows the entire scene and shows the area that
    the main view is zoomed to. Alows user to move the view area around and
    change the zoom level'''

    def __init__(self, mainView):
        QtGui.QGraphicsView.__init__(self)
        self.setWindowTitle('Overview')
        self.resize(QtCore.QSize(400, 300))

        self._mainView = mainView
        self.setScene(mainView.scene())

        mouseFilter = MouseInsideFilterObj(self.enterGV, self.leaveGV)
        self.viewport().installEventFilter(mouseFilter)

        self._tracker = MovableGraphicsRectItem(
           lambda pos: self._mainView.setSceneRect(
               QtCore.QRectF(self._mainView.viewport().geometry())))
        self._tracker.setRect(self._getMainViewArea_())
        self._tracker.setPen(QtGui.QPen(QtCore.Qt.darkCyan))
        self.scene().addItem(self._tracker)

    def _getMainViewArea_(self):
        mainView = self._mainView
        visibleSceneRect = mainView.mapToScene(
            mainView.viewport().geometry()).boundingRect()
        return visibleSceneRect

    def resizeEvent(self, event):
        self.fitInView(self.sceneRect(), QtCore.Qt.KeepAspectRatio)

    def leaveGV(self, gv):
        if gv is self.overview:
            print('exited overview')
            self.tracker.deactivate()

    def enterGV(self, gv):
        if gv is self.overview:
            print('using overview')
            self.tracker.activate()

答案 2 :(得分:0)

默认情况下,

QGraphicsItems已禁用某些功能以最大限度地提高性能。通过启用这些功能,您可以使它们移动,您可以让它们知道它们的位置。理想情况下,人们可以使用信号/插槽机制向其他人通知更改,但出于性能原因QGraphicsItems不再继承QObject。但是,始终可以发送事件或手动调用回调。

你必须:

  • 启用QGraphicsItem.ItemIsMovable
  • 的标记QGraphicsItem.ItemSendsScenePositionChangesQGraphicsItem
  • 提供方法itemChange(change, value)的自定义实现,并在其中收听QGraphicsItem.ItemPositionChange更改。
  • 根据这些变化采取相应行动(在您的情况下更改详细视图)。

一个小例子:

from PySide import QtGui, QtCore

class MovableGraphicsRectItem(QtGui.QGraphicsRectItem):
    """
        A QGraphicsRectItem that can be moved and is aware of its position.
    """

    def __init__(self):
        super().__init__()
        # enable moving and position tracking
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        # sets a non-default cursor
        self.setCursor(QtCore.Qt.PointingHandCursor)

    def itemChange(self, change, value):
        if change == QtGui.QGraphicsItem.ItemPositionChange:
            print(value)
        return super().itemChange(change, value)

app = QtGui.QApplication([])

# create our movable rectangle
rectangle = MovableGraphicsRectItem()
rectangle.setRect(0, 0, 100, 100)

# create a scene and add our rectangle
scene = QtGui.QGraphicsScene()
scene.addItem(rectangle)

# create view, set fixed scene rectangle and show
view = QtGui.QGraphicsView(scene)
view.setSceneRect(0, 0, 600, 400)
view.show()

app.exec_()

在此示例(Python 3.X)中,您可以拖动矩形,并将更改的位置打印到控制台。

更多评论:

  • 您有两个视图和两个相关场景。
  • 它们的显示部分重叠,但这不是问题,因为顶视图将始终消耗所有鼠标事件。
  • 要在其他视图中更改内容,您只需从覆盖itemChange方法发送事件或调用回调。
  • 您还可以通过继承QGraphicsRectItemQObject来添加信号/插槽功能,然后定义信号并将其发出。
  • 如果您偶然想要一个可移动且位置感知的椭圆或其他项目,则需要为每个xxxItem类创建自定义类。我多次偶然发现这个问题并认为这可能是设计的一个缺点。

答案 3 :(得分:0)

通过在概述QgraphcisView上安装Eventfilter,扩展了Trilarion的答案,我能够解决问题。在Enter事件中,启用了拖动,在Leave事件中,拖动被禁用。

from PySide import QtGui, QtCore

# special GraphicsRectItem that is aware of its position and does something if the position is changed
class MovableGraphicsRectItem(QtGui.QGraphicsRectItem):

    def __init__(self, callback=None):
        super(MovableGraphicsRectItem, self).__init__()
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.PointingHandCursor)
        self.callback = callback

    def itemChange(self, change, value):
        if change == QtGui.QGraphicsItem.ItemPositionChange and self.callback:
            self.callback(value)

        return super(MovableGraphicsRectItem, self).itemChange(change, value)

    def activate(self):
        self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.PointingHandCursor)

    def deactivate(self):
        self.setFlags(not QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
        self.setCursor(QtCore.Qt.ArrowCursor)


class MouseInsideFilterObj(QtCore.QObject):#And this one
    def __init__(self, enterCallback, leaveCallback):
        QtCore.QObject.__init__(self)
        self.enterCallback = enterCallback
        self.leaveCallback = leaveCallback

    def eventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.Type.Enter:
            self.enterCallback(obj)

        if event.type() == QtCore.QEvent.Type.Leave:
            self.leaveCallback(obj)

        return True


class TestClass:

    def __init__(self):

        self.app = QtGui.QApplication([])

        # the scene with some rectangles
        self.scene = QtGui.QGraphicsScene()
        self.scene.addRect(30, 30, 100, 50, pen=QtGui.QPen(QtCore.Qt.darkGreen))
        self.scene.addRect(150, 0, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkYellow))
        self.scene.addRect(80, 80, 100, 20, pen=QtGui.QPen(QtCore.Qt.darkMagenta))
        self.scene.addRect(200, 10, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkRed))

        self.window = QtGui.QWidget()

        # put two graphicsviews into the window with different scaling for each
        self.layout = QtGui.QVBoxLayout(self.window)
        self.v1 = QtGui.QGraphicsView(self.scene)
        self.v1.setFixedSize(500, 100)
        self.v1.scale(0.5, 0.5)
        self.v1.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.v1.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.layout.addWidget(self.v1) 
        self.v2 = QtGui.QGraphicsView(self.scene)
        self.v2.setFixedSize(500, 500)
        self.v2.scale(5, 5)
        self.v2.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.v2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.layout.addWidget(self.v2)

        mouseFilter = MouseInsideFilterObj(self.enterGV, self.leaveGV)
        self.v1.installEventFilter(mouseFilter)

        # the tracker rectangle
        self.tracker = MovableGraphicsRectItem(lambda pos: self.v2.setSceneRect(pos.x(), pos.y(), 100, 100))
        self.tracker.setRect(0, 0, 100, 100)
        self.v2.setSceneRect(0, 0, 100, 100)
        self.tracker.setPen(QtGui.QPen(QtCore.Qt.darkCyan))
        self.scene.addItem(self.tracker)

        self.window.show()
        self.app.exec_()

    def leaveGV(self, gv):
        if gv is self.v1:
            self.tracker.deactivate()

    def enterGV(self, gv):
        if gv is self.v1:
            self.tracker.activate()





TestClass()