Tkinter - Creating multiple check boxes using a loop

Viewed 74

I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.

#!/usr/bin/python3

from tkinter import *
window = Tk()

#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)

f = [] #check boxes

ft = open("Fighters.txt").readlines() #list of all the character names

fv=[0]*78 #list for tracking what boxes are checked

ff=[] #list to place final character strings

def  reset():
   for i in fv:
       fv[i]=0

rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)

for y in range (0,77):
    f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
    f[y].grid(column=0, row=4+y)

def done():
    for j in fv:
        if fv[j] == 1:
            ff.append(fv[j])
    result = random.choice(ff)
    r=Label(window, text=result)

d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)

window.mainloop()
2 Answers

Unfortunately I'm afraid you are going to have to create variables for each checkbox.

tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.

Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):

import random
from tkinter import *

window = Tk()

#window name and header
window.title("Custom Random SSBU")

lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)

with open("Fighters.txt") as fighters:
    ft = fighters.read().splitlines() # List of all the character names.

fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.

ff = [] # List to place final character strings.

def  reset():
   for var in fv:
       var.set(False)

rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)

for i, (name, var) in enumerate(zip(ft, fv)):
    chk_btn = Checkbutton(window, text=name, variable=var)
    chk_btn.grid(column=0, row=i+4, sticky=W)

def done():
    global ff
    ff = [name for name, var in zip(ft, fv) if var.get()]  # List of checked names.
    # Randomly select one of them.
    choice.configure(text=random.choice(ff) if ff else "None")

d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)

choice = Label(window, text="None")
choice.grid(column=1, row=3)

window.mainloop()

I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.

variable = fv[y]

This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.

You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:

text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
    Checkbutton(window, text = line, variable = tracker)
    for line, tracker in zip(text, trackers)
]

(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)

When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.

So we want something more like:

result_label = Label(window) # create it ahead of time

def done():
    result_label.text = random.choice(
        label
        for label, tracker in zip(text, trackers)
        if tracker.get()
    )
Related