0

I know that you can have a Tkinter class access another Tkinter class (How to access variables from different classes in tkinter?) But im looking for a way to have a regular class, to call functions / modify attributes from a Tkinter class.

import tkinter as tk
import tkinter.ttk as ttk


class Main(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.geometry("1000x750+125+100")
        self.configure(bg="#272c34")
        self.resizable(False, False)
        self.frames = {}
        main_page_frame = tk.Frame(self)
        main_page_frame.pack(side="top", fill="both", expand=True)
        main_page_frame.grid_rowconfigure(0, weight=1)
        main_page_frame.grid_columnconfigure(0, weight=1)
        self.frames = {}
        self.my_label = tk.Label(self, text="old text")
        self.my_label.place(x=200, y=250)
        for F in (main_page, second_page):
            frame = F(main_page_frame, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(main_page)

    def update(self):
        self.my_label.configure(text="New text!")

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.event_generate("<<ShowFrame>>")
        frame.tkraise()

    def get_page(self, page_class):
        return self.frames[page_class]

class Music:
    def __init__(self, controller):
        self.controller = controller
    song = "Song!"
    album = "Album!"

    def change_album(self):
        self.controller.update(Main)

everyones_music = Music(controller=Main)

class main_page(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.configure(bg="#272c34")
        y = tk.Label(self, text="main_page!")
        x = tk.Button(self, text="View second:", command=lambda: self.controller.show_frame(second_page))
        x.place(x=100, y=100)
        y.place(x=250, y=100)
        new_button = tk.Button(self, text='Change the variable', command=lambda: everyones_music.change_album())
        new_button.place(x=100, y=150)
        

class second_page(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.configure(bg="#272c34")
        self.me = tk.Label(self, text=everyones_music.album)
        self.me.place(x=200, y=100)
        self.but = tk.Button(self, text="UPdate ??", command=self.update_is)
        self.but.place(x=100, y=100)

    def update_is(self):
        print(everyones_music.album)
        self.me.configure(text=everyones_music.album)


main_app = Main()
main_app.mainloop()

Essentially, I want the instance of class Music to be able to modify, or call functions in a Tkinter class. Specifically, function "change_album" should have like:

    def change_album(self):
        self.album = "new album!
        second_page.update_is()

Do I want an instance for each page? Does the class Music need to have a way to access the controller and modify properties from there? It is important that there is only one instance of Music, as required for the program.

  • In normal practice, it is not recommended to call `second_page.update_is()` inside `Music.change_album()`. I would suggest to call `everyones_music.change_album()` inside `second_page.update_is()` instead. – acw1668 Jul 12 '22 at 04:55
  • Problem with that is that I need to be able to modify the Music class, and have those changes reflected on the Tkinter second_page class. Do you know if this would be any different if instead of a Tkinter class, it was a frame based on the class 'Main'? it would be able to interact with controller and whatnot right? – NedNoodleHead Jul 12 '22 at 18:53
  • You can easily make a class inheriting from a tkinter class and define a function with your own name or a name already used by tkinter. Then, instead of using the tkinter class name, you can just use your class name. This is how to write a class changing an imported class. – OmegaO333 Jul 13 '22 at 01:53

1 Answers1

0

In the code you posted in the second version of your question, you are setting the controller incorrectly here:

everyones_music = Music(controller=Main)

The above sets the controller to the class Main rather than to an instance of Main. The controller needs to be the instance - in this case, main_app.

You also make a similar problem of passing the class when calling update here:

self.controller.update(Main)

The problem is, you have a chicken and the egg problem in the code as written. Music needs an instance of Main, but Main requires that the instance of Music already exists. More correctly, second_page is instantiated by Main and the instance of second_page requires that everyones_music has been defined.

You need to make sure you don't have a circular dependency. You can do that by either removing the controller argument from Music.__init__, or you can make second_page not require that the controller is defined in its __init__.

Let's do the first solution. To do that, you can rewrite the Music class to not require a controller:

class Music:
    def __init__(self, controller=None):
        self.controller = controller
    song = "Song!"
    album = "Album!"

    def change_album(self):
        if self.controller is not None:
            self.controller.update()

everyones_music = Music()

Then, inside Main.__init__ you can assign self to the controller of everyones_music:

class Main(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        everyones_music.controller = self
        ...

With that, your code should work.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685