PyQt5 拖动框架时移动 window

PyQt5 move window when dragging frame

希望你们在这些困难时期都过得很好。

我在 window 的顶部添加了一个框架,目的是让它看起来像大多数应用程序顶部的栏(请原谅我,我不知道我的行话),您可以在其中单击它并通过移动鼠标来移动屏幕。到目前为止,我只得到了顶部的框架,并研究了如何实现这个结果,我看过 this one 这样的例子,但它只适用于当你点击 [=] 上的任何地方时21=],而我正在寻找一种仅在您单击顶部框架时才移动屏幕的方法。

我的代码如下

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

感谢您的帮助,希望您和您的朋友及家人在此期间保持安全<3

你的问题的 来源 是你可能正在编辑由 pyuic 生成的代码(或者你试图模仿它的行为),这是永远 不应该做的事情。这些文件只能作为模块导入,不得无故修改。要了解如何正确使用通过 Designer 创建的 ui 文件,请阅读 using Designer.

上的更多信息

采用这种方法的结果是 mousePressEventmouseMoveEvent 永远不会被调用

原因是,在您的代码中,那些方法属于 Ui_MainWindow class 实例 ui 创建接近尾声),这只不过是一个基本的 Python object subclass:它几乎什么都不做,除了 build UI onsetupUi() 中的 QMainWindow 实例; 那个 QMainWindow 实例是应该接收那些事件的实例,那些方法应该在那里实现。
或者,至少,理论上。那是因为您要捕获的 mousePressEvent 和 mouseMoveEvent 实际上应该是从 infoBar 对象接收的,而不是主要的 window.

一个常见的解决方案是子class 将接收这些事件并在那里实现这些方法的小部件,但是由于您可能使用的是在 Designer 中创建的 ui,它不像似乎(稍后会详细介绍)。

最直接的方法,就是在watched widget上安装一个事件过滤器,这样我们就可以过滤那些事件并做出反应如果我们愿意的话给他们。

在下面的示例中,我假设您使用 pyuic 重新创建了文件,并将其命名为 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)

关于上面提到的设计器中子class的使用:可以在设计器中创建自定义子class和"use"。您必须在您的代码中创建一个 subclass,然后将其最接近的祖先(在您的情况下为 QFrame)添加到您的 ui,这将用作一种占位符,然后 推广 它。做一些关于 "promoted widgets" 的研究以了解更多信息。

一个重要的建议:使用固定大小和位置不是一个好的选择,layout managers 应该 always 是首选。这样做的原因是,您在计算机上看到的内容几乎总是与其他人的显示方式截然不同(最常见的原因之一是:不同的屏幕尺寸、默认设置、DPI、字体等),这可能会导致您的 GUI 变得不可用,因为小部件可能变得不可见、重叠或不可读。在非常非常少见的情况下可以使用固定的几何图形(但是当程序员意识到他们需要这些图形时,可能是因为 code/ui 设计不当),而这肯定不是你的情况。
例如,如果我尝试将 window 的大小调整为比原始大小更小,右上角的按钮将无法访问。此外,右侧靠近 "exit" 的按钮未正确对齐。如果您使用布局管理器(例如 infoBar 小部件上的水平布局),它会自动解决所有这些问题。