How to create a draggable and resizable frameless window

Viewed 245

I want to create a draggable and resizable frameless window in Pyside6 by rewritting the mouseEvent and resizeEvent.And try to use QSizeGrip to control the shape of window.
Drag and resize, I can implement both functions alone, but there is a problem when they are combined.
when I resize the window after dragging, the position will be wrong. I want to know what's the wrong in this code:

import sys
from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtGui import *

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()


        self.text = QLabel("Hello World",alignment=Qt.AlignCenter)
        self.layout =QVBoxLayout(self)
        self.layout.addWidget(self.text)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.gripSize = 16
        self.grips = []
        for i in range(4):
            grip = QSizeGrip(self)
            grip.resize(self.gripSize, self.gripSize)
            self.grips.append(grip)

    def resizeEvent(self, event):
        QWidget.resizeEvent(self, event)
        rect = self.rect()
        # top left grip doesn't need to be moved...
        # top right
        self.grips[1].move(rect.right() - self.gripSize, 0)
        # bottom right
        self.grips[2].move(
            rect.right() - self.gripSize, rect.bottom() - self.gripSize)
        # bottom left
        self.grips[3].move(0, rect.bottom() - self.gripSize)


    def mousePressEvent(self, event):
        self.oldPos = event.globalPos()

    def mouseMoveEvent(self, event):
        delta = QPoint(event.globalPos() - self.oldPos)
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.oldPos = event.globalPos()




if __name__ == "__main__":
    app =QApplication([])
1 Answers

By default, QSizeGrip interfaces with the OS for the actual resizing as soon as it's activated (by pressing the left mouse button on it).

The result is that, after that, all mouse move events are intercepted by the system until the button is released. Since the button release is also intercepted by the system (to know that the resizing has been completed), QSizeGrip will be able to handle again mouse events only after the button release; since the previous condition was the mouse button press, it will receive a MouseMove event, and, by defaults, those events are ignored by widgets if they don't handle it.

If a mouse event is ignored, it is propagated to its parent(s), which in this case is your MyWidget.

Unfortunately, your assumption is that you only get mouse move events only after a button press, but, due to what explained above, this is not the case: you will not receive a mouse button press (it was handled by the size grip), but only a mouse move (since it's been ignored by the size grip).

Now, there are two cases:

  1. you previously moved the window, so there is an oldPos based on the previous start mouse position, and the window will be moved using the wrong parameters;
  2. you only resized the window since startup, and the program will crash because there was no oldPos attribute;

There are various possible solutions, but the simple one is to create a default oldPos attribute having a None value, set it in the mouse press, check if self.oldPos is not None in the mouse move (and eventually move) and, most importantly, restore self.oldPos = None in the mouse release.

Note that it's usually better to move the window only using a single button (the convention is the left one, but the middle one is not uncommon)

class MyWidget(QWidget):
    oldPos = None

    # ...

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.oldPos = event.globalPos()

    def mouseMoveEvent(self, event):
        if self.oldPos is not None:
            delta = event.globalPos() - self.oldPos
            self.move(self.pos() + delta)
            self.oldPos = event.globalPos()

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

Note: QPoint + QPoint is already a QPoint, and move() accepts a QPoint by default, so there's no need to sum x and y coordinates individually.

Related