Running into an issue while working with tkinter, here's a simple example of what I'm doing
import tkinter as tk
from tkinter import ttk
from typing import Any, Callable, Mapping, NamedTuple, Type
values = {
'eat': ('something', 'nice'),
'dirt': ('something', 'else'),
'kid': ('something'), '': tuple()
}
class MyTuple(NamedTuple):
widget: Type[tk.Widget]
config: Mapping[str, Any] = {}
postcommand: Callable[[ttk.Combobox], None] = None
class Reg():
def __init__(self, var):
self.var = var
self.widgets = (
MyTuple(ttk.Label),
MyTuple(ttk.Combobox, {'values': tuple(values.keys()), 'textvariable': self.var}),
MyTuple(ttk.Combobox, postcommand=self.cb),
MyTuple(ttk.Combobox, postcommand=self.other_cb),
)
def display(self, frame: tk.Tk):
for w in self.widgets:
widget = w.widget(frame, **w.config)
widget.pack()
if not w.postcommand is None:
widget['postcommand'] = lambda widget=widget : w.postcommand(widget)
def cb(self, combobox: ttk.Combobox):
combobox['values'] = values[self.var.get()]
def other_cb(self, combobox: ttk.Combobox):
combobox['values'] = tuple(values.keys())
class GUI(tk.Tk):
def __init__(self):
super().__init__()
v = tk.StringVar()
reg = Reg(v)
reg.display(self)
_ = GUI()
_.mainloop()
When running this, it seems that w gets overwritten and the same postcommand is set for all of the comboboxes. I tried changing display to look like this:
from copy import deepcopy
...
def display(self, frame: tk.Tk):
for w in self.widgets:
widget = w.widget(frame, **w.config)
widget.pack()
if not w.postcommand is None:
command = deepcopy(w.postcommand)
widget['postcommand'] = lambda widget=widget : command(widget)
And I get the error TypeError: cannot pickle '_tkinter.tkapp' object
.
Is there an alternative to deepcopy that I can use? All I could find was a way to overwrite the pickle function, but I can't figure out how to apply that here, since I can't even figure out what tkinter object is part of the deepcopy.
Alternatively, is there a way to fix the scoping issue with w?
(Using python 3.8.10)
The lambda that calls w.postcommand
is not capturing the correct value of w
. What happens if you change the lambda inside your loop to this?
widget['postcommand'] = lambda widget=widget, w=w: w.postcommand(widget)
Further reading: What do lambda function closures capture?