简体   繁体   中英

PyQt5 move window when dragging frame

hope you're all doing well during these difficult times.

I've added a frame at the top of my window, and the aim is to make it act like the bar you find at the top of most applications (Forgive me, I don't know my jargon just yet), where you're able to click on it and move the screen by moving your mouse around. So far I've only got the frame at the top and have done research into how I could achieve this result, I've looked at examples such this one , but it is only for when you click anywhere on the window, while I am looking for a method which only moves the screen when you are clicking the top frame.

My code is as follows

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.setWindowModality(QtCore.Qt.NonModal)
        MainWindow.resize(909, 544)
        MainWindow.setAnimated(True)
        MainWindow.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
        self.centralwidget.setSizePolicy(sizePolicy)
        self.centralwidget.setObjectName("centralwidget")
        self.backgroundFrame = QtWidgets.QFrame(self.centralwidget)
        self.backgroundFrame.setEnabled(True)
        self.backgroundFrame.setGeometry(QtCore.QRect(-1, -1, 941, 551))
        self.backgroundFrame.setStyleSheet("QLabel {\n"
"font-family: \"Microsoft Sans Serif\", sans-serif;\n"
"color: white;\n"
"}\n"
"\n"
"#backgroundFrame\n"
"{\n"
"background-color: #060321;\n"
"}\n"
"\n"
"#infoBar\n"
"{\n"
"background-color: #0F0334;\n"
"}")
        self.backgroundFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.backgroundFrame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.backgroundFrame.setObjectName("backgroundFrame")
        self.infoBar = QtWidgets.QFrame(self.backgroundFrame)
        self.infoBar.setGeometry(QtCore.QRect(-10, -10, 921, 71))
        self.infoBar.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.infoBar.setFrameShadow(QtWidgets.QFrame.Raised)
        self.infoBar.setObjectName("infoBar")
        self.exitButton = QtWidgets.QPushButton(self.infoBar)
        self.exitButton.setGeometry(QtCore.QRect(874, 26, 31, 31))
        self.exitButton.setText("")
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("Resources/Exit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.exitButton.setIcon(icon)
        self.exitButton.setIconSize(QtCore.QSize(32, 32))
        self.exitButton.setObjectName("exitButton")
        self.pushButton = QtWidgets.QPushButton(self.infoBar)
        self.pushButton.setGeometry(QtCore.QRect(827, 30, 31, 31))
        self.pushButton.setText("")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("Resources/Minimize.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton.setIcon(icon1)
        self.pushButton.setIconSize(QtCore.QSize(32, 32))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.infoBar)
        self.pushButton_2.setGeometry(QtCore.QRect(26, 26, 34, 31))
        self.pushButton_2.setText("")
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("Resources/Settings.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_2.setIcon(icon2)
        self.pushButton_2.setIconSize(QtCore.QSize(32, 32))
        self.pushButton_2.setObjectName("pushButton_2")
        self.label = QtWidgets.QLabel(self.infoBar)
        self.label.setGeometry(QtCore.QRect(390, 23, 145, 40))
        self.label.setText("")
        self.label.setPixmap(QtGui.QPixmap("Resources/Titan Window.png"))
        self.label.setObjectName("label")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Titan"))

    def mousePressEvent(self, event):
        self.offset = event.pos()

    def mouseMoveEvent(self, event):
        x=event.globalX()
        y=event.globalY()
        x_w = self.offset.x()
        y_w = self.offset.y()
        self.move(x-x_w, y-y_w)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Thanks for the help and hope you and your friends and family stay safe during this time <3

The source of your problem is that you're probably editing the code generated by pyuic (or you're trying to mimick its behavior), which is something that should never be done. Those files must only be imported as modules, and should not be modified for no reason at all. To know how to correctly use ui files created with Designer, read more on using Designer .

The result of following this kind of approach, in the way you did it, is that the mousePressEvent and mouseMoveEvent will never be called.

The reason for this is that, in your code, those methods belong to the Ui_MainWindow class instance (the ui created near the end), which is nothing else than a basic Python object subclass: it does almost nothing, except build the UI on the QMainWindow instance in its setupUi() ; that QMainWindow instance is the one that should receive those events instead, and those methods should be implemented there.
Or, at least, theoretically . That's because the mousePressEvent and mouseMoveEvent you'll want to catch should actually be received from the infoBar object, not the main window.

A common solution is to subclass the widget that will receive those events and implement those methods there, but since you're probably using an ui created in Designer that's not as immediate as it seems (more about this later).

The most direct approach, is to install an event filter on the watched widget, so that we can filter those events and react to them if we want to.

In the following example I'm assuming that you've recreated the file with pyuic , naming it ui_mainwindow.py :

from PyQt5 import QtCore, QtGui, QtWidgets
from ui_mainwindow import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.offset = None
        # install the event filter on the infoBar widget
        self.infoBar.installEventFilter(self)

    def eventFilter(self, source, event):
        if source == self.infoBar:
            if event.type() == QtCore.QEvent.MouseButtonPress:
                self.offset = event.pos()
            elif event.type() == QtCore.QEvent.MouseMove and self.offset is not None:
                # no need for complex computations: just use the offset to compute
                # "delta" position, and add that to the current one
                self.move(self.pos() - self.offset + event.pos())
                # return True to tell Qt that the event has been accepted and
                # should not be processed any further
                return True
            elif event.type() == QtCore.QEvent.MouseButtonRelease:
                self.offset = None
        # let Qt process any other event
        return super().eventFilter(source, event)

About the usage of subclasses in Designer mentioned above: it is possible to create custom subclasses and "use" them in Designer. You have to create a subclass in your code, and then add to your ui its most close ancestor (in your case, a QFrame), which will work as a sort of placeholder, then promote it. Do some research about "promoted widgets" to know more.

An important suggestion: using fixed sizes and positions is not a good choice, and layout managers should always be preferred instead. The reason for this is that what you see on your computer will almost always be shown very differently in other's (amongst the most common reasons: different screen sizes, default settings, DPI, fonts, etc), and that might result in your GUI becoming unusable since widgets can become invisible, overlapped or unreadable. There are very, very rare situations for which fixed geometries could be used (but when programmers realizes that they need those, it's possibly due to bad code/ui design), and that's certainly not your case.
For instance, if I try to resize your window to a smaller size than the original, the top right buttons become inaccessible. Also, the button on the right near the "exit" one, is not corretly aligned. If you use a layout manager (like a horizontal layout on the infoBar widget), it would solve all those problems automatically.

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