Pyqt:自定义框架在 mouseMoveEvent 下不检测鼠标点击

Pyqt: custom frame doesn't detect mouse click under mouseMoveEvent

我已经为 Qgraphics 小部件创建了自定义框架。我遇到了两个问题,首先,一个是在 MouseMoveEvent 下没有检测到点击,即 event.button() 总是 returns 0 即使有鼠标点击。其次,我的 setCursor() 没有改变光标。这是我在自定义框架 class.

下的代码
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QRectF, QEvent, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, qRgb, QPolygon
from PyQt5.QtWidgets import *
import sys


class Frame(QFrame):

    def __init__(self, parent=None, option=[], margin=0):
        super(Frame, self).__init__()

        self.parent = parent
        self._triangle = QPolygon()
        self.options = option
        self._margin = margin
        self.start_pos = None
        # self.parent.setViewport(parent)
        self.setStyleSheet('background-color: lightblue')
        self.setMouseTracking(True)
        self.installEventFilter(self)
        self.show()

    def update_option(self, option):
        self.options = option

    def paintEvent(self, event):
        super().paintEvent(event)

        qp = QPainter(self)

        qp.setPen(Qt.white)
        qp.setBrush(Qt.gray)
        qp.drawPolygon(self._triangle)

    def _recalculate_triangle(self):
        p = QPoint(self.width() - 20, self.height() - 10)
        q = QPoint(self.width() - 10, self.height() - 20)
        r = QPoint(self.width() - 10, self.height() - 10)

        self._triangle = QPolygon([p, q, r])
        self.update()

    def resizeEvent(self, event):
        self._recalculate_triangle()
        super().resizeEvent(event)

    def mousePressEvent(self, event):

        if event.button() == Qt.LeftButton:

            if event.button() == Qt.LeftButton and self._triangle.containsPoint(
                event.pos(), Qt.OddEvenFill
            ):
                self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
                self.start_pos = event.pos()
                # print(self.start_pos)

            else:
                self.parent.viewport().unsetCursor()
                self.start_pos = None

        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):

        if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
            self.parent.viewport().setCursor(Qt.SizeFDiagCursor)

        else:
            self.parent.viewport().unsetCursor()
            self.start_pos = None

        if event.button() == Qt.LeftButton:
            if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
                self.parent.viewport().setCursor(Qt.SizeFDiagCursor)

                delta = event.pos() - self.start_pos

                self.n_resize(self.width()+delta.x(), self.height()+delta.y())
                self.start_pos = event.pos()

            elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
                self.parent.viewport().unsetCursor()
                self.start_pos = None

        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self.parent.viewport().unsetCursor()
        self.start_pos = None
        super().mouseReleaseEvent(event)

    def n_resize(self, width, height):
        self.resize(width, height)


if __name__ == '__main__':
    q = QApplication(sys.argv)
    a = Frame()
    sys.exit(q.exec())

我也试过使用eventfilter但是没有用。

编辑:

from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPen, QColor, QPainter, QBrush, QPolygon
from PyQt5.QtWidgets import *
import sys



class graphLayout(QGraphicsView):

    def __init__(self, parent=None):
        super().__init__(parent)

        self.scene = QGraphicsScene()
        self.lines = []
        self.draw_grid()
        self.set_opacity(0.3)

        widget = QGraphicsProxyWidget()

        t = stack(self)
        t.setFlag(QGraphicsItem.ItemIsMovable)
        t.resize(340, 330)

        self.scene.addItem(t)
        self.setScene(self.scene)
        self.show()

    def create_texture(self):
        image = QtGui.QImage(QtCore.QSize(30, 30), QtGui.QImage.Format_RGBA64)

        pen = QPen()
        pen.setColor(QColor(189, 190, 191))
        pen.setWidth(2)

        painter = QtGui.QPainter(image)
        painter.setPen(pen)
        painter.drawRect(image.rect())
        painter.end()

        return image

    def draw_grid(self):

        texture = self.create_texture()
        brush = QBrush()
        # brush.setColor(QColor('#999'))
        brush.setTextureImage(texture)  # Grid pattern.
        self.scene.setBackgroundBrush(brush)

        borderColor = Qt.black
        fillColor = QColor('#DDD')

    def set_visible(self, visible=True):
        for line in self.lines:
            line.setVisible(visible)

    def delete_grid(self):
        for line in self.lines:
            self.scene.removeItem(line)
        del self.lines[:]

    def set_opacity(self, opacity):
        for line in self.lines:
            line.setOpacity(opacity)

    def wheelEvent(self, event):

        if event.modifiers() == Qt.ControlModifier:

            delta = event.angleDelta().y()
            if delta > 0:
                self.on_zoom_in()

            elif delta < 0:
                self.on_zoom_out()

        super(graphLayout, self).wheelEvent(event)

    def mousePressEvent(self, event):

        if event.button() == Qt.MidButton:
            self.setCursor(Qt.OpenHandCursor)
            self.mousepos = event.localPos()

        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):


        # This helps to pan the area
        if event.buttons() == Qt.MidButton:
            delta = event.localPos() - self.mousepos
            h = self.horizontalScrollBar().value()
            v = self.verticalScrollBar().value()

            self.horizontalScrollBar().setValue(int(h - delta.x()))
            self.verticalScrollBar().setValue(int(v - delta.y()))

        self.mousepos = event.localPos()

        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):

        self.unsetCursor()
        self.mousepos = event.localPos()
        super().mouseReleaseEvent(event)

    def on_zoom_in(self):

        if self.transform().m11() < 3.375:
            self.setTransformationAnchor(self.AnchorUnderMouse)
            self.scale(1.5, 1.5)

    def on_zoom_out(self):

        if self.transform().m11() > 0.7:
            self.setTransformationAnchor(self.AnchorUnderMouse)
            self.scale(1.0 / 1.5, 1.0 / 1.5)


