python - 在pyqt中使用sceneEventFilter捕获悬停事件

标签 python pyqt pyqt5 pyqtgraph eventfilter

我在图形场景中有用户可调整的注释。注释的大小/旋转通过拖动注释周围矩形的角来处理。我使用自定义矩形(而不是boundingRect),因此它遵循父注释的旋转。控制角由两个椭圆标记,其父级是矩形,因此矩形/椭圆/注释的转换是无缝的。

我想检测光标何时位于某个角上、它是哪个角以及确切的坐标。对于此任务,我似乎应该使用 sceneEventFilter 过滤带有父矩形的悬停事件。

我尝试了无数种实现 sceneEventFilter 的方法,但没有成功。所有事件都直接进入hoverEnterEvent函数。我只找到了一些执行类似操作的示例代码,但我只是卡住了。顺便说一句,过去 3 个月我完全自学了 Python 和 QT,所以请耐心等待。我确信我错过了一些非常基本的东西。该代码是一个带有两个省略号的简化 GUI。我们希望捕获 sceneEventFilter 中的事件,但总是转到hoverEnterEvent。

from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem
import sys

class myHandle(QtGui.QGraphicsEllipseItem):
    def __init__(self, parent = None):
        super(myHandle, self).__init__(parent)

    def addTheHandle(self, h_parent = 'null', kind = 'null'):
        handle_w = 40
        if kind == 'scaling handle':
            handle_x = h_parent.boundingRect().topRight().x() - handle_w/2
            handle_y = h_parent.boundingRect().topRight().y() - handle_w/2
        if kind == 'rotation handle':
            handle_x = h_parent.boundingRect().topLeft().x() - handle_w/2
            handle_y = h_parent.boundingRect().topLeft().y() - handle_w/2
        the_handle = QtGui.QGraphicsEllipseItem(QtCore.QRectF(handle_x, handle_y, handle_w, handle_w))
        the_handle.setPen(QtGui.QPen(QtGui.QColor(255, 100, 0), 3))
        the_handle.setParentItem(h_parent)
        the_handle.setAcceptHoverEvents(True)
        the_handle.kind = kind

        return the_handle

class myRect(QtGui.QGraphicsRectItem):
    def __init__(self, parent = None):
        super(myRect, self).__init__(parent)

    def rectThing(self, boundingrectangle):
        self.setAcceptHoverEvents(True)
        self.setRect(boundingrectangle)
        mh = myHandle()
        rotation_handle = mh.addTheHandle(h_parent = self, kind = 'rotation handle')
        scaling_handle  = mh.addTheHandle(h_parent = self, kind = 'scaling handle')        
        self.installSceneEventFilter(rotation_handle)
        self.installSceneEventFilter(scaling_handle)

        return self, rotation_handle, scaling_handle

    def sceneEventFilter(self, event):    
        print('scene ev filter')
        return False

    def hoverEnterEvent(self, event):
        print('hover enter event')

class Basic(QtGui.QMainWindow):
    def __init__(self):
        super(Basic, self).__init__()
        self.initUI()

    def eventFilter(self, source, event):
        return QtGui.QMainWindow.eventFilter(self, source, event)

    def exit_the_program(self):
        pg.exit()

    def initUI(self):
        self.resize(300, 300)

        self.centralwidget = QtGui.QWidget()
        self.setCentralWidget(self.centralwidget)        
        self.h_layout = QtGui.QHBoxLayout(self.centralwidget)

        self.exit_program = QtGui.QPushButton('Exit')
        self.exit_program.clicked.connect(self.exit_the_program)        
        self.h_layout.addWidget(self.exit_program)

        self.this_scene = QGraphicsScene()
        self.this_view = QGraphicsView(self.this_scene)
        self.this_view.setMouseTracking(True)
        self.this_view.viewport().installEventFilter(self)

        self.h_layout.addWidget(self.this_view)        
        self.circle = self.this_scene.addEllipse(QtCore.QRectF(40, 40, 65, 65), QtGui.QPen(QtCore.Qt.black))

        mr = myRect()
        the_rect, rotation_handle, scaling_handle = mr.rectThing(self.circle.boundingRect())
        the_rect.setPen(QtGui.QPen(QtCore.Qt.black))
        the_rect.setParentItem(self.circle)

        self.this_scene.addItem(the_rect)
        self.this_scene.addItem(rotation_handle)
        self.this_scene.addItem(scaling_handle)        

def main():
    app = QtGui.QApplication([])
    main = Basic()
    main.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

最佳答案

主要问题是您正在矩形上安装目标项的事件过滤器:矩形的事件过滤器永远不会接收任何内容。此外,sceneEventFilter 接受两个参数(监视的项目和事件),但您只使用了一个。

您应该做的是将矩形的事件过滤器安装在目标项上:

    rotation_handle.installSceneEventFilter(self)
    scaling_handle.installSceneEventFilter(self)

也就是说,如果您想使用这些椭圆项来缩放或旋转源圆,那么您的方法一开始就有点错误。

from math import sqrt
# ...

class myRect(QtGui.QGraphicsRectItem):
    def __init__(self, parent):
        super(myRect, self).__init__(parent)
        self.setRect(parent.boundingRect())
        # rotation is usually based on the center of an object
        self.parentItem().setTransformOriginPoint(self.parentItem().rect().center())

        # a rectangle that has a center at (0, 0)
        handleRect = QtCore.QRectF(-20, -20, 40, 40)

        self.rotation_handle = QtGui.QGraphicsEllipseItem(handleRect, self)
        self.scaling_handle = QtGui.QGraphicsEllipseItem(handleRect, self)
        # position the handles by centering them at the right corners
        self.rotation_handle.setPos(self.rect().topLeft())
        self.scaling_handle.setPos(self.rect().topRight())
        for source in (self.rotation_handle, self.scaling_handle):
            # install the *self* event filter on the handles
            source.installSceneEventFilter(self)
            source.setPen(QtGui.QPen(QtGui.QColor(255, 100, 0), 3))

    def sceneEventFilter(self, source, event):
        if event.type() == QtCore.QEvent.GraphicsSceneMouseMove:
            # map the handle event position to the ellipse parent item; we could
            # also map to "self", but using the parent is more consistent
            localPos = self.parentItem().mapFromItem(source, event.pos())
            if source == self.rotation_handle:
                # create a temporary line to get the rotation angle
                line = QtCore.QLineF(self.boundingRect().center(), localPos)
                # add the current rotation to the angle between the center and the
                # top left corner, then subtract the new line angle
                self.parentItem().setRotation(135 + self.parentItem().rotation() - line.angle())
                # note that I'm assuming that the ellipse is a circle, so the top
                # left angle will always be at 135°; if it's not a circle, the
                # rect width and height won't match and the angle will be
                # different, so you'll need to compute that

                # parentRect = self.parentItem().rect()
                # oldLine = QtCore.QLineF(parentRect.center(), parentRect.topLeft())
                # self.parentItem().setRotation(
                #     oldLine.angle() + self.parentItem().rotation() - line.angle())

            elif source == self.scaling_handle:
                # still assuming a perfect circle, so the rectangle is a square;
                # the line from the center to the top right corner is used to
                # compute the square side size, which is the double of a
                # right-triangle cathetus where the hypotenuse is the line
                # between the center and any of its corners;
                # if the ellipse is not a perfect circle, you'll have to
                # compute both of the catheti
                hyp = QtCore.QLineF(self.boundingRect().center(), localPos)
                size = sqrt(2) * hyp.length()
                rect = QtCore.QRectF(0, 0, size, size)
                rect.moveCenter(self.rect().center())
                self.parentItem().setRect(rect)
                self.setRect(rect)
                # update the positions of both handles
                self.rotation_handle.setPos(self.rect().topLeft())
                self.scaling_handle.setPos(self.rect().topRight())
                return True
        elif event.type() == QtCore.QEvent.GraphicsSceneMousePress:
            # return True to the press event (which is almost as setting it as
            # accepted, so that it won't be processed further more by the scene,
            # allowing the sceneEventFilter to capture the following mouseMove
            # events that the watched graphics items will receive
            return True
        return super(myRect, self).sceneEventFilter(source, event)

class Basic(QtGui.QMainWindow):
    # ...
    def initUI(self):
        # ...
        self.circle = self.this_scene.addEllipse(QtCore.QRectF(40, 40, 65, 65), QtGui.QPen(QtCore.Qt.black))

        mr = myRect(self.circle)
        self.this_scene.addItem(mr)

关于python - 在pyqt中使用sceneEventFilter捕获悬停事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59243514/

相关文章:

python刷新/重新加载

python2.7 将二进制有符号 double 转换为 ASCII 的难度

python - 将 QcomboBox 的默认值设置为空

python - 为单个标签赋予颜色会消耗过多的处理能力

Python 正则表达式 : find all words/phrases within a querystring containing OR and AND

python - 确定信号中是否包含特定频率

python - PyQt:类对象删除,调用析构函数但内存未释放

python - PySide 中的样式表不起作用

python - 让 PyQt5 标签在发送 http 请求时动态更新

python - 如何使用 QSortFilterProxyModel 对浮点值进行排序?