Как передать метод экземпляра главного окна, в другое глубоко вложенное окно

Добрый день есть приложение на PyQt5. В MainWindow реализован специфический метод addWidgetToMDI(). Функция addWidgetToMDI() работает с виджетами главного окна и, в частности, QMdiArea.

Вложенность окон большая (главное окно вызывает первое, первое вызывает второе, втрое третье и т. д.), и каждое вложенное окно должно иметь доступ к функции addWidgetToMDI() экземпляра класс MainWindow.

Подскажите есть ли простой прием получить ссылку на эту функцию? Или как из любого места получить экземпляр класса MainWindow.

Ниже представлены пример.

main.py

#!/usr/bin/env/python3
# -*- coding: utf-8 -*-

import datetime
import os
import sys

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication
from mainWindow import MainWindow


class App(QApplication):
    __version__ = 1
    __dateVer__ = datetime.date.today().strftime('%d-%m-%Y')
    confDir = os.path.abspath(os.curdir)

    # обратите внимание settings тоже передаю в другие окна в нем я сохраняю геометрию и настройки окон выбранные
    # пользователем
    settings = QtCore.QSettings(os.path.join(confDir, ' view_conf.ini'), QtCore.QSettings.IniFormat)
    settings.setIniCodec("utf-8")


def main():
    app = App(sys.argv)
    # МОЙ КАСТЫЛЬ с помощью которого получаю ссылку на экземпляр класса main_window
    app.main_window = MainWindow()
    app.main_window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

mainWindow.py

#!/usr/bin/env/python3
# -*- coding: utf-8 -*-

import datetime
from PyQt5 import QtCore, QtGui, QtWidgets
from window_1 import WindowOne


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setObjectName("MainWindow")
        self.resize(800, 600)
        self.action_window_1 = QtWidgets.QAction(self)
        self.action_window_1.setObjectName(u"action_window_1")
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName(u"gridLayout")
        self.mdiArea = MdiAreaCustom(self.centralwidget)
        self.mdiArea.setObjectName("mdiArea")
        self.gridLayout.addWidget(self.mdiArea, 0, 0, 1, 1)
        self.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)
        self.menubar.addAction(self.menu.menuAction())
        self.menu.addAction(self.action_window_1)
        self.setWindowTitle("MainWindow")
        self.menu.setTitle("Данные")
        self.action_window_1.setText('вызвать window_1')

        self.settings = QtCore.QCoreApplication.instance().settings
        self.action_window_1.triggered.connect(lambda: self.addWidgetToMDI(parent=self, form=WindowOne()))

    # !!! Это упрощеный вариант функции которые должны получить все окна
    # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    def addWidgetToMDI(self, parent: QtWidgets.QWidget, form: QtWidgets.QWidget) -> None:
        sub = MdiSubWindow(parent, flags=form.windowFlags())
        sub.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        sub.setWidget(form)
        sub = self.mdiArea.addSubWindow(sub)
        self.menubar.setDisabled(True)
        if len(self.mdiArea.subWindowList()) > 1:
            self.mdiArea.subWindowList()[-2].setDisabled(True)
        sub.closeSub.connect(self.unblockWindow)
        sub.resize(form.size().width() + 18, form.size().height() + 34)
        sub.show()
        sub.setFocus()

    def unblockWindow(self):
        # Блокирую родителя и меню,
        # пользователь должен иметь возможность работать только с одним окном и ни как иначе
        if len(self.mdiArea.subWindowList()) > 0:
            self.mdiArea.subWindowList()[-1].setDisabled(False)
            self.mdiArea.activatePreviousSubWindow()
        else:
            self.menubar.setDisabled(False)


class MdiSubWindow(QtWidgets.QMdiSubWindow):
    closeSub = QtCore.pyqtSignal()

    def __init__(self, parent=None, flags=QtCore.Qt.Widget):
        super().__init__(parent, flags)
        if parent:
            self.batya = parent
            self.setObjectName(parent.objectName())
        self.setStyleSheet('background-color: rgb(240, 240, 240)')

    def closeEvent(self, closeEvent: QtGui.QCloseEvent) -> None:
        super().closeEvent(closeEvent)
        self.closeSub.emit()
        closeEvent.accept()

    @property
    def getDirParent(self) -> object:
        # при добавлении окна в mdiarea родителем становится mdiarea,
        #  чтобы получить доступ к методам и атрибутам  класса родителя использую
        return self.batya