class stack(QGraphicsWidget):
    _margin = 0

    def __init__(self, parent=None):
        super().__init__()

        self.options = []
        self.gridlayout = parent

        graphic_layout = QGraphicsLinearLayout(Qt.Vertical, self)

        self.width, self.height = 10, 10

        self.outer_container = Frame(parent, self.options, self._margin)

        self.outer_container.setContentsMargins(0, 0, 0, 0)

        self.setParent(self.outer_container)

        layout = QVBoxLayout()
        self.headerLayout = QGridLayout()
        self.headerLayout.setContentsMargins(2, 0, 0, 0)

        self.top_bar = QFrame()
        self.top_bar.setFrameShape(QFrame.StyledPanel)
        self.top_bar.setLayout(self.headerLayout)
        self.top_bar.setContentsMargins(0, 0, 0, 0)
        self.top_bar.setMaximumHeight(30)

        # self.contentLayout = QVBoxLayout()
        self.contentLayout = QFormLayout()
        self.contentLayout.setContentsMargins(10, 10, 10, 10)
        self.contentLayout.setSpacing(5)

        layout.addWidget(self.top_bar)
        layout.addLayout(self.contentLayout)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)

        self.outer_container.setContentsMargins(0, 0, 0, 0)
        self.setContentsMargins(0, 0, 0, 0)

        self.setMaximumSize(400, 800)

        self.outer_container.setLayout(layout)

        widget = QGraphicsProxyWidget()
        widget.setWidget(self.outer_container)

        # todo: figure out a way to add top_bar widget
        graphic_layout.addItem(widget)
        graphic_layout.setSpacing(0)
        graphic_layout.setContentsMargins(0, 0, 0, 0)

        # widget move and resize note: don't touch any of these
        self.__mouseMovePos = None
        self._triangle = QPolygon()
        self.start_pos = None


    def addHeaderWidget(self, widget=None, column=0, bg_color='green'):
        self.top_bar.setStyleSheet(f'background-color:{bg_color};')
        self.headerLayout.addWidget(widget, 0, column)



class Frame(QFrame):

    def __init__(self, parent=None, option=[], margin=0):
        super(Frame, self).__init__()

        self.parent = parent
        self._triangle = QPolygon()
        self.options = option
        self._margin = margin
        self.start_pos = None
        # self.parent.setViewport(parent)
        self.setStyleSheet('background-color: lightblue')
        self.setMouseTracking(True)
        self.installEventFilter(self)
        self.show()

    def update_option(self, option):
        self.options = option

    def paintEvent(self, event):
        super().paintEvent(event)

        qp = QPainter(self)

        qp.setPen(Qt.white)
        qp.setBrush(Qt.gray)
        qp.drawPolygon(self._triangle)

    def _recalculate_triangle(self):
        p = QPoint(self.width() - 20, self.height() - 10)
        q = QPoint(self.width() - 10, self.height() - 20)
        r = QPoint(self.width() - 10, self.height() - 10)

        self._triangle = QPolygon([p, q, r])
        self.update()

    def resizeEvent(self, event):
        self._recalculate_triangle()
        super().resizeEvent(event)

    def mousePressEvent(self, event):

        if event.button() == Qt.LeftButton:

            if event.button() == Qt.LeftButton and self._triangle.containsPoint(
                event.pos(), Qt.OddEvenFill
            ):
                self.parent.viewport().setCursor(Qt.SizeFDiagCursor)
                self.start_pos = event.pos()
                # print(self.start_pos)

            else:
                self.parent.viewport().unsetCursor()
                self.start_pos = None

        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):

        if self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
            self.parent.viewport().setCursor(Qt.SizeFDiagCursor)

        else:
            self.parent.viewport().unsetCursor()
            self.start_pos = None

        if event.button() == Qt.LeftButton:
            if event.button() == QtCore.Qt.LeftButton and self.start_pos is not None:
                self.parent.viewport().setCursor(Qt.SizeFDiagCursor)

                delta = event.pos() - self.start_pos

                self.n_resize(self.width()+delta.x(), self.height()+delta.y())
                self.start_pos = event.pos()

            elif not self._triangle.containsPoint(event.pos(), Qt.OddEvenFill):
                self.parent.viewport().unsetCursor()
                self.start_pos = None

        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self.parent.viewport().unsetCursor()
        self.start_pos = None
        super().mouseReleaseEvent(event)

    def n_resize(self, width, height):
        self.resize(width, height)


