Search code examples
python-3.xtkintercombobox

How to validate if a group of comboboxes have selections using tkinter?


I have a set of simple comboboxes and I created two buttons: one to clear the selections and one to validate if all the comboboxes have been selected. I'm using frames so I can treat the comboboxes as a group for each operation and I want to display a messagebox if one or more of the comboboxes have no selection. I found selection_present in the docs and a validatecommand, but I'm getting an error: AttributeError: 'ComboBoxTestGroup' object has no attribute 'tk', which is a separate issue that is preventing me from trying things.

My main question is: for a tkinter Combobox, what is a simple way to check if all the comboboxes are selected and message the user if they are not? Then, why am I getting an attribute error on the comboboxes as they should be members of the ComboBoxTestGroup class?

I'm new to tkinter and I'm stuck on this. Here is the code:

import tkinter
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import Combobox
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
   
window = ttk.Window(themename = 'superhero')
window.title('Combobox Test Group')
window.geometry('1200x275')

class ComboBoxTestGroup:
    def __init__(self, master):

        # Create the main frame.
        self.main_frame = ttk.Frame(master, relief=SUNKEN, borderwidth=1)
        self.main_frame.grid(row=0, column=0)

        # Dropdowns
        self.base_frame = tkinter.LabelFrame(self.main_frame, text='BASE', relief=SUNKEN, borderwidth=1)
        self.base_frame.grid(row=4, column=0, sticky="news", padx=10, pady=15)

        self.base_label_ti = ttk.Label(self.base_frame, text='Technical Interface (TI)', font=('Helvetica', 10))
        self.base_label_ti.grid(row=0, column=0)

        self.ti_values = [("High", "H", "1.0"), ("Medium", "M", "0.5"), ("Low", "L", "0.1"),  ("None", "N", "0.0")]
        self.ti_combobox = ttk.Combobox(self.base_frame, bootstyle='primary', values=self.ti_values, state = 'readonly')
        self.ti_combobox.grid(row=1, column=0)

        self.base_label_ap = ttk.Label(self.base_frame, text='Application Project (AP)', font=('Helvetica', 10))
        self.base_label_ap.grid(row=0, column=1)

        self.ap_values = [("High", "H", "1.0"), ("Medium", "M", "0.5"), ("Low", "L", "0.1"),  ("None", "N", "0.0")]
        self.ap_combobox = ttk.Combobox(self.base_frame, bootstyle='primary', values=self.ap_values, state = 'readonly')
        self.ap_combobox.grid(row=1, column=1)
        
        self.base_label_al = ttk.Label(self.base_frame, text='Application Logs (AL)', font=('Helvetica', 10))
        self.base_label_al.grid(row=0, column=2)

        self.al_values = [("High", "H", "1.0"), ("Medium", "M", "0.5"), ("Low", "L", "0.1"),  ("None", "N", "0.0")]
        self.al_combobox = ttk.Combobox(self.base_frame, bootstyle='primary', values=self.al_values, state = 'readonly')
        self.al_combobox.grid(row=1, column=2)
        
        self.base_label_ic = ttk.Label(self.base_frame, text='Internal Composition (IC)', font=('Helvetica', 10))
        self.base_label_ic.grid(row=0, column=3)

        self.ic_values = [("High", "H", "1.0"), ("Medium", "M", "0.5"), ("Low", "L", "0.1"),  ("None", "N", "0.0")]
        self.ic_combobox = ttk.Combobox(self.base_frame, bootstyle='primary', values=self.ic_values, state = 'readonly')
        self.ic_combobox.grid(row=1, column=3)
       
        self.base_label_fc = ttk.Label(self.base_frame, text='Fixed Controls (FC)', font=('Helvetica', 10))
        self.base_label_fc.grid(row=0, column=4)

        self.fc_values = [("High", "H", "1.0"), ("Medium", "M", "0.5"), ("Low", "L", "0.1"),  ("None", "N", "0.0")]
        self.fc_combobox = ttk.Combobox(self.base_frame, bootstyle='primary', values=self.fc_values, state = 'readonly')
        self.fc_combobox.grid(row=1, column=4)

        # For loop to space widgets within the base_frame
        for widget in self.base_frame.winfo_children():
            widget.grid_configure(padx=20, pady=10) 

        # Create a frame for the buttons.
        button_frame = ttk.Frame(self.main_frame, relief=SUNKEN, borderwidth=1)
        button_frame.grid(row=5, column=0)

        # Validate Button
        self.calculate_button = ttk.Button(button_frame,  
                            command = lambda: validateComboboxes(self),  # Use a lambda to send params to the function for command.
                            width = 12, 
                            text = "VALIDATE",
                            bootstyle = 'primary', 
                            state = 'enabled') 

        self.calculate_button.grid(row=10, column=0, padx=75, pady=10)

        # Clear Button
        self.clear_button = ttk.Button(button_frame,  
                            command = lambda: clear(self),  # Use a lambda to send params to the function for command.
                            width = 12, 
                            text = "CLEAR",
                            bootstyle = 'primary', 
                            state = 'enabled') 
        self.clear_button.grid(row=10, column=1, padx=75, pady=10)

        # Function to clear the ttk widgets.
        def clear(self):
            # This clears the Comboboxes by the enclosing frame.
            for widget in self.base_frame.winfo_children():
                if isinstance(widget, ttk.Combobox):
                    widget.set("")

                    # Function to validate if all the Comboboxes were selected.
        def validateComboboxes(self):
            for widget in self.base_frame.winfo_children():
                if not ttk.Combobox.selection_present(self):
                    messagebox.showerror("error", "All Comboboxes must be selected in order to do calculations!")
                    # widget.set("")

widget_class = ComboBoxTestGroup(window)

# Run the GUI 
window.mainloop() 

I used selection present as that appeared to be the obvious choice for my use case, but the attribute error is blocking me from getting to that result.


Solution

  • You are receiving an error because you are attempting to call selection_present as a class method when it should be used as an instance method.

    Either way, selection_present is not the method to use for this situation.

    One possible solution to the problem would be to check if the widget is an Combobox similar to what you are already doing in the clear method, and then use the get method to retrieve the current text, and check if it's == "" to see if its valid selection or not:

    For example something similar to this should probably work:

    def validateComboboxes(self):
        for widget in self.base_frame.winfo_children():
            if isinstance(widget, ttk.Combobox) and widget.get() == "":
                messagebox.showerror("error", "All Comboboxes must be selected in order to do calculations!")
                ...