class MdiAreaCustom(QtWidgets.QMdiArea):
    def __init__(self, parent):
        super().__init__(parent)
        font = QtGui.QFont()
        font.setPointSize(17)
        self.textDate = QtGui.QStaticText(f"""<font style="color: rgb(77, 75, 227); font-size: 17px; font-weight: bold">
                                          Сегодня {datetime.date.today().strftime('%d.%m.%Y')}</font>""")
        self.textDate.setTextWidth(450)
        self.VerApp = QtGui.QStaticText()
        self.VerApp.setTextWidth(450)

    # Ниже идет набор функций необходимы визуала в данном примере представлена только одна
    def enterVersionApp(self, text=''):
        pass

файл window_1.py

#!/usr/bin/env/python3
# -*- coding: utf-8 -*-

from PyQt5 import QtCore, QtWidgets
from window_2 import WindowTwo


class WindowOne(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(400, 300)
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setGeometry(QtCore.QRect(130, 120, 181, 23))
        self.setWindowTitle("Form")
        self.pushButton.setText("вызвать окно 2")

        # !!! Еще раз повторюсь вложенность окон глубокая, вовремя передачи ссылки через self.parent
        # я теряю связь с родителем так как родителем становится QMdiArea
        # vvvvvvvvvv Так получаю доступ к экземпляру APP
        self.settings = QtCore.QCoreApplication.instance().settings

        # vvvvvvvvvv Так получаю доступ к методам MainWindow на мой взгляд крайне неудачная практика
        # собственно в этом и вопрос. Как получать методы экземпляра класса main_window
        self.addWidgetToMDI = QtCore.QCoreApplication.instance().main_window.addWidgetToMDI
        self.pushButton.clicked.connect(lambda: self.addWidgetToMDI(parent=self, form=WindowTwo()))

файл window_2.py

# -*- coding: utf-8 -*-

from PyQt5 import QtCore, QtWidgets


class WindowTwo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(400, 300)
        self.setWindowTitle("Form_2")

        self.settings = QtCore.QCoreApplication.instance().settings
        self.addWidgetToMDI = QtCore.QCoreApplication.instance().main_window.addWidgetToMDI

Ответы (1 шт):

Автор решения: S. Nick

Я отметил для вас строки, в которые внес изменения.

q1444033_main.py

import sys
import datetime
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *

from q1444033_window_1 import WindowOne


class MdiAreaCustom(QtWidgets.QMdiArea):
    def __init__(self, parent):
        super().__init__(parent)
        font = QtGui.QFont()
        font.setPointSize(17)
        self.textDate = QtGui.QStaticText(f"""
            <font style="color: rgb(77, 75, 227); font-size: 17px; font-weight: bold">
            Сегодня {datetime.date.today().strftime('%d.%m.%Y')}</font>
        """)
        self.textDate.setTextWidth(450)
        self.VerApp = QtGui.QStaticText()
        self.VerApp.setTextWidth(450)

    # Ниже идет набор функций необходимы визуала в данном примере представлена только одна
    def enterVersionApp(self, text=''):
        print(f'def enterVersionApp(self, text=''): {text} ???????????????') #
        pass


class MdiSubWindow(QtWidgets.QMdiSubWindow):
    closeSub = QtCore.pyqtSignal()

    def __init__(self, parent=None, flags=QtCore.Qt.Widget):
        super().__init__(parent, flags)
# ???        if parent:
# ???            self.batya = parent
# ???            self.setObjectName(parent.objectName())

# +++            
        self.parent = parent                                                 # +++
        
        self.setStyleSheet('background-color: rgb(240, 240, 240)')

    def closeEvent(self, closeEvent: QtGui.QCloseEvent) -> None:
        super().closeEvent(closeEvent)
        self.closeSub.emit()
        closeEvent.accept()

# ???  vvvvvvvvvvvvvvvvvvvvvvvvv
    @property 
    def getDirParent(self) -> object:
        # при добавлении окна в mdiarea родителем становится mdiarea,
        #  чтобы получить доступ к методам и атрибутам  класса родителя использую
        return self.batya
        

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setObjectName("MainWindow")
        self.resize(800, 600)
# +++        
        self.subs = []                                                       # +++
        
        self.action_window_1 = QtWidgets.QAction(self)
        self.action_window_1.setObjectName(u"action_window_1")
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName(u"gridLayout")
        
        self.mdiArea = MdiAreaCustom(self.centralwidget)
        self.mdiArea.setObjectName("mdiArea")
        self.gridLayout.addWidget(self.mdiArea, 0, 0, 1, 1)
        
        self.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)
        self.menubar.addAction(self.menu.menuAction())
        self.menu.addAction(self.action_window_1)
        self.setWindowTitle("MainWindow")
        self.menu.setTitle("Данные")
        self.action_window_1.setText('вызвать window_1')

