A-K-
A-K-

Reputation: 73

Why does changing a variable from a lambda not work?

I have been working on Tkinter and am finding trouble passing values between different frames, so I followed this tutorial here, using the "shared data" solution provided by Bryan Oakley and adding it to my own code. Except I cannot set the value in the "shared data" dictionary as a command on a button.

A few comments in the code below outline the problem. If I just try to change the variable during the init of my choice page, it changes normally. But putting it in a lambda means that the dictionary variable won't change at all. And trying to use a def for the button command has its own complications.

import tkinter as tk
import tkinter.ttk as ttk

# from tkinter import messagebox

TITLE_FONT = ("Segoe UI Light", 22)
SUBTITLE_FONT = ("Segoe UI Light", 12)

window_size = [300, 200]

resistors = []
choice = "default"


class RegApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.iconbitmap(self, default="test.ico")
        tk.Tk.wm_title(self, "Test")

        self.shared_data = {
            "choice": tk.StringVar(),
        }

        container = tk.Frame(self, width=window_size[0], height=window_size[1])
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        for F in panels:
            frame = F(container, self)

            self.frames[F] = frame

            frame.grid(row=0, column=0, sticky="NSEW")

        self.show_frame(WelcomePage)

    def show_frame(self, container):
        frame = self.frames[container]
        frame.tkraise()


class WelcomePage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        title_label = ttk.Label(self, text="Welcome", font=TITLE_FONT)
        subtitle_label = ttk.Label(self, text="Let's run some numbers.", font=SUBTITLE_FONT)
        start_button = ttk.Button(self, text="Begin", width=24, command=lambda: controller.show_frame(ChoicePage))
        title_label.pack(pady=(40, 5))
        subtitle_label.pack(pady=(0, 10))
        start_button.pack()


class ChoicePage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        self.controller.shared_data["choice"].set("test2")  # Here, the variable is set fine

        title_label = ttk.Label(self, text="Is your resistor network \nin series or parallel?", font=SUBTITLE_FONT,
                                justify=tk.CENTER)
        series_button = ttk.Button(self, text="Series", width=24,
                                   command=lambda: [self.controller.shared_data["choice"].set("series"), controller.show_frame(ValuePage)])
        # But when I use it in a lambda, the variable doesn't even seem to set at all. It switches to the next page and has the value ""
        parallel_button = ttk.Button(self, text="Parallel", width=24,
                                     command=lambda: controller.show_frame(ValuePage))

        title_label.pack()
        series_button.pack()
        parallel_button.pack()

        # TODO Make the user select between 'series' and 'parallel'


class ValuePage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        title_label = ttk.Label(self, text=self.controller.shared_data["choice"].get(), font=SUBTITLE_FONT,
                                justify=tk.CENTER)

        title_label.pack()


panels = [WelcomePage, ChoicePage, ValuePage]

app = RegApp()
app.resizable(False, False)
app.geometry('{}x{}'.format(window_size[0], window_size[1]))
app.mainloop()

Upvotes: 0

Views: 92

Answers (2)

David  W. Beck
David W. Beck

Reputation: 192

well passing data between frames isn't too hard. there are 2 ways I like to do it.

Method 1:

setup a frame to be something like this....

class ThirdName(tk.Frame):   

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        
        self.controller = controller

now just say something like:

self.data = "this is some data"

now when you're in another frame you can call it like this:

print(ThirdName.data)
>>> "this is some data"

Second way is just to send it somewhere like this:

value_1 = 'Richard'
bobby_def(value_1, 42, 'this is some text')

...

def bobby_def(name, number, text)
    print(text)

    or...
    return(name, number, text)

Bobby will get the data :)

ok.... second point... moving between frames can be done with something like this:

self.button_to_go_to_home_page = tk.Button(self, text='Third\nPage', font=Roboto_Normal_Font,
                             command=lambda: self.controller.show_frame(ThirdName),
                             height=2, width=12, bd = 0, activeforeground=active_fg, activebackground=active_bg, highlightbackground=border_colour,
                             foreground=bg_text_colour, background=background_deselected)
self.button_to_go_to_home_page.place(x=20, y=280)

**set up a frame with stuff like this:

class SecondName(tk.Frame):   

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        
        self.controller = controller

Yes, the self.controller method is one I use too and it's nice to have lots of your data together in one frame.

Upvotes: 1

Why are you using 'controller' instead of 'self.controller'? It's kind of confusing that you assign 'self.controller' at the start of the constructor and then you use 'controller' instead. Maybe that variable shadowing is causing your problem.

Upvotes: 0

Related Questions