-1

I am using Tkinter in Python 3, and have two windows, each represented by a class. I need to click a button in WindowB (my second class) that runs a method from WindowA (my first class). This should be fine, however the method I am calling calls another method from WindowA (my first class). This results in the error: AttributeError: 'WindowB' object has no attribute 'functionB'. How would I fix this so I can run functionA in WindowB (2nd class) with no errors? Code:

from tkinter import *
from functools import partial

class WindowA(Frame):
    def __init__(self, master= None): # initialise Menu
        Frame.__init__(self, master) # initialise Frame
        self.master = master
        self.createWindow() # adds buttons

    def createWindow(self):
        self.pack(fill = BOTH, expand = 1)
        # lambda prevents self.changeWindow from executing before button pressed
        button1 = Button(self, text = 'Change to Window B', command = lambda: self.changeWindow(WindowB))
        button1.pack(fill = X, padx = 10, pady = 10)

        button2 = Button(self, text = 'Run Function A', command = lambda: self.functionA())
        button2.pack(fill = X, padx = 10, pady = 10)


    def changeWindow(self, object):
        root.withdraw()
        currentFrame = object(root)

    def functionA(self):
        print("I am function A.")
        self.functionB()

    def functionB(self):
        print("I am function B, called from function A.")

class WindowB(Toplevel):
    def __init__(self, master = None):
        Toplevel.__init__(self, master)
        self.master = master
        self.Window()

    def Window(self):
        button3 = Button(self, text = 'Run Function A', command = lambda:WindowA.functionA(self))
        button3.grid(row = 1, column = 0, padx = 10)

root = Tk()
app = WindowA(root)
root.mainloop()
StudyAccount
  • 91
  • 1
  • 11
  • Does this answer your question? [how-would-i-access-variables-from-one-class-to-another](https://stackoverflow.com/questions/19993795) – stovfl Jun 14 '20 at 11:08

4 Answers4

1

The most common solution is to pass the instance of WindowA when creating WindowB, so that the latter can access methods in the former.

First, make sure that WindowB can accept the name of the other window:

class WindowB(Toplevel):
    def __init__(self, master = None, windowA=None):
        Toplevel.__init__(self, master)
        self.master = master
        self.windowA=windowA
        self.Window()

Next, pass self as the windowA parameter when creating the instance of WindowB. I renamed object to self since you're passing a class rather than an instance of the class:

class WindowA(Frame):
    ...
    def changeWindow(self, cls):
        root.withdraw()
        currentFrame = cls(root, windowA=self)

You can then use self.windowA to call any method in that object:

class WindowB(Toplevel):
    ...
    def Window(self):
        ...
        button3 = Button(self, text = 'Run Function A', command = lambda:self.windowA.functionA())
        ...
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • I think I understand this but just to check, when I call the changeWindow method are you suggesting when creating WindowB the argument for self is WindowB and the argument for the object is self? (sorry if its a dumb q) – StudyAccount Jun 14 '20 at 14:14
  • @StudyAccount: my apologies. I did a very poor job of creating the examples. Your code is very confusing with the way it names things and the fact that it doesn't follow PEP8 naming conventions. I think I got it right now. The point is, for B to be able to call something in A, B needs to have a reference to A. – Bryan Oakley Jun 14 '20 at 14:48
0

As an alternative answer, you could use the classmethod decorator:

    def functionA(self):
        print("I am function A.")
        WindowA.functionB()

    @classmethod
    def functionB(cls):
        print("I am function B, called from function A.")

Note I am calling WindowA.functionB()

Peaceful James
  • 1,807
  • 1
  • 7
  • 16
  • Thanks! I'll do some research on decorators first I'll try to use this on future projects. – StudyAccount Jun 14 '20 at 14:28
  • That's a good idea. The `classmethod` decorator basically just lets you call class methods like they are normal module functions. It is a good fit for your example because the function doesn't actually use `self`. However, I should point out that the first argument to a classmethod is usually not `self`. It is usually `cls`, which is the class object. The documentation is here https://docs.python.org/3/library/functions.html#classmethod – Peaceful James Jun 14 '20 at 14:32
0

You're almost there, you only need to change just one thing in tge Window() method of class WindowB. You need to change it to be like so:

button3 = Button(self, text = 'Run Function A', command = lambda:WindowA().functionA())
# instead of 
# button3 = Button(self, text = 'Run Function A', command = lambda:WindowA.functionA(self))
Anwarvic
  • 12,156
  • 4
  • 49
  • 69
  • Thank you! I tried these methods in my actual program and it didn't work. Could this be as my 'WindowA' I am calling the method from is a 'TopLevel' and not a Frame? – StudyAccount Jun 14 '20 at 12:13
  • It works just fine with me... try copying the code from my answer to your app – Anwarvic Jun 14 '20 at 12:17
  • When I implement this in my project, the window of the class from which the method is called from (in this case WindowA) opens, leading to the error: 'NoneType object has no attribute 'title'. – StudyAccount Jun 14 '20 at 12:25
-1

you have to create instance from WindowA class to use WindowA function

class WindowB(Toplevel):
    def __init__(self, master=None):
        Toplevel.__init__(self, master)
        self.master = master
        self.Window()
        self.WindowA = WindowA()  # added here

    def Window(self):
        button3 = Button(self, text='Run Function A', command=lambda: self.WindowA.functionA())
        button3.grid(row=1, column=0, padx=10)
Sibyl
  • 134
  • 1
  • 8
  • I just implemented this in my actual code, and it doesn't seem to work, sorry. It opens two blank windows (the window from the class the method was called and the window for the current class). – StudyAccount Jun 14 '20 at 12:46
  • @StudyAccount i tested it with your code but it was working, maybe you changed something later? – Sibyl Jun 14 '20 at 12:47
  • You're correct :'). In my program I needed to write the equivalent of self.WindowA = WindowA(root). This opens both WindowA and WindowB fully but then I can write self.WindowA.withdraw() to only show WindowB and run my method. Thanks! – StudyAccount Jun 14 '20 at 13:02
  • There already is an instance of `WindowA`. This solution creates a second instance. – Bryan Oakley Jun 14 '20 at 19:58