You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制PyQt5子窗口移动范围至父窗口几何区域?

子窗口移动限制异常问题及解决

我希望将子对话框窗口的移动限制在父窗口的几何范围内,当子窗口移出边界时,程序将其定位到父窗口的边界上。

当用鼠标快速拖动子窗口使其超出边界时,代码可以正常工作,但缓慢拖动时却不行。不过一旦子窗口处于边界外,移动父窗口时,子窗口会被定位到正确位置。

原始问题代码

from PyQt5 import QtWidgets, QtCore
import sys

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

        self.setWindowTitle("Mainwindow")
        self.setFixedSize(560, 720)
        self.resolution = QtWidgets.QDesktopWidget().screenGeometry()
        self.move(int((self.resolution.width() - self.width()) / 2),
                  int((self.resolution.height() - self.height()) / 2))
        self.button = QtWidgets.QPushButton(self)
        self.button.setGeometry(QtCore.QRect(180, 660, 200, 30))
        self.button.setText("Insert window")
        self.button.clicked.connect(self.show_new_window)

        self.w = None

    def moveEvent(self, event):
        if self.w:
           self.w.move(self.frameGeometry().topLeft() + self.windowsgap)

    def show_new_window(self):
        if not self.w:
            self.w = Ui_Progreso(self)

        self.windowsgap = QtCore.QPoint(int((self.width()-self.w.width())/2),
                                        int((self.height()-self.w.height())/2))
        self.w.move(self.frameGeometry().topLeft() +  self.windowsgap)
        
        self.w.exec()

class Ui_Progreso(QtWidgets.QDialog):
    def __init__(self, parent):
        super().__init__(parent)

        self.setFixedSize(320, 160)
        self.setWindowTitle("Window movement restriction test")
        self.label = QtWidgets.QLabel(self)
        self.label.setGeometry(QtCore.QRect(20, 20, 280, 25))
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setText("Move window with mouse outside mainwindow bounds")
        self.buttonBox = QtWidgets.QDialogButtonBox(self)
        self.buttonBox.setGeometry(QtCore.QRect(-90, 110, 341, 32))
        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Yes |
                                          QtWidgets.QDialogButtonBox.No)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def moveEvent(self, event):
        if not window.geometry().contains(window.w.geometry()):
            wicoords = list(window.geometry().getCoords())
            wwcoords = list(window.w.geometry().getCoords())
            if wicoords[0] > wwcoords[0]:
                rx = wicoords[0]
            elif wicoords[2] < wwcoords[2]:
                rx = wicoords[2] - window.w.width()
            else:
                rx = wwcoords[0]
            if wicoords[1] > wwcoords[1]:
                ry = wicoords[1]
            elif wicoords[3] < wwcoords[3]:
                ry = wicoords[3] - window.w.height()
            else:
                ry = wwcoords[1] 

            r = QtCore.QRect(rx, ry, window.w.width(), window.w.height())
            window.w.setGeometry(r)

            return super().moveEvent(event)
 
        window.windowsgap = window.w.frameGeometry().topLeft() - \
                            window.frameGeometry().topLeft()

    def closeEvent(self, event):
        pass

    def accept(self):
        pass

    def reject(self):
        self.close()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()

解决方案及最终代码

改用QMdiArea子窗口方案解决了该问题,通过事件过滤器实现子窗口的移动限制,确保无论拖动速度快慢,子窗口都无法移出父窗口边界。最终代码如下:

from PyQt5 import QtCore, QtWidgets
import sys

class Window2(QtWidgets.QWidget):
    def __init__(self):
        super(Window2, self).__init__()
        self.setObjectName("subWindow2")
        self.setWindowTitle("Window movement restriction test")
        self.setFixedSize(310, 120)
        self.layout = QtWidgets.QVBoxLayout(self)
        self.label = QtWidgets.QLabel(self)
        self.label.setGeometry(QtCore.QRect(20, 20, 285, 25))
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setText("Now window can not be moved with mouse" + 
                            "\noutside of mainwindow bounds")
        self.layout.addWidget(self.label)
        self.buttonBox = QtWidgets.QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Yes |
                                          QtWidgets.QDialogButtonBox.No)
        self.layout.addWidget(self.buttonBox, alignment=QtCore.Qt.AlignCenter)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def accept(self):
        print("Yes........")
        
    def reject(self):
        self.parent().close()

class Window1(QtWidgets.QWidget):
    def __init__(self):
        super(Window1, self).__init__()
        self.setObjectName("subWindow1")
        self.setStyleSheet('background-color: #efefef')
        self.layout = QtWidgets.QHBoxLayout(self)
        self.layout.setContentsMargins(0, 620, 0, 0)
        self.pushButton = QtWidgets.QPushButton()
        self.pushButton.setText("Insert window")
        self.pushButton.setFixedSize(200, 30)
        self.layout.addWidget(self.pushButton)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setObjectName("mainwindow")
        self.setWindowTitle("Mainwindow")
        self.setFixedSize(560, 720)
        self.mdiArea = QtWidgets.QMdiArea(self)
        self.mdiArea.setObjectName("mdiArea")
        self.mdiArea.setFixedSize(self.size())
        self.resolution = QtWidgets.QDesktopWidget().screenGeometry()
        self.move(int((self.resolution.width() - self.width()) / 2),
                  int((self.resolution.height() - self.height()) / 2))

        # Insert sub1
        self.sub1 = QtWidgets.QMdiSubWindow()
        self.sub1.setWidget(Window1())
        self.sub1.setFixedSize(self.size())
        self.sub1.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.mdiArea.addSubWindow(self.sub1)
        self.sub1.pushbutton = self.sub1.findChildren(QtWidgets.QPushButton)[0]
        self.sub1.pushbutton.clicked.connect(self.showNewWindow)
        self.sub1.show()  

    def closeEvent(self, event):
        if self.findChildren(QtWidgets.QMdiSubWindow, "sub2"):
            event.ignore()
        else:
            event.accept()

    def showNewWindow(self):
        self.sub2 = QtWidgets.QMdiSubWindow()
        self.sub2.setWidget(Window2())
        self.sub2.setObjectName("sub2")
        self.mdiArea.addSubWindow(self.sub2)
        self.sub2.move(int((self.width()-self.sub2.width())/2),
                       int((self.height()-self.sub2.height())/2))
        self.sub2.installEventFilter(self)
        self.sub1.setEnabled(False)
        self.sub2.show()  

    def eventFilter(self, obj, event):
        if (event.type() == event.Move and
            obj.objectName() == "sub2" and
            self.sub2Restrict(obj, self.mdiArea.viewport().geometry())):
            return True
        if (event.type() == event.Close and
            obj.objectName() == "sub2" ):
            self.mdiArea.removeSubWindow(self.sub2)
            self.sub1.setEnabled(True)
            return True
        return super().eventFilter(obj, event)

    # sub2 movement restrictions
    def sub2Restrict(self, window, viewGeo):
        winGeo = window.geometry()
        if not viewGeo.contains(winGeo):
            if winGeo.right() > viewGeo.right():
                winGeo.moveRight(viewGeo.right())
            if winGeo.x() < 0:
                winGeo.moveLeft(0)

            if winGeo.bottom() > viewGeo.bottom():
                winGeo.moveBottom(viewGeo.bottom())
            if winGeo.y() < 0:
                winGeo.moveTop(0)
            if winGeo != window.geometry():
                window.setGeometry(winGeo)
                return True
        return False            

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

内容的提问来源于stack exchange,提问作者Manuel Antón

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 03:22:03