#        self.settings = QtCore.QCoreApplication.instance().settings
        self.action_window_1.triggered.connect(
# +++-------------------------------------------------------------> self <----  +++
            lambda: self.addWidgetToMDI(parent=self, form=WindowOne(self)))
# ----------------------------------------------------------------->^^^^            

    def addWidgetToMDI(self, parent: QtWidgets.QWidget, form: QtWidgets.QWidget) -> None:
        #print(f'def addWidgetToMDI -parent- {parent}') 
        #print(f'def addWidgetToMDI - form - {form}')  
        
        sub = MdiSubWindow(parent, flags=form.windowFlags())
        sub.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        sub.setWidget(form)
        sub = self.mdiArea.addSubWindow(sub)
        self.menubar.setDisabled(True)
        if len(self.mdiArea.subWindowList()) > 1:
            self.mdiArea.subWindowList()[-2].setDisabled(True)
        sub.closeSub.connect(self.unblockWindow)
        sub.resize(form.size().width() + 18, form.size().height() + 34)
        sub.show()
        sub.setFocus()
# +++        
        self.subs.append(sub)                                                # +++
        
    def unblockWindow(self):
        if len(self.mdiArea.subWindowList()) > 0:
            self.mdiArea.subWindowList()[-1].setDisabled(False)
            self.mdiArea.activatePreviousSubWindow()
        else:
            self.menubar.setDisabled(False)        


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

q1444033_window_1.py

from PyQt5 import QtCore, QtWidgets

from q1444033_window_2 import WindowTwo


class WindowOne(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
# +++        
        self.parent = parent                                                 # +++
        self.resize(400, 300)
        
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setGeometry(QtCore.QRect(130, 120, 181, 23))
        self.setWindowTitle("Form")
        self.pushButton.setText("вызвать окно 2")

        # Так получаю доступ к экземпляру APP
#        self.settings = QtCore.QCoreApplication.instance().settings

        # Так получаю доступ к методам MainWindow на мой взгляд 
        # крайне неудачная практика собственно в этом и вопрос.
        # Как получать методы экземпляра класса main_window
# ???        self.addWidgetToMDI = QtCore.QCoreApplication.instance().main_window.addWidgetToMDI

# !!! +++
        self.pushButton.clicked.connect(
#-            lambda: self.addWidgetToMDI(parent=self, form=WindowTwo()))
            lambda: self.parent.addWidgetToMDI(parent=self.parent, form=WindowTwo(self.parent)))
# ----------------> ^^^^^^^^^^^ <-------------------> ^^^^^^^^^^^ <-------------> ^^^^^^^^^^^

q1444033_window_2.py

from PyQt5 import QtCore, QtWidgets


class WindowTwo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
# +++        
        self.parent = parent                                                 # +++
        
        self.resize(400, 300)
        self.setWindowTitle("Form_2")

#        self.settings = QtCore.QCoreApplication.instance().settings

# ???       self.addWidgetToMDI = QtCore.QCoreApplication.instance().main_window.addWidgetToMDI

# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv        
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setObjectName(u"pushButton")
        self.pushButton.setGeometry(QtCore.QRect(130, 120, 181, 23))
        self.pushButton.setText("вызвать окно MainWindow")      
        self.pushButton.clicked.connect(self._subs)     
        
    def _subs(self):
        subWindows = self.parent.findChildren(QtWidgets.QMdiSubWindow) 
        #print(*subWindows, sep='\n')         
    
        for sub in self.parent.subs:
            if sub in subWindows:
                sub.close()
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    
        

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

→ Ссылка