Как я могу определить, когда одно окно перекрывает другое в PyQt5?

#python #pyqt #pyqt5 #positioning #qmainwindow

#python #pyqt #pyqt5 #позиционирование #qmainwindow

Вопрос:

Я использую PyQt5 для создания приложения с несколькими основными окнами. Я хочу иметь возможность разрешать пользователю сохранять и загружать размеры окон и положения окон. Это легко сделать, например, с QMainWindow.saveGeometry() помощью and QMainWindow.loadGeometry() или соответствующих .saveState() .loadState() вариантов and . Они отлично подходят для положения и размера, но если пользователь перемещает или изменяет размер одного окна так, чтобы оно перекрывало другое, я хочу также восстановить это позиционирование. Я не против написать свой собственный код для сохранения информации для каждого окна, но я не вижу никакого способа определить относительный порядок окон по Z. Я пропускаю это в документах или это невозможно?

Чтобы понять, что я имею в виду, попробуйте следующее:

 from PyQt5.QtWidgets import QApplication, QMainWindow, QPlainTextEdit
from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QCloseEvent

'''
context: Linux Mint 19.3 Tricia x86_64 
         Python 3.9
         PyQt5 5.15.1
'''


class RememberWin(QMainWindow):
    def __init__(self, win_name: str):
        super(RememberWin, self).__init__()
        self.win_name = win_name
        self.setWindowTitle(win_name)
        self.can_close = False

    def restore_window(self) -> bool:
        try:
            settings = QSettings("PyQtExamples", "RememberWinTest")
            self.restoreGeometry(settings.value(f'{self.win_name} Geometry'))
            self.restoreState(settings.value(f'{self.win_name} State'))
            return True
        except:
            return False

    def closeEvent(self, event: QCloseEvent):
        if not self.can_close:
            event.ignore()
        else:
            settings = QSettings("PyQtExamples", "RememberWinTest")
            settings.setValue(f'{self.win_name} Geometry', self.saveGeometry())
            settings.setValue(f'{self.win_name} State', self.saveState())
            QMainWindow.closeEvent(self, event)


class ControlWindow(RememberWin):
    def __init__(self, win_name: str = "ControlWindow"):
        super(ControlWindow, self).__init__(win_name=win_name)
        self.can_close = True

        self.window1 = RememberWin(win_name='WindowOne')
        self.window2 = RememberWin(win_name='WindowTwo')

        self.text = QPlainTextEdit(self)
        s = "Try making Window1 wide enough to cover Window2.n" 
            "Then close this window (auto closes others).n" 
            "Re-run the app and you'll notice that Window2n" 
            "is not on top of Window1 which means that thisn" 
            "info isn't getting saved."

        self.text.setPlainText(s)
        self.setCentralWidget(self.text)

        if not self.restore_window():
            self.setGeometry(100, 390, 512, 100)
        if not self.window1.restore_window():
            self.window1.setGeometry(100, 100, 512, 384)
        if not self.window2.restore_window():
            self.window2.setGeometry(622, 100, 512, 384)

        self.window1.show()
        self.window2.show()

    def closeEvent(self, event: QCloseEvent):
        for win in (self.window1, self.window2):
            win.can_close = True
            win.close()
        super(ControlWindow, self).closeEvent(event)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)

    window = ControlWindow(win_name='ControlWindow (You can only close this one)')
    window.show()

    sys.exit(app.exec_())
 

Ответ №1:

Самый простой способ сделать то, чего вы хотите достичь, — это отслеживать текущий сфокусированный виджет или, если быть точным, окно верхнего уровня последнего сфокусированного виджета.

Вы можете сохранить сфокусированные окна в настройках в виде списка, используя уникальные objectName для каждого окна (вы уже делаете это, поэтому вам просто нужно использовать setObjectName() ), затем восстановить окно, показывая их в правильном порядке, если имя объекта совпадает.

 class RememberWin(QMainWindow):
    def __init__(self, win_name: str):
        super(RememberWin, self).__init__()
        self.win_name = win_name
        self.setObjectName(win_name)
        self.setWindowTitle(win_name)
        self.can_close = False

    # ...


class ControlWindow(RememberWin):
    def __init__(self, win_name: str = "ControlWindow"):
        # ...
        self.settings = QSettings("PyQtExamples", "RememberWinTest")
        self.zOrder = []
        QApplication.instance().focusObjectChanged.connect(self.focusChanged)

        windowOrder = self.settings.value('windowOrder', type='QStringList')
        topLevelWindows = QApplication.topLevelWidgets()
        if windowOrder:
            for objName in windowOrder:
                for win in topLevelWindows:
                    if win.objectName() == objName:
                        win.show()
        else:
            self.window1.show()
            self.window2.show()

    def focusChanged(self, obj):
        if not obj or obj.window() == self.window():
            return
        if obj.window() in self.zOrder[:-1]:
            self.zOrder.remove(obj.window())
        self.zOrder.append(obj.window())

    def closeEvent(self, event: QCloseEvent):
        for win in (self.window1, self.window2):
            win.can_close = True
            win.close()
        self.settings.setValue('windowOrder', 
            [w.window().objectName() for w in self.zOrder])
        super(ControlWindow, self).closeEvent(event)