0

I'm doing my own tweak on the technique from this post, using a canvas vs. a text widget to get some finer control over the scrolling behavior. My code appears below.

All is working as I want it but for some reason the trace I'm using to track the checkbutton values isn't working. No errors of any kind show up in the console window. But I don't get the expected printed message (from _cbWasClicked) when I click one any of the checkbuttons. As best I can tell the method is just never invoked.

I know it's got to be a simple and obvious bug but I'm stumped. I've used print statements to confirm that the 100 IntVars get instantiated as expected. Then I deliberately misspelled the method name in the .trace and this time it generated an error. So when I yank those diagnostic tweaks all should be working.... it just isn't. Can someone tell me what I'm missing?

Environment is Python 2.7 on Windows 7.

import Tkinter as tk

class myCheckList(tk.Frame):
    def __init__(self, root, *args, **kwargs):
        tk.Frame.__init__(self, root, *args, **kwargs)
        self.root = root
        self.vsb = tk.Scrollbar(self, orient="vertical")
        self.canvas = tk.Canvas(self, width=200, height=290, 
                                relief=tk.GROOVE,bd=3,
                                scrollregion=(0,0,0,2020),
                                yscrollcommand=self.vsb.set,
                                yscrollincrement=20)
        basecolor = self.canvas.cget('background')
        self.vsb.config(command=self.canvas.yview)
        self.canvas.grid(row=0,column=0,sticky=tk.NSEW,padx=(0,0),pady=0)
        self.vsb.grid(row=0,column=1,sticky=tk.NS,padx=(0,0),pady=0)
        for i in range(100):
            cbv = tk.IntVar()
            cbv.trace('w',self._cbWasClicked)
            cb  = tk.Checkbutton(self, background=basecolor,
                                 variable=cbv,
                                 text="Checkbutton #%s" % i)
            self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=cb)
        self.canvas.bind_all('<MouseWheel>',
         lambda event: self.canvas.yview_scroll(-1*event.delta/120, tk.UNITS))

    def _cbWasClicked(self,*args):
        print 'checkbox clicked'

if __name__ == "__main__":
    root = tk.Tk()
    myCheckList(root).grid(row=0,column=0,sticky=tk.W,padx=0,pady=0)
    root.mainloop()
Community
  • 1
  • 1
JDM
  • 1,709
  • 3
  • 25
  • 48

2 Answers2

2

Found it, after much wrestling and experimenting. It turns out that the trace works perfectly when I add a couple of lines to the class's __init__:

self.status = []

...and then, inside the loop...

    self.status.append((cb,cbv))

...which tells me that garbage collection is the culprit. By creating a list and storing the object references in it, they couldn't be garbage-collected and so the .trace remains effective.

JDM
  • 1,709
  • 3
  • 25
  • 48
1

First off, you should prepend self. to cbv and cb within the FOR cycle. Secondly, even then it is going to work only for the very last checkbox, because with each iteration you overwrite the variable cbv again and again. As a workaround I used a list of vaiables (self.li) generated one step before the cycle. This way you can link each checkbox to its own variable:

self.li = ['cbv' + str(i) for i in range(100)]
        for i in range(100):
            self.li[i] = tk.IntVar()
            self.cb  = tk.Checkbutton(self, background=basecolor,
                                 variable=self.li[i],
                                 text="Checkbutton #%s" % i)
            self.li[i].trace('w', self._cbWasClicked)
            self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=self.cb)
            ...

This code worked fine for me. You will then need to identify each checkbox somehow. You can do it using the internal variable name which is passed as the first param to the callback function in the trace method (What are the arguments to Tkinter variable trace method callbacks?):

def _cbWasClicked(self, name, *args):
        print('checkbox %s clicked:' % name)

In the output you'll get something like this:

checkbox PY_VAR10 clicked:
checkbox PY_VAR99 clicked:
checkbox PY_VAR0 clicked: