Home>

I want to create a function that arranges multiple Tkinter checkboxes and gets the value (True, False) when the checkbox is pressed.

In Tkinter, 9x5 layout, the process of arranging the check box and the process of getting the value were implemented using the exec function.
In addition, there are 45 check box items.
Below is the code when implemented.

from tkinter import *
from tkinter import ttk
root = Tk ()
root.title ('Checkbutton')
frame1 = ttk.Frame (root, padding = (10))
frame1.grid ()
# 9 x 5 matrix
for i in range (9): #line
    for j in range (5): #column
        exec ('v {} = BooleanVar ()'. format (j + 5 * i + 1))
        exec ('v {} .set (False)'.format (j + 5 * i + 1))
        exec ("" "cb {} = ttk.Checkbutton ("" "cb {}
        frame1, padding = 10, text ='test {}',
        variable = v {},
        command = lambda: print ('v {} =% s'% v {} .get ())) "" ". format (j + 5 * i + 1, j + i * 5 + 1, j + 5 * i + 1, j + 5 * i + 1, j + 5 * i + 1, j + 5 * i + 1))
        exec ("cb {} .grid (row = {}, column = {})". format (j + 5 * i + 1, i, j))
        print ("cb {} .grid (row = {}, column = {})". format (j + 5 * i + 1, i, j))
root.mainloop ()


When executed, the following screen will be displayed, and if you press or do not press the check box,v1 = True,v2 = True,v3 = False,v3 = TrueCan be obtained.

However, if you make it a function like the code below, you will not be able to get it.

from tkinter import *
from tkinter import ttk
root = Tk ()
root.title ('Checkbutton')

def main ():
    #Frame
    frame1 = ttk.Frame (root, padding = (10))
    frame1.grid ()
    # 9 x 5 matrix
    for i in range (9): #line
        for j in range (5): #column
            exec ('v {} = BooleanVar ()'. format (j + 5 * i + 1))
            exec ('v {} .set (False)'.format (j + 5 * i + 1))
            exec ("" "cb {} = ttk.Checkbutton ("" "cb {}
            frame1, padding = 10, text ='test {}',
            variable = v {},
            command = lambda: print ('v {} =% s'% v {} .get ())) "" ". format (j + 5 * i + 1, j + i * 5 + 1, j + 5 * i + 1, j + 5 * i + 1, j + 5 * i + 1, j + 5 * i + 1))
            exec ("cb {} .grid (row = {}, column = {})". format (j + 5 * i + 1, i, j))
            print ("cb {} .grid (row = {}, column = {})". format (j + 5 * i + 1, i, j))
main ()
root.mainloop ()


If i make it a function, for example, when you press the check box of test1, the following error will be displayed.

Example
NameError: name'v1' is not defined


How can I get the value when I make it a function?

Postscript

Actually, I think it is less likely to cause an error if you declare variables one by one without using the exec function, but I don't like the large number of variables, so I definitely want to use the exec function.
Also, if it does not function, it will work normally, but I would like to incorporate it into other processing based on the created function, so please let me know if there is a way to make it a function.

  • Answer # 1

    I don't like the large number of variables, so I really want to use the exec function.

    If the reason is the number of variables
    It's definitely better not to use the exec function here.

    There is no need to increase the number of variables, manage them in a list or dictionary.
    rather,The number of variables is increased by using exec.
    It is also not a good use for exec.

    If you make it into a function like the code below, you will not be able to get it.

    The cause is that tk.BooleanVar is a local variable in the main function.
    If you didn't use any functions, they were all global variables.

    If you make it a global variable, it will cause malfunction when used elsewhere.
    I don't recommend this, but you can see it in action.

    exec ('global v {0} \ nv {0} = BooleanVar ()'. format (j + 5 * i + 1))


    Addendum: Other changes, root ~ mainloop (), will be moved into the main function.

    To make it a local variable, you can specify a dictionary of local variables as an argument to exec.
    However, this is also not the recommended solution. The code becomes more complicated.

    Use a list or dictionary to manage Checkbuttons and BooleanVar.

  • Answer # 2

    How to make multiple checkbuttons

    The checkbutton is generated in a loop.

    Variables generated including checkbutton are stored in the dictionary.

    To identify the checked widget and print its status

    In the event before the check status changes, hold down the widget that generated the event.

    In the event after the check state changes, the necessary data is fetched from the widget and printed.

    from tkinter import *
    from tkinter import ttk
    root = Tk ()
    root.title ('Checkbutton')
    frame1 = ttk.Frame (root, padding = (10))
    frame1.grid ()
    #Before the check status changes
    def prev_cb (e):
        # Hold down the widget from which the event occurred
        global cur_cb
        cur_cb = e.widget
    #After the check status changes
    def after_cb ():
        row, col, var = cbs [cur_cb]
        print (f'{row + 1}, {col + 1} = {var.get ()}')
    # Initialize one checkbutton
    def init_check (frm, row, col):
        var = BooleanVar ()
        var.set (False)
        cb = ttk.Checkbutton (frm, padding = 10, text = f'{row + 1} x {col + 1}', variable = var, command = after_cb)
        cb.bind ('<1>', prev_cb)
        cb.grid (row = row, column = col)
        return cb, var
    cbs = {} #Dictionary for holding widget etc.
    cur_cb = None # event checkbutton
    for row in range (2): #row
        for col in range (3): #column
            cb, var = init_check (frame1, row, col)
            cbs [cb] = (row, col, var) # Keep necessary variables linked to widget
    root.mainloop ()
    Example using functools.partial proposed by teamikl
    from tkinter import *
    from tkinter import ttk
    import functools
    root = Tk ()
    root.title ('Checkbutton')
    frame1 = ttk.Frame (root, padding = (10))
    frame1.grid ()
    def after_cb (row, col, var):
        print (f'{row + 1}, {col + 1} = {var.get ()}')
    # Initialize one checkbutton
    def init_check (frm, row, col):
        var = BooleanVar ()
        var.set (False)
        f = functools.partial (after_cb, row, col, var)
        #print (id (f))
        cb = ttk.Checkbutton (frm, padding = 10, text = f'{row + 1} x {col + 1}', variable = var, command = f)
        cb.grid (row = row, column = col)
    for row in range (2): #row
        for col in range (3): #column
            init_check (frame1, row, col)
    root.mainloop ()