I have a class: MyClassName for example, with two functions: one to calculate a number and one to validate if a group of comboboxes (tkinter) all have selections. I'm also using tkinter for a button to call the calculate function using a lambda because I want the user to make the combobox selections and then press the calculate button, hence the need for a lambda.
Here is the button code:
# Calculate Button
self.calculate_button = ttk.Button(button_frame,
command = lambda: calculate(self), # Use a lambda to send params to the function for command.
width = 12,
text = "CALCULATE",
bootstyle = 'primary',
state = 'enabled')
# In the calculate function, I call another function:
def calculate(self):
# Call the Class function from within another Class function.
self.validate_Comboboxes() ...
And in the validate_Comboboxes function I use something like this:
# Function to validate if all the Comboboxes were selected.
def validate_Comboboxes(self):
for widget in self.base_frame.winfo_children():
if isinstance(widget, ttk.Combobox) and widget.get() == "":
messagebox.showerror("error", "All Comboboxes for the base frame must be selected for calculations!")
break
...
I tested the validate_Comboboxes function standalone outside this app, so I know it works. However, I get this error: AttributeError: 'MyClassName' object has no attribute 'validate_Comboboxes'. Which seems like I don't have it declared properly? I understand if it was simply a class attribute, you would use: myAttribute(self) to declare it, but validate_Comboboxes(self) doesn't make sense to me as I declare it using my def statement, unless I"m missing something?
What is the correct way to code this for my use case, mainly to validate the user has all the comboboxes selected before they hit the calculate button?
I thought if you had two functions inside a class, like functionA(self) and functionB(self), you could call functionA from functionB using self.function(). So is the mechanism of using a button with a lambda the issue here?
Using your previous questions code as context, you are experiencing these errors due to the scope at which you define your validateComboboxes
, calculate
, and clear
functions.
Considering the following to be a summarized version of your code...
class ComboBoxTestGroup:
def __init__(self, master):
...
...
def calculate(self):
...
def clear(self):
...
def validateComboboxes(self):
...
Because the three functions previously mentioned are defined inside the __init__
method body, those functions are not bound to the ComboBoxTestGroup
instance and are only accessible to the code that executes within the __init__
methods scope. This, plus the fact that you are using a lambda which will be evaluated during runtime means that when you attempt to call the calculate(self)
the function definition will no longer exist.
There is a very simple solution. Define your methods in the body/scope of the class instead of the __init__
method. This will ensure that the functions will be bound to the class instance and will remain callable for the lifetime of the instance object.
Example:
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 = self.calculate, # no need for lambda
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=self.clear, # no need for lambda
width = 12,
text = "CLEAR",
bootstyle = 'primary',
state = 'enabled')
self.clear_button.grid(row=10, column=1, padx=75, pady=10)
def calculate(self):
self.validateComboboxes()
# 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 isinstance(widget, ttk.Combobox) and widget.get() == "":
messagebox.showerror("error", "All Comboboxes must be selected in order to do calculations!")
widget_class = ComboBoxTestGroup(window)
# Run the GUI
window.mainloop()