I have this sample code I am running. I am creating a window, and when I load the template page and click the "Click me" button, it adds 20 boxes on the screen. 10 rows, 2 wide. Column 1 is Car makes, and column 2 is Models.
When I click the Make box in row 1, and change it from Ford to Toyota, I want the model combobox in row 1 to change to show the Toyota models. But it only works for the last row. Is it possible to get each row to work?
import tkinter as tk
from tkinter import font as tkfont, filedialog, messagebox
from tkinter.ttk import Combobox
class SLS_v1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Setting up the root window
self.title('Test App')
self.geometry("200x300")
self.resizable(False, False)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["MenuPage"] = MenuPage(parent=container, controller=self)
self.frames["template"] = template(parent=container, controller=self)
self.frames["MenuPage"].grid(row=0, column=0, sticky="nsew")
self.frames["template"].grid(row=0, column=0, sticky="nsew")
self.show_frame("MenuPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class MenuPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
template = tk.Button(self, text='Template', height=3, width=20, bg='white', font=('12'),
command=lambda: controller.show_frame('template'))
template.pack(pady=50)
class template(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid(columnspan=10, rowspan=10)
button = tk.Button(self, text='Click me', command= lambda: stored_functions().add_boxes(self))
button.grid(row=11, column=1)
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
self.make_var = tk.StringVar(main_window)
self.make_options = self.make
self.make_var.set(self.make_options[0])
self.make_selection = tk.ttk.Combobox(main_window, value=self.make_options,
state='readonly', width=10)
self.make_selection.current(0)
self.make_selection.bind('<<ComboboxSelected>>', lambda event:
stored_functions.update_models(self, selection=self.make_selection))
self.make_selection.grid(row=row, column=1)
self.model_var = tk.StringVar(main_window)
self.model_options = self.models[0]
self.model_var.set(self.model_options[0])
self.model_selection = tk.ttk.Combobox(main_window, value=self.model_options,
state='readonly', width=10)
self.model_selection.current(0)
self.model_selection.grid(row=row, column=2)
def update_models(self, selection):
if selection.get() == 'Ford':
self.model_options = self.models[0]
if selection.get() == 'Toyota':
self.model_options = self.models[1]
if selection.get() == 'Honda':
self.model_options = self.models[2]
self.model_selection.config(values=self.model_options)
self.model_selection.current(0)
if __name__ == "__main__":
app = SLS_v1()
app.mainloop()
CodePudding user response:
You have used same variables for car brands and models selection, so the variables refer the last set after the for loop.
You need to pass the model combobox to update_models()
using default value of argument:
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
self.make_var = tk.StringVar(main_window)
self.make_options = self.make
self.make_var.set(self.make_options[0])
self.make_selection = tk.ttk.Combobox(main_window, value=self.make_options,
state='readonly', width=10)
self.make_selection.current(0)
self.make_selection.grid(row=row, column=1)
self.model_var = tk.StringVar(main_window)
self.model_options = self.models[0]
self.model_var.set(self.model_options[0])
self.model_selection = tk.ttk.Combobox(main_window, value=self.model_options,
state='readonly', width=10)
self.model_selection.current(0)
self.model_selection.grid(row=row, column=2)
# pass the corresponding model combobox to bind function
self.make_selection.bind(
'<<ComboboxSelected>>',
lambda event, peer=self.model_selection: self.update_models(event.widget.get(), peer)
)
def update_models(self, selection, model_selection):
model_options = self.models[self.make.index(selection)]
model_selection.config(values=model_options)
model_selection.current(0)
Note that it is not necessary to use instance variables inside the for loop. Also those StringVar
s are not used at all, so the functions can be simplified as below:
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
make_selection = tk.ttk.Combobox(main_window, value=self.make,
state='readonly', width=10)
make_selection.current(0)
make_selection.grid(row=row, column=1)
model_selection = tk.ttk.Combobox(main_window, value=self.models[0],
state='readonly', width=10)
model_selection.current(0)
model_selection.grid(row=row, column=2)
make_selection.bind(
'<<ComboboxSelected>>',
lambda event, peer=model_selection: self.update_models(event.widget.get(), peer)
)
def update_models(self, selection, model_selection):
model_options = self.models[self.make.index(selection)]
model_selection.config(values=model_options)
model_selection.current(0)