John Hall John Hall - 7 months ago 20
Python Question

Why do PyTk Tkinter control value variables fail to update if stored in a list? What is the alternative?

The control variable fails to work if a reference to an object in a list is used as the variable property of a widget. The following code illustrates an example of an application object set to a list of TkInt objects. A test example is just set to a member variable that is a single TkInt object works as expected.
Why does this fail and what is the correct way to manage widgets and control variables that are variable in number?

import tkinter as tk

class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.line_whole = [tk.IntVar() for _ in range(6)]

def createWidgets(self):
tk
self.line_whole=list()
#self.line_whole = [tk.IntVar() for _ in range(6)]
self.line_whole_cb = list()
for x in range(6):
self.line_whole.append(tk.IntVar())
self.line_whole_cb.append(tk.Checkbutton(self,variable=self.line_whole[x]))
self.line_whole_cb[x].pack(side="left")
self.test_val = tk.IntVar()
self.test_cb = tk.Checkbutton(self,variable=self.test_val)
self.test_cb.pack(side="bottom")
self.output_record_btn = tk.Button(self)
self.output_record_btn["text"] = "Output Check Values"
self.output_record_btn["command"] = self.say_hi
self.output_record_btn.pack(side="top")

self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")

def say_hi(self):
print("checks:",",".join([str(tk_int.get()) for tk_int in self.line_whole]))
print ("test_check",self.test_val.get())

root = tk.Tk()
app = Application(master=root)
app.mainloop()

Answer

Here's a corrected version with minimal changes:

You were calling createWidgets before self.line_whole was set. I'm assuming that's why you had recreated it. You simply needed to switch the order you were calling things. Minor thing, if you don't plan on changing the onvalue and offvalue attributes why not use a BooleanVar()? Since the only values this can take are 0 and 1 and these are the defaults of the checkbuttons off and on values. Either one works of course and the choice is merely semantics.

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack()
        self.line_whole = [tk.IntVar() for _ in range(6)]
        self.createWidgets()


    def createWidgets(self):
        #self.line_whole = [tk.IntVar() for _ in range(6)]
        self.line_whole_cb = list()
        for x in range(6):
            self.line_whole_cb.append(tk.Checkbutton(self, 
                variable=self.line_whole[x]))
            self.line_whole_cb[x].pack(side="left")
        self.test_val = tk.IntVar()
        self.test_cb = tk.Checkbutton(self,variable=self.test_val)
        self.test_cb.pack(side="bottom")
        self.output_record_btn = tk.Button(self)
        self.output_record_btn["text"] = "Output Check Values"
        self.output_record_btn["command"] = self.say_hi
        self.output_record_btn.pack(side="top")

        self.QUIT = tk.Button(self, text="QUIT", fg="red",
                                            command=root.destroy)
        self.QUIT.pack(side="bottom")

    def say_hi(self):
        print("checks:",",".join([str(tk_int.get()) for tk_int in self.line_whole]))
        print ("test_check",self.test_val.get())

root = tk.Tk()
app = Application(master=root)
root.mainloop()
Comments