PyQt5 draggable frameless window

Question:

I found an example to set borders on a frameless window, however it’s not draggable. How can I make a frameless window draggable? Especially if I can see an example it’ll be awesome. Here is my example code(normally the code is longer, that’s why there are much libraries just don’t mind them);

from PyQt5.QtWidgets import (QMessageBox,QApplication, QWidget, QToolTip, QPushButton,
                             QDesktopWidget, QMainWindow, QAction, qApp, QToolBar, QVBoxLayout,
                             QComboBox,QLabel,QLineEdit,QGridLayout,QMenuBar,QMenu,QStatusBar,
                             QTextEdit,QDialog,QFrame,QProgressBar
                             )
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtGui import QIcon,QFont,QPixmap,QPalette
from PyQt5.QtCore import QCoreApplication, Qt,QBasicTimer

import sys

class cssden(QMainWindow):
    def __init__(self):
        super().__init__()


        self.mwidget = QMainWindow(self)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)


        #size
        self.setFixedSize(320, 450)
        self.center


        #label
        self.lbl = QLabel(self)
        self.lbl.setText("test")
        self.lbl.setStyleSheet("background-color: rgb(0,0,0);"
                               "border: 1px solid red;"
                               "color: rgb(255,255,255);"
                               "font: bold italic 20pt 'Times New Roman';")
        self.lbl.setGeometry(5,5,60,40)

        self.show()

    #center
    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

app = QApplication(sys.argv)
app.setStyleSheet("QMainWindow{background-color: darkgray;border: 1px solid black}")

ex = cssden()
sys.exit(app.exec_())
Asked By: GLHF

||

Answers:

You need to handle the mouse events yourself.

  • We will need to add an event on mousePressEvent, which will keep the place where we last clicked on the window
  • Then, we will add a mouseMoveEvent, which will calculate the distance between the last clicked point and the current mouse location. We will move the window according to this distance.

This is the fixed code:

import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel


class cssden(QMainWindow):
    def __init__(self):
        super().__init__()

        # <MainWindow Properties>
        self.setFixedSize(320, 450)
        self.setStyleSheet("QMainWindow{background-color: darkgray;border: 1px solid black}")
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.center()
        # </MainWindow Properties>

        # <Label Properties>
        self.lbl = QLabel(self)
        self.lbl.setText("test")
        self.lbl.setStyleSheet("QLabel{background-color: rgb(0,0,0); border: 1px solid red; color: rgb(255,255,255); font: bold italic 20pt 'Times New Roman';}")
        self.lbl.setGeometry(5, 5, 60, 40)
        # </Label Properties>

        self.oldPos = self.pos()
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    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(sys.argv)
    ex = cssden()
    sys.exit(app.exec_())
Answered By: Elad Joseph

here is dragable and resizable frameless window

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


class movable_label(QLabel):
    def __init__(self, parent):
        super().__init__(parent)

        self.parent = parent

        self.setStyleSheet("background-color: #ccc")
        self.setMinimumHeight(30)

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            if self.parent.press_control == 0:
                self.pos = e.pos()
                self.main_pos = self.parent.pos()
        super().mousePressEvent(e)
    def mouseMoveEvent(self, e):
        if self.parent.cursor().shape() == Qt.ArrowCursor:
            self.last_pos = e.pos() - self.pos
            self.main_pos += self.last_pos
            self.parent.move(self.main_pos)
        super(movable_label, self).mouseMoveEvent(e)


class main(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.central = QWidget()

        self.vbox = QVBoxLayout(self.central)
        self.vbox.addWidget(movable_label(self))
        self.vbox.addWidget(QPushButton("Click"))
        self.vbox.setAlignment(Qt.AlignTop)
        self.vbox.setSpacing(0)
        self.vbox.setContentsMargins(0,0,0,0)
        
        self.press_control = 0        
        
        self.setCentralWidget(self.central)
        self.resize(800,500)
        self.show()          

    def eventFilter(self, obj, e):
        #hovermoveevent
        if e.type() == 129:
            if self.press_control == 0:
                self.pos_control(e)#cursor position control for cursor shape setup

        #mousepressevent
        if e.type() == 2:
            self.press_control = 1
            self.origin = self.mapToGlobal(e.pos())
            self.ori_geo = self.geometry()

        #mousereleaseevent
        if e.type() == 3:

            self.press_control = 0
            self.pos_control(e)
        
        #mosuemoveevent
        if e.type() == 5:
            if self.cursor().shape() != Qt.ArrowCursor:
                self.resizing(self.origin, e, self.ori_geo, self.value)

        return True

    def pos_control(self, e):
        rect = self.rect()
        top_left = rect.topLeft()
        top_right = rect.topRight()
        bottom_left = rect.bottomLeft()
        bottom_right = rect.bottomRight()
        pos = e.pos()

        #top catch
        if pos in QRect(QPoint(top_left.x()+5,top_left.y()), QPoint(top_right.x()-5,top_right.y()+5)):
            self.setCursor(Qt.SizeVerCursor)
            self.value = 1

        #bottom catch
        elif pos in QRect(QPoint(bottom_left.x()+5,bottom_left.y()), QPoint(bottom_right.x()-5,bottom_right.y()-5)):
            self.setCursor(Qt.SizeVerCursor)
            self.value = 2
        
        #right catch
        elif pos in QRect(QPoint(top_right.x()-5,top_right.y()+5), QPoint(bottom_right.x(),bottom_right.y()-5)):
            self.setCursor(Qt.SizeHorCursor)
            self.value = 3

        #left catch
        elif pos in QRect(QPoint(top_left.x()+5,top_left.y()+5), QPoint(bottom_left.x(),bottom_left.y()-5)):
            self.setCursor(Qt.SizeHorCursor)
            self.value = 4

        #top_right catch
        elif pos in QRect(QPoint(top_right.x(),top_right.y()), QPoint(top_right.x()-5,top_right.y()+5)):
            self.setCursor(Qt.SizeBDiagCursor)
            self.value = 5

        #botom_left catch
        elif pos in QRect(QPoint(bottom_left.x(),bottom_left.y()), QPoint(bottom_left.x()+5,bottom_left.y()-5)):
            self.setCursor(Qt.SizeBDiagCursor)
            self.value = 6

        #top_left catch
        elif pos in QRect(QPoint(top_left.x(),top_left.y()), QPoint(top_left.x()+5,top_left.y()+5)):
            self.setCursor(Qt.SizeFDiagCursor)
            self.value = 7

        #bottom_right catch
        elif pos in QRect(QPoint(bottom_right.x(),bottom_right.y()), QPoint(bottom_right.x()-5,bottom_right.y()-5)):
            self.setCursor(Qt.SizeFDiagCursor)
            self.value = 8
        
        #default
        else:
            self.setCursor(Qt.ArrowCursor)       

    def resizing(self, ori, e, geo, value):    
        #top_resize
        if self.value == 1:
            last = self.mapToGlobal(e.pos())-ori
            first = geo.height()
            first -= last.y()
            Y = geo.y()
            Y += last.y()

            if first > self.minimumHeight():
                self.setGeometry(geo.x(), Y, geo.width(), first)                    
        
        #bottom_resize
        if self.value == 2:
            last = self.mapToGlobal(e.pos())-ori
            first = geo.height()
            first += last.y()
            self.resize(geo.width(), first)

        #right_resize
        if self.value == 3:
            last = self.mapToGlobal(e.pos())-ori
            first = geo.width()
            first += last.x()
            self.resize(first, geo.height())

        #left_resize
        if self.value == 4:
            last = self.mapToGlobal(e.pos())-ori
            first = geo.width()
            first -= last.x()
            X = geo.x()
            X += last.x()

            if first > self.minimumWidth():
                self.setGeometry(X, geo.y(), first, geo.height())

        #top_right_resize
        if self.value == 5:
            last = self.mapToGlobal(e.pos())-ori
            first_width = geo.width()
            first_height = geo.height()
            first_Y = geo.y()
            first_width += last.x()
            first_height -= last.y()
            first_Y += last.y()
                
            if first_height > self.minimumHeight():
                self.setGeometry(geo.x(), first_Y, first_width, first_height)

        #bottom_right_resize
        if self.value == 6:
            last = self.mapToGlobal(e.pos())-ori
            first_width = geo.width()
            first_height = geo.height()
            first_X = geo.x()
            first_width -= last.x()
            first_height += last.y()
            first_X += last.x()
                
            if first_width > self.minimumWidth():
                self.setGeometry(first_X, geo.y(), first_width, first_height)

        #top_left_resize
        if self.value == 7:
            last = self.mapToGlobal(e.pos())-ori
            first_width = geo.width()
            first_height = geo.height()
            first_X = geo.x()
            first_Y = geo.y()
            first_width -= last.x()
            first_height -= last.y()
            first_X += last.x()
            first_Y += last.y()
                
            if first_height > self.minimumHeight() and first_width > self.minimumWidth():
                self.setGeometry(first_X, first_Y, first_width, first_height)

        #bottom_right_resize
        if self.value == 8:
            last = self.mapToGlobal(e.pos())-ori
            first_width = geo.width()
            first_height = geo.height()
            first_width += last.x()
            first_height += last.y()                    
            
            self.setGeometry(geo.x(), geo.y(), first_width, first_height)           



app = QApplication([])
window = main()
window.installEventFilter(window)
app.exec()
Answered By: SimoN SavioR

Adding to Elad Joseph’s answer, the following events need to be updated for PyQt6:

def mousePressEvent(self, event):
    self.oldPos = event.globalPosition().toPoint()

def mouseMoveEvent(self, event):
    delta = QPoint (event.globalPosition().toPoint() - self.oldPos)
    self.move(self.x() + delta.x(), self.y() + delta.y())
    self.oldPos = event.globalPosition().toPoint()
Answered By: fastlan

I released a pyqt frameless window repo on GitHub, which is implemented by pywin32 on Windows, xcffib on Linux and pyobjc on macOS.

Here is the repo link: https://github.com/zhiyiYo/PyQt-Frameless-Window
demo

Answered By: zhiyi
class Window(QWidget):  
def __init__(self):
    super().__init__() 
    self.center()
    self.oldPos = = self.pos()
    self.isMoveApp = False 
def center(self):
    qr = self.frameGeometry()
    cp = QDesktopWidget().availableGeometry().center()
    qr.moveCenter(cp)
    self.move(qr.topLeft())
    pos = qr.topLeft()
    self.topLabelPos = [pos.x(), pos.y(), pos.x()+720, pos.y()+60]

def mousePressEvent(self, event):
    self.isMoveApp = False
    self.oldPos = event.globalPos()
    x, y = self.oldPos.x(), self.oldPos.y()
    if x > self.topLabelPos[0] and x < self.topLabelPos[2]:
        if y > self.topLabelPos[1] and y < self.topLabelPos[3]:
            self.isMoveApp = True

def mouseMoveEvent(self, event):
    if self.isMoveApp:
        delta = QPoint(event.globalPos() - self.oldPos)
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.oldPos = event.globalPos()
        x, y = self.topLabelPos[0] + 
            delta.x(), self.topLabelPos[1] + delta.y()
        self.topLabelPos = [x, y, x+720, y+60]

Header label x = 0 y = 0 width = 720 height = 60
#click header label to move app

Answered By: unknowCoder.py

You can use the startSystemResize, startSystemMove functions of QWindow since Qt 5.15, which make things a lot more easier.

Here’s the example of how to use it:

def mousePressEvent(self, e):
    if e.button() == Qt.LeftButton:
        if self._resizing:
            self._resize()
        else:
            if self._pressToMove:
                self._move()
        return super().mousePressEvent(e)

def _move(self):
    window = self.window().windowHandle()
    window.startSystemMove()

def _resize(self):
    window = self.window().windowHandle()
    window.startSystemResize(Qt.LeftEdge) // or the other ones

I use both functions to my frameless window below and it works perfectly.

You can check it out to my repo if you want: https://github.com/yjg30737/pyqt-frameless-window

Answered By: yjg30737