简体   繁体   中英

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

I have created a custom Frame for a Qgraphicswidget. I have come across two problems, First, one being that clicks are not being detected under MouseMoveEvent, that is event.button() always returns 0 even if there is a mouse click. Second, is that my setCursor() doesn't change the cursor. Here is my code under the custom frame 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())

I have also tried using eventfilter but of no use.

EDIT:

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

There are various issues on your code, but the base problems are:

  • mouse button state cannot be retrieved by event.button() in a MouseMove event, and event.button () should be used instead: the difference is clear: button() shows the buttons that generate the event (and a mouse move event is obviously not generated by any button), buttons() shows the button state when the event is generated; ()代替:区别很明显: button()显示生成事件的按钮(和鼠标移动事件显然不是由任何按钮生成的), buttons()显示事件生成时的按钮state
  • events that are not explicitly managed by an object are always propagated to its parent(s), which means that your mouse movements are also possibly processed by the parent widget, then the graphics proxy, the scene, the viewport, the view, etc, and that up to the top level window, until one of the previous objects actually returns True from event() or an event filter; in your case it results in moving the graphics item, since you enabled the ItemIsMovable flag.

I don't know why the cursor is not actually set, but frankly your code is so convoluted that I really cannot find the reason.

Since what you're actually looking for is a way to resize the widget, I suggest you another solution.
While implementing a resizing with custom painting is certainly feasible, in most cases it's well enough to use a QSizeGrip (as already suggested to you in another post), which is a widget that allows resizing top-level windows and is automatically able to understand which "corner" use for the resizing based on its position. Remember that the parent of the QSizeGrip is very important, because it uses it to understand which is its top level window, and, in this case, the "container frame", even if it's in a QGraphicsScene.

Note that QSizeGrip should not be added to a layout, and it should always be manually moved according to its corner position and the size of its parent (unless it's placed on the top left corner), and since you already need custom painting, it's better to subclass it.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM