I am currently having a problem with Tkinter.Variable initialization and naming.
Setup:
I have a window, with a frame (window_frame) in it and menubar at the top (tk.Menu). Each command on the menu opens a new app, which clears window_frame and re-fills it with all new widgets. Some of the widgets have a tk.Variable attached to them (ex. tk.Variable, tk.StringVar, etc).
Problem:
I am using customized widget classes (for global configuration), and originally, I had the variables initialized inside the widget with a name based on the widget name, for example:
<-SNIP->
# In the widgets module:
class CustomWidget(tk.Entry):
def __init__(self, master=None, cnf={}, **kw):
value = kw.pop('value', None)
tk.Entry.__init__(self, master, cnf, **kw)
self.variable = tk.Variable(self, name=self._name + '_var', value=value)
def destroy_widgets(master):
for child in master.winfo_children():
destroy_children(child)
child.destroy()
# In each app module:
def main(window_frame):
# Destroy the widgets in the window_frame:
destroy_widgets(window_frame)
# Add new widgets to the window_frame:
widgets = dict(
child1 = dict(widget = CustomWidget(name='child1', master=master)),
child2 = dict(widget = CustomWidget(name='child2', master=master)),
# etc
)
# Keep track of the variable in the dict as well:
for key in widgets:
widgets[key]['variable'] = widgets[key]['widget'].variable
# Notes:
# window_frame is populated with widgets & a button to re-run main().
# At root level, window_frame also has a menu to run main()
# from various apps.
<-SNIP->
The expected behavior was that after the Widget (and its Variable) were destroyed, each tk.Variable would be re-initialized, and have the new value.
This appeared to be working for all of the widgets except for ones based on ttk.Checkbutton, which had the checkbutton going into an "alternate" state on app change/reload.
However upon further inspection, all the Widgets were affected.
My understanding is that after destroying the Widgets in window_frame, the named tk.Variables would still be "set" in Tcl when the widgets (and their embedded tk.Variables) were initialized. Therefore tkinter would re-use the Tcl variables with those names instead of initializing new tk.Variable instances. However, by the time the code attempted to get() the values from the tk.Variables (for example, to display a checkbutton in "selected" or "!selected" state) the tk.Variable was no longer "set" in Tcl and the widget would behave erratically, since the tk.Variable was not accessible via tk's setvar() or getvar() methods.
Proposed solution:
Someone recommended I keep track of the tk.Variable names at the root level, for example in a dict that would have the variable names passed to it. Then reuse variables as I go instead of creating new Variables with each Widget. (Note: I quickly discovered the tk.Variable instances would have to be kept track of in this dict, not just the name of the Variable!)
However, before I move forward, is there something else I am missing that is causing the tk.Variable instances to behave erratically?
For example, since I am keeping all the widget information, including a pointer to the widget, in a dict that is initialized on each main() call in each app module. Perhaps these dicts are not being properly deleted by the garbage collector when I run a new app's main()? Should I propagate the dictionaries upward, and update a dict at the root level, rather than initializing a new dict inside the main() of each app?
Initial fix:
My initial fix was to remove all the names I assigned to all of my tk.Variable instances. This allowed tkinter to initialize a new Variable upon each Widget initialization ("PYVAR_1", "PYVAR_2", etc.).
However, when I added a print statement in an app to track the number of Tcl variables, the number rapidly increased with each app change/refresh. The number of variables does not appear to decrease at any point, whether I wait several seconds and then change/refresh the app, or force window_frame.update() after destroying the child widgets.
Example app module:
<-SNIP->
# App window definition:
def main(window_frame, **kw):
print(root.tk.call("info", "vars")) # This number always increases on main() call
destroy_children(window_frame)
# Add new widgets here
<-SNIP->