Generate checkbutton based on radiobutton selection

171 views Asked by At

I am trying to generate checkbuttons based on radiobutton selection using Tkinter in Python. Here are the dictionaries; the keys should show as checkbuttons:

PERISHABLE_OPTIONS = {'Vegetables': 0, 'Fruits': 0, 'Bread': 0, 'Dairy': 0, 'Meat': 0, 'Other': 0}
NONPERISHABLE_OPTIONS = {'Books': 0, 'Clothes': 0, 'Dry Food': 0, 'Household': 0, 'Sanitary': 0, 'Other': 0}

Here is the code that shows the radiobuttons and calls the method for the checkbuttons:

class FormInput(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="Choose your donations!", font=controller.title_font)
        label.pack(side="top", fill="x", pady=10)
        style = Style(self)
        style.configure("TRadiobutton", font=("Calibri", 18))
        category_of_donation = {"Perishable": "1", "Non-Perishable": "2"}
        var = tk.StringVar()
        for text, value in category_of_donation.items():
            r = Radiobutton(self, text=text, variable=var, value=value, command=lambda: controller.show_checkbox(var))
            r.pack(anchor=CENTER, padx=5, pady=5)
        submit_button = tk.Button(self, text="Submit", command=lambda: controller.show_frame("PageTwo"))
        submit_button.pack()

And here is the method for showing checkbuttons:

def show_checkbox(self, v):
    if v.get() == '1':
        for i in PERISHABLE_OPTIONS:
            PERISHABLE_OPTIONS[i] = IntVar()
            c = Checkbutton(self, text=i, variable=PERISHABLE_OPTIONS[i])
            c.pack(anchor="center", padx=5, pady=5)
    elif v.get() == '2':
        for i in NONPERISHABLE_OPTIONS:
            NONPERISHABLE_OPTIONS[i] = IntVar()
            c = Checkbutton(self, text=i, variable=NONPERISHABLE_OPTIONS[i])
            c.pack(anchor="center", padx=5, pady=5)

My issue is the checkbuttons keep appearing every time I click on the radiobuttons. For example, I will click on the radiobutton that says Perishable, and it will give me the list of checkbuttons I want (Vegetables to Other). But when I toggle, it shows the list attributing to Non-Perishable (Books and onwards), but it will still show the Perishable list too. Here is a screenshot:

checkbutton not erasing

I'm surely missing something but I cannot figure it out. Can someone help me please? Thank you!

1

There are 1 answers

0
Sylvester Kruin - try Codidact On

As @Henry Yik said, you shouldn't recreate all the checkbuttons every time you want to show them; 1) because it's easier to write that doesn't recreate them, and 2) because code that does recreate them will be much slower than code that doesn't.

As usual, I'll explain everything, but if you want to skip the explanations, at the bottom will be a (minimalized) working example.


Creating the checkbuttons

First, in order to make it so that the checkbuttons can be packed and pack_forgotten, they need to be created. For your purposes, it's simplest to use a for loop to create the checkbuttons, and then add them to a list. Here's an example for creating the perishable checkbuttons:

self.perishables = []
    for i in PERISHABLE_OPTIONS:
        PERISHABLE_OPTIONS[i] = tk.IntVar()
        checkbutton = tk.Checkbutton(self, text=i, variable=PERISHABLE_OPTIONS[i])
        self.perishables.append(checkbutton)

As you can see, this simply loops through all the keys in PERISHABLE_OPTIONS, and creates checkbuttons based on them, in much the same way as you did in your code. The only difference is that the checkbuttons are stored in a list called self.perishables for later use. You can do the same for the non-perishable checkbuttons.

Packing/unpacking the checkbuttons

Now that you've created a list for each type of checkbutton, you need to know how to pack/unpack them. We'll do that by checking which radio button is selected (which you've already done), and then:

  1. Looping through the checkbuttons of the not-selected radiobutton and calling their pack_forget() method.
  2. Looping through the checkbuttons of the selected radiobutton and calling their pack() method, with the appropriate arguments.

Here is an example that does just that, unpacking the non-perishable checkbuttons and packing the perishable checkbuttons:

def show_checkbox(self, v):
    """Show the checkboxes for selected radio button NUMBER."""
    
    # Show the perishables
    if v.get() == "1":
        for c in self.nonperishables:
            c.pack_forget()
        for c in self.perishables:
            c.pack(anchor="center", padx=5, pady=5)
    
    # Or show the non-perishables
    if v.get() == "2":
        for c in self.perishables:
            c.pack_forget()
        for c in self.nonperishables:
            c.pack(anchor="center", padx=5, pady=5)

Showing the checkbuttons when the app starts

You might notice that, when the app starts, neither radiobutton will be selected, so neither list of checkbuttons will be shown. We can solve both these problems simultaneously by setting an initial value for the radiobuttons' var, like this:

category_of_donation = {"Perishable": "1", "Non-Perishable": "2"}
var = tk.StringVar(value="1")
for text, value in category_of_donation.items():
    r = tk.Radiobutton(self, text=text, variable=var, value=value, command=lambda: self.show_checkbox(var))
    r.pack(anchor="center", padx=5, pady=5)

This code will have the perishables selected first. If you want to show the non-perishables first, just change tk.StringVar(value="1") to tk.StringVar(value="2").


The final code

Alright, now that all that's said, here is a simplified working example based off of your code:

import tkinter as tk

PERISHABLE_OPTIONS = {'Vegetables': 0, 'Fruits': 0, 'Bread': 0, 'Dairy': 0, 'Meat': 0, 'Other': 0}
NONPERISHABLE_OPTIONS = {'Books': 0, 'Clothes': 0, 'Dry Food': 0, 'Household': 0, 'Sanitary': 0, 'Other': 0}

class Frame(tk.Frame):
    """The frame that has all the checkbuttons."""
    
    def __init__(self, master):
        tk.Frame.__init__(self, master=master)
        
        # Create the label
        label = tk.Label(self, text="Choose your donations!")
        label.pack(side="top", fill="x", pady=10)

        # Create the radiobuttons
        category_of_donation = {"Perishable": "1", "Non-Perishable": "2"}
        var = tk.StringVar(value="1")
        for text, value in category_of_donation.items():
            r = tk.Radiobutton(self, text=text, variable=var, value=value, command=lambda: self.show_checkbox(var))
            r.pack(anchor="center", padx=5, pady=5)
            
        # Create the submit button (doesn't do anything here)
        tk.Button(self, text="Submit").pack()
        
        # Create the checkbuttons
        self.perishables = []
        for i in PERISHABLE_OPTIONS:
            PERISHABLE_OPTIONS[i] = tk.IntVar()
            checkbutton = tk.Checkbutton(self, text=i, variable=PERISHABLE_OPTIONS[i])
            self.perishables.append(checkbutton)
        
        # Create the checkbuttons
        self.nonperishables = []
        for i in NONPERISHABLE_OPTIONS:
            NONPERISHABLE_OPTIONS[i] = tk.IntVar()
            checkbutton = tk.Checkbutton(self, text=i, variable=NONPERISHABLE_OPTIONS[i])
            self.nonperishables.append(checkbutton)
            
        # Show the perishabe checkbuttons
        self.show_checkbox(var)
            
    def show_checkbox(self, v):
        """Show the checkboxes for selected radio button NUMBER."""
        
        # Show the perishables
        if v.get() == "1":
            for c in self.nonperishables:
                c.pack_forget()
            for c in self.perishables:
                c.pack(anchor="center", padx=5, pady=5)
        
        # Or show the non-perishables
        if v.get() == "2":
            for c in self.perishables:
                c.pack_forget()
            for c in self.nonperishables:
                c.pack(anchor="center", padx=5, pady=5)

if __name__ == "__main__":
    root = tk.Tk()
    frame = Frame(root)
    frame.pack(expand=True, fill="both")
    root.mainloop()