if __name__ == '__main__':
    q = QApplication(sys.argv)
    a = graphLayout()
    sys.exit(q.exec())

您的代码存在各种问题,但基本问题是:

  • 在 MouseMove 事件中 event.button() 无法检索鼠标按钮状态,event.button<b>s</b>() 应该改用:区别很明显:button() 显示 生成 事件的按钮(鼠标移动事件显然 不是 任意按钮产生),buttons()显示按钮状态产生事件时;
  • 未由对象显式管理的事件总是传播到其父控件,这意味着您的鼠标移动也可能由父控件处理,然后图形代理、场景、视口、视图等,直到顶层 window,直到前面的对象之一实际上 returns True 来自 event() 或事件过滤器;在您的情况下,它会导致移动图形项目,因为您启用了 ItemIsMovable 标志。

我不知道为什么没有真正设置光标,但坦白说你的代码很复杂,我真的找不到原因。

由于您真正要找的是一种调整小部件大小的方法,我建议您使用另一种解决方案。
虽然使用自定义绘画实现调整大小当然是可行的,但在大多数情况下,使用 QSizeGrip 就足够了(正如另一个 post 中已经向您建议的那样),这是一个允许调整顶层大小的小部件windows 并且能够自动理解哪个“角”用于根据其位置调整大小。请记住 QSizeGrip 的 parent 非常重要,因为它使用它来了解哪个是它的顶层 window,并且在这种情况下,“容器框架”,即使它在 QGraphicsScene 中。

请注意,QSizeGrip 不应 添加到布局中,并且应始终根据其 位置和其父级的大小(除非它被放置在左上角),并且由于您已经需要自定义绘画,所以最好将其子类化。

from PyQt5 import QtCore, QtGui, QtWidgets

class SizeGrip(QtWidgets.QSizeGrip):
    def __init__(self, parent):
        super().__init__(parent)
        parent.installEventFilter(self)
        self.setFixedSize(30, 30)
        self.polygon = QtGui.QPolygon([
            QtCore.QPoint(10, 20), 
            QtCore.QPoint(20, 10), 
            QtCore.QPoint(20, 20), 
        ])

    def eventFilter(self, source, event):
        if event.type() == QtCore.QEvent.Resize:
            geo = self.rect()
            geo.moveBottomRight(source.rect().bottomRight())
            self.setGeometry(geo)
        return super().eventFilter(source, event)

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setPen(QtCore.Qt.white)
        qp.setBrush(QtCore.Qt.gray)
        qp.drawPolygon(self.polygon)


class Container(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sizeGrip = SizeGrip(self)
        self.startPos = None
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(6, 6, 6, 30)
        self.setStyleSheet('''
            Container {
                background: lightblue;
                border: 0px;
                border-radius: 4px;
            }
        ''')

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.startPos = event.pos()

    def mouseMoveEvent(self, event):
        if self.startPos:
            self.move(self.pos() + (event.pos() - self.startPos))

    def mouseReleaseEvent(self, event):
        self.startPos = None


class GraphicsRoundedFrame(QtWidgets.QGraphicsProxyWidget):
    def __init__(self):
        super().__init__()
        self.container = Container()
        self.setWidget(self.container)

    def addWidget(self, widget):
        self.container.layout().addWidget(widget)

    def paint(self, qp, opt, widget):
        qp.save()
        p = QtGui.QPainterPath()
        p.addRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
        qp.setClipPath(p)
        super().paint(qp, opt, widget)
        qp.restore()


class View(QtWidgets.QGraphicsView):
    def __init__(self):
        super().__init__()
        scene = QtWidgets.QGraphicsScene()
        self.setScene(scene)
        self.setRenderHints(QtGui.QPainter.Antialiasing)
        scene.setSceneRect(0, 0, 1024, 768)

        texture = QtGui.QImage(30, 30, QtGui.QImage.Format_ARGB32)
        qp = QtGui.QPainter(texture)
        qp.setBrush(QtCore.Qt.white)
        qp.setPen(QtGui.QPen(QtGui.QColor(189, 190, 191), 2))
        qp.drawRect(texture.rect())
        qp.end()
        scene.setBackgroundBrush(QtGui.QBrush(texture))

        testFrame = GraphicsRoundedFrame()
        scene.addItem(testFrame)

        testFrame.addWidget(QtWidgets.QLabel('I am a label'))
        testFrame.addWidget(QtWidgets.QPushButton('I am a button'))

import sys
app = QtWidgets.QApplication(sys.argv)
w = View()
w.show()
sys.exit(app.exec_())