4

What I can't do

I'm not able to send data back from a child to a parent window.

What I have

I've got a complex GUI with several windows sendíng data to child windows. Each window represents a unique Python-script in the same directory. There was no need to explicitely specify parents and childs, as the communication was always unidirectional (parent to child). However, now I need to send back data from childs to parents and can't figure out how to do this as each window (i.e. each class) has its own file.

Example

Here's a minimal example showing the base of what I want to accomplish. What it does: win01 opens win02 and win02 triggers func in win01.

# testfile01.py

import sys
from PyQt5.QtWidgets import *
import testfile02 as t02

class win01(QWidget):

    def __init__(self, parent=None):
        super(win01, self).__init__(parent)

        self.win02 = t02.win02()
        self.button = QPushButton("open win02", self)
        self.button.move(100, 100)
        self.button.clicked.connect(self.show_t02)

    def initUI(self):
        self.center

    def show_t02(self):
        self.win02.show()

    def func(self):
        print("yes!")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = win01()
    ex.show()
    sys.exit(app.exec_())

##########################################################################

# testfile02.py

from PyQt5.QtWidgets import *
import testfile01 as t01

class win02(QWidget):

    def __init__(self, parent=None):
        super(win02, self).__init__(parent)

        self.win01 = t01.win01()
        self.button = QPushButton()
        self.button.clicked.connect(self.win01.func)

    def initUI(self):
        self.center()

What I tried

Importing testfile01 in the second window always leads to the error: RecursionError: maximum recursion depth exceeded.

Then, I tried the following approaches, but they didn't work either:

  • Not importing testfile01 in win02 and adjusting parent=None to different other objects
  • Importing testfile01 within the __init__ call of win02
  • Creating a signal in win02 to trigger func in win01

The Question

Is there a solution how to properly trigger func in win01 from win02?

alex_555
  • 1,092
  • 1
  • 14
  • 27

2 Answers2

4

Why are you getting RecursionError: maximum recursion depth exceeded?

You are getting it because you have a circular import that generates an infinite loop, in testfile01 you are importing the file testfile02, and in testfile02 you are importing testfile01, .... So that is a shows of a bad design.


Qt offers the mechanism of signals for objects to communicate information to other objects, and this has the advantage of non-dependence between classes that is a great long-term benefit (as for example that avoids circular import), so for that reason I think it is the most appropriate.

For this I will create the clicked signal in the class win02 that will be triggered by the clicked signal of the button, and make that clicked signal call the func:

testfile01.py

import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
import testfile02 as t02


class win01(QWidget):
    def __init__(self, parent=None):
        super(win01, self).__init__(parent)

        self.win02 = t02.win02()
        self.win02.clicked.connect(self.func)
        self.button = QPushButton("open win02", self)
        self.button.move(100, 100)
        self.button.clicked.connect(self.show_t02)

    def show_t02(self):
        self.win02.show()

    def func(self):
        print("yes!")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = win01()
    ex.show()
    sys.exit(app.exec_())

testfile02.py

from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout


class win02(QWidget):
    clicked = pyqtSignal()

    def __init__(self, parent=None):
        super(win02, self).__init__(parent)
        self.button = QPushButton("call to func")
        self.button.clicked.connect(self.clicked)
        lay = QVBoxLayout(self)
        lay.addWidget(self.button)

I recommend you read:

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • OK ,thank you, this makes sense. Now almost everything works. The second window opens. Clicking the button, however, is not linked to func from win01. I suppose something like `self.clicked.connect(self.parent().func)` might be missing in win02 to complete the connection (which is not working, unfortunately). – alex_555 Apr 24 '19 at 14:32
  • @alex_555 What you point out is a bad design, the idea is that the classes do not intertwine, but the objects. And why is it a bad design? because if the classes are intertwined doing maintenance is more complicated because if you modify the win01 class you will probably have to modify the win02 class, which obviously is not the ideas, so Qt offers the signals that it makes transparent. – eyllanesc Apr 24 '19 at 14:34
  • @alex_555 Have you read of the Single responsibility principle? that indicates that each class must have a defined task, in the case of win02 its responsibility is to notify that the button was pressed, and the responsibility of win01 is to connect the notification to the method that will be invoked – eyllanesc Apr 24 '19 at 14:38
  • @alex_555 I recommend you first design your classes before implementing them, so you will avoid these problems. In Qt it is recommended to notify internal changes to the external elements through the signals since this way we fulfill the principle of encapsulation and we only make visible the necessary information – eyllanesc Apr 24 '19 at 14:42
  • Thank you! This was very useful. Now I got it to work. I completely understand what you meant with bad design. Thanks a lot! This really helped to improve my code. – alex_555 Apr 25 '19 at 06:41
  • @eyllanesc It's possible send the signal without a function connection? I need send it in the end of a function. – Jalkhov Sep 11 '20 at 14:33
1

Both the Widgets are independent and have no link in between. Set win01 parent of win02.

In class win01
Replace :

self.win01 = t02.win02()
#and
self.win02.show()

with:

self.win01 = t02.win02(self)
#and
self.win01.show()

and in class win02
Replace:

self.win02 = t01.win01()
#and
self.button.clicked.connect(self.win01.func)

with:

self.win02 = self.parent()
#and
self.button.clicked.connect(self.win02.func)
Omi
  • 111
  • 8
  • Almost. Now I get: `AttributeError: 'win02' object has no attribute 'win01'` – alex_555 Apr 24 '19 at 14:17
  • It's because you have mistyped win01 and win02 in both classes, let me update my answer, make sure to vote up if it helps. – Omi Apr 24 '19 at 14:20
  • You're right! I updated the typos now (sorry, you might have to update your answer again). Interestingly, win01 opens, but the button doesn't open win02. – alex_555 Apr 24 '19 at 14:22