如何限制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
相关产品推荐
相关产品推荐

