在更改QGraphicsView比例后,使用ItemIgnoresTransformations移动QGraphicsProxyWidget

时间:2017-02-11 23:21:11

标签: qt5 pyqt5

我有一个包含多个自定义QGraphicsItems的QGraphicsScene。每个项目都包含一个QGraphicsProxyWidget,它本身包含业务逻辑所需的任何小部件。代理有一个Qt :: Window标志,因此它有一个标题栏来移动它。这一切都运行良好,除非在缩放视图时移动代理窗口小部件。

用户可以在谷歌地图中移动场景,即缩小然后稍微放大一点。这是通过调用QGraphicsView :: scale完成的。无论缩放值如何,项都应始终可见,因此它们设置了QGraphicsItem :: ItemIgnoresTransformations标志。

在缩放视图时移动proxyWidget时会发生的情况是,在第一次移动事件中,窗口小部件会在正确拖动之前跳转到某个位置。

我在Qt5.7.1中遇到过这个问题,并且可以使用PyQt5重现它,因为它更容易重现和破解,请参阅下面的代码片段。

重现的步骤:

  1. 移动小部件,注意没什么异常
  2. 使用鼠标滚轮放大或缩小。绝对比例越高,对问题的影响越大。
  3. 点击小部件,注意它在第一次移动鼠标时是如何跳跃的。
  4. 段:

    import sys
    import PyQt5
    from PyQt5.QtCore import Qt
    from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
    from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsProxyWidget, QGraphicsWidget, QGraphicsObject
    
    global view
    global scaleLabel
    
    def scaleScene(event):
        delta = 1.0015**event.angleDelta().y()
        view.scale(delta, delta)
        scaleLabel.setPlainText("scale: %.2f"%view.transform().m11())
        view.update()
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        # create main widget
        w = QWidget()
        w.resize(800, 600)
        layout = QVBoxLayout()
        w.setLayout(layout)
        w.setWindowTitle('Example')
        w.show()
        # rescale view on mouse wheel, notice how when view.transform().m11() is not 1, 
        # dragging the subwindow is not smooth on the first mouse move event
        w.wheelEvent = scaleScene
    
        # create scene and view
        scene = QGraphicsScene()
        scaleLabel = scene.addText("scale: 1")
        view = QGraphicsView(scene)
        layout.addWidget(view)
        view.show();
    
        # create item in which the proxy lives
        item = QGraphicsWidget()
        scene.addItem(item)
        item.setFlag(PyQt5.QtWidgets.QGraphicsItem.ItemIgnoresTransformations)
        item.setAcceptHoverEvents(True)
    
        # create proxy with window and dummy content
        proxy = QGraphicsProxyWidget(item, Qt.Window)
        button = QPushButton('dummy')
        proxy.setWidget(button)
    
        # start app
        sys.exit(app.exec_())
    

    跳跃距离为:

    • 与视图的缩放比例以及鼠标与场景原点的距离
    • 成比例
    • 从场景位置(0,0)朝向鼠标位置(我认为)
    • 可能是由代理窗口小部件未报告鼠标按下/正确移动引起的。在qgraphicsproxywidget.cpp(sample source)中查看QGraphicsProxyWidgetPrivate :: mapToReceiver之后,我暗示了这个诊断,它似乎没有考虑场景缩放。

    我正在寻找

    • 确认这是Qt的一个问题,我没有错误配置代理。
    • 解释如何将代理指定的鼠标位置修复到其子窗口小部件(安装eventFilter之后)
    • 任何其他解决方法

    由于

1 个答案:

答案 0 :(得分:0)

大约2年后,我再次回到了这个问题,终于找到了解决方案。或者说是一种解决方法,但至少是一个简单的解决方法。事实证明,我可以很轻松地避免一开始就遇到本地/场景/忽略的转换问题。

我没有直接将QGraphicsProxyWidget附加到QGraphicsWidget,而是将QWidget显式设置为代理目标,而是直接从QGraphicsScene获取代理,让它在包装器上设置窗口标志,并在代理上设置ItemIgnoresTransformations标志。然后(这是解决方法),我在代理上安装了一个事件过滤器,拦截GraphicsSceneMouseMove事件,在该事件中我将代理位置强制为currentPos + mouseDelta(均在场景坐标中)。

这是上面的代码示例,并用该解决方案进行了修补:

import sys
import PyQt5
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *

global view
global scaleLabel

def scaleScene(event):
    delta = 1.0015**event.angleDelta().y()
    view.scale(delta, delta)
    scaleLabel.setPlainText("scale: %.2f"%view.transform().m11())
    view.update()

class ItemFilter(PyQt5.QtWidgets.QGraphicsItem):
    def __init__(self, target):
        super(ItemFilter, self).__init__()
        self.target = target

    def boundingRect(self):
        return self.target.boundingRect()
    def paint(self, *args, **kwargs):
        pass

    def sceneEventFilter(self, watched, event):
        if watched != self.target:
            return False

        if event.type() == PyQt5.QtCore.QEvent.GraphicsSceneMouseMove:
            self.target.setPos(self.target.pos()+event.scenePos()-event.lastScenePos())
            event.setAccepted(True)
            return True

        return super(ItemFilter, self).sceneEventFilter(watched, event)

if __name__ == '__main__':
    app = QApplication(sys.argv)

    # create main widget
    w = QWidget()
    w.resize(800, 600)
    layout = QVBoxLayout()
    w.setLayout(layout)
    w.setWindowTitle('Example')
    w.show()
    # rescale view on mouse wheel, notice how when view.transform().m11() is not 1,
    # dragging the subwindow is not smooth on the first mouse move event
    w.wheelEvent = scaleScene

    # create scene and view
    scene = QGraphicsScene()
    scaleLabel = scene.addText("scale: 1")
    view = QGraphicsView(scene)
    layout.addWidget(view)
    view.show();

    button = QPushButton('dummy')
    proxy = scene.addWidget(button, Qt.Window)
    proxy.setFlag(PyQt5.QtWidgets.QGraphicsItem.ItemIgnoresTransformations)

    itemFilter = ItemFilter(proxy)
    scene.addItem(itemFilter)
    proxy.installSceneEventFilter(itemFilter)

    # start app
    sys.exit(app.exec_())

希望这可以帮助那些陷入与我同样死胡同的人:)