5

I am writing a small Tkinter/Python program, that has a list of checkboxes with variable length (determined at run time).

I want to be able to read the state of all the checkboxes at any time, but I am not sure how I should go about that.

Here's the code snippet for generating the list (adopted from this post):

def relist(self):
    self.text.delete(1.0,END)
    p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)
    lst = p.communicate()[0].split('\n')
    print lst
    for item in lst:
        v = tk.IntVar()
        cb = tk.Checkbutton(text="/dev/%s" % item, variable=v, command=self.cb(index))
        self.text.window_create("end", window=cb)
        self.text.insert("end", "\n") # to force one checkbox per line

And my dummy handler:

def cb(self,idx):
    print ("var is %s", str(idx))
    lst[idx] = 1;

The problem is that my handler is getting called once (when the Checkbuttons are created), whereas I want it to get called everytime a Checkbutton is clicked (checked or unchecked), and when it is called, I want it to update lst.

Community
  • 1
  • 1
EagerToLearn
  • 147
  • 1
  • 7
  • does it work ? where `index` in `self.cb(index)` is defined ? – joaquin Jan 26 '12 at 23:22
  • Please elaborate? In the code above, cb gets called once for each checkbox created, but only upon creating the checkbox. When called, cb is printing "var is " (where idx differs for each checkbox). But the issue is that cb is not getting called when a checkbox is modified (checked/unchecked) – EagerToLearn Jan 30 '12 at 23:10

4 Answers4

3

Your CheckButton command is executing the callback because that's what you are telling it to do. The command is supposed to be a reference to a function that tkinter can execute when the checkbutton is clicked. Tkinter passes the event object to the callback function. See this Effbot tutorial, but it looks like you are trying to implement their pattern already. You can get a reference to the checkbutton from the event.widget attribute as explained here. Finally, you need to attach your variable to "self" if you want to refer to it in the callback.

def relist(self):
    self.text.delete(1.0,END)       
    p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)       
    lst = p.communicate()[0].split('\n')       
    print lst       
    self.var = tk.IntVar()
    for item in lst:           
        cb = tk.Checkbutton(text="/dev/%s" % item, variable=self.var, command=self.myCallback)
        self.text.window_create("end", window=cb)     
        self.text.insert("end", "\n") # to force one checkbox per line

def myCallback(self,event):
    var = self.var.get()
    print ("var is %s", str(var))
tharen
  • 1,262
  • 10
  • 22
  • Thank you for the response. My issue is that the callback method(s, for the multiple checkboxes) are not being called every time the checkbox is clicked, they are all being called only once when the checkboxes are created. The output is always "var is 0" repeated. Also, if I include 'event' in myCallback definition, I get an error "TypeError: myCallback() takes exactly 2 arguments (1 given)" – EagerToLearn Jan 30 '12 at 17:37
  • Is there a way to pass an index to the callback method then? – EagerToLearn Jan 30 '12 at 23:39
  • Use the event object to get to the checkbox object. If you need a variable, is the variable attribute. – tharen Jan 30 '12 at 23:44
  • 1
    This causes the same variable to be associated with multiple checkboxes. That's not how you're supposed to use checkboxes -- each one should have it's own variable. – Bryan Oakley Mar 18 '15 at 20:07
0

I think what you have asked for can be derived from here.

For each item in lst it must be previously created different IntVar() variable, just to indicate independent state of each checkbox. I do not see other way than to create them manually (I assume you don't have hundred of checkboxes). I will re-use the code from this answer and do the following:

def relist(self):
    self.text.delete(1.0,END)       
    p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)       
    lst = p.communicate()[0].split('\n')       
    print lst       
    self.var1 = tk.IntVar()
    self.var2 = tk.IntVar()
    self.var3 = tk.IntVar()
    .
    .
    . 
    vars = [self.var1,self.var2,self.var3,...]
    for item, var in zip(self.lst, vars):           
        cb = tk.Checkbutton(text="/dev/%s" % item, variable=var, command= lambda: self.myCallback(var))
        self.text.window_create("end", window=cb)     
        self.text.insert("end", "\n") # to force one checkbox per line

def myCallback(self,event,var):
    each_var = var.get()
    print ("var is %s", str(each_var))
Community
  • 1
  • 1
lujjas
  • 68
  • 9
0

I had the same issue. Try this one:

cb = tk.Checkbutton(text="/dev/%s" % item, variable=v, command=lambda: self.cb(index))

If you pass method as lambda function it executes the method on every change of the variable.

0

Personally i don't use a tk.IntVar() / tk.StringVar() etc. but maybe i should. It may not be the best way to do that but i think it's pretty much easy to understand. don't hesitate to criticize and tell me what's really bad and not pythonic so i can improve myself (i'm still a newbie).

i make an interator then i create my checkbuttons in a loop and in the callback I pass in parameter the value of the checkbutton and the iterator.

    ...
    self.listeColonneFile1 = []

    self.chbFile1 = []
    indice = 0
    for column in dfFile1.columns:
        btn = ttk.Checkbutton(self.frameCheckButtonsFile1,
            text=column,
            command=lambda i=indice, col=column: self.callback_onCheck(col, i)
        )

        self.chbFile1.append(btn)
        self.chbFile1[indice].grid(row = indice, column = 0, sticky="nw")
        self.chbFile1[indice].state(['!alternate'])
        indice += 1

In my callback, i have a list of all the checkButtons which are checked (well, not the ChB but its text or its value) :

def callback_onCheck(self, column, indice):
    if self.chbFile1[indice].instate(['selected']) == True:
        self.listeColonneFile1.append(column)
    else:
        self.listeColonneFile1.remove(column)

PS : dfFile1 is a pandas DataFrame, see the doc