【问题标题】:Make QGraphicsItem only selectable in one graphicsview使 QGraphicsItem 只能在一个图形视图中选择
【发布时间】:2014-07-07 17:45:12
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我写了一个关于我认为你应该做什么的答案,但我承认我对这个问题有点困惑。你有一个场景和两个视图,对吗?更好的是两个场景和两个视图(详细和概述),或者您如何准确地覆盖这两个视图?在前台的一个视图会消耗所有的鼠标事件,那你为什么要担心红色矩形(bounding-box)的移动呢?
  • 我在同一个场景中有两个视图,因为我更改了场景中的内容,我需要在概览视图和详细视图中更新它。因此,我去了一个有两个视图的场景
  • 但概览显示的图像与详细视图(在您的示例中)完全不同。从场景中或在场景中的单独位置缩小的俯视图也是如此。我想无论是一个场景还是两个场景都没有任何区别 - 答案应该适用于两者。但是我不想扩展代码示例。
  • 我只是从谷歌图片中复制并粘贴了一张地图。那不是我展示的。我应该使用屏幕截图。我会在几秒钟内提供这个。
  • 更新了图片。它现在显示了我的程序中显示的内容。

标签: python qt pyside qgraphicsview qgraphicsscene


【解决方案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_()

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

【讨论】:

  • 感谢您的分析。这很好地展示了如何使QGraphicsItem 可拖动,但它准确地引入了我在问题中陈述的问题。因为tracker 占据了v2 中的完整空间,所以无论何时单击v2,都会拖动tracker。但是,我需要以某种方式实现只有当我单击v1 时才会发生拖动。 v2 中的点击和操作有不同的用途(例如绘制矩形)。
  • 谢谢。使用你的代码和一些额外的东西,它现在可以工作了。
  • @P.R.从未检查过点击 v2。有兴趣了解您作为额外位添加的内容吗?
  • 我在上面发布了我的完整解决方案。基本上,我在上方图形视图的enterleave 事件中激活和停用trackerQtGui.QGraphicsItem.ItemIsMovable 属性。即tracker只能被追踪,只有在鼠标在上方QGraphicsview时才会调用callback函数
  • @P.R.非常好的主意。这应该最终解决了整个问题。
【解决方案2】:

我真的很喜欢这个想法,并试图将它概括为创建一个小部件,您可以将“主视图”传递给它,它会创建一个概览,您可以使用它来平移和放大。不幸的是,我还没有让它工作目前还没有时间研究它,但我想我会分享迄今为止的进展。

这是小部件代码:

"""
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()

这里是测试脚本代码:

import sys
from PyQt4 import QtGui, QtCore
import overviewWidget as ov

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    # 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))

    # the main view
    mainView = QtGui.QGraphicsView(scene)
    mainView.resize(600, 400)
    mainView.update()
    mainView.show()

    # the overview
    overview = ov.Overview(mainView)
    overview.update()
    overview.show()

    sys.exit(app.exec_())

【讨论】:

    【解决方案3】:

    QGraphicsItems 默认情况下禁用了一些能力以最大限度地提高性能。通过启用这些能力,您可以使它们移动,并让它们知道自己的位置。理想情况下,一个人会使用信号/插槽机制来通知其他人更改,但同样出于性能原因QGraphicsItems 不是从QObject 继承的。但是发送事件或手动调用回调始终是可能的。

    你必须:

    • 启用QGraphicsItem 的标志QGraphicsItem.ItemIsMovableQGraphicsItem.ItemSendsScenePositionChanges
    • 提供方法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),您可以拖动矩形并将变化的位置打印到控制台。

    更多的cmets:

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

    扩展 Trilarion 的答案,我能够通过在概览 QgraphcisView 上安装 Eventfilter 来解决问题。在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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-19
      • 2014-11-12
      • 1970-01-01
      • 2020-11-10
      • 1970-01-01
      • 1970-01-01
      • 2018-02-05
      相关资源
      最近更新 更多