自定义tkinter Combobox首次点击下拉箭头无响应问题求助
问题描述
自定义的CTkComboBox存在异常:首次点击下拉箭头时,下拉框显示为空;点击框体空白区域后,选项才会正常显示。为何首次点击无法加载选项?
相关代码如下:
# nested class is called when the user wants to create a student class Student_Details: def __init__(self,parent): self.parent = parent # 'parent' is used so nested class can call and inherit any attributes and methods from parent class ('Classes') self.creating_Student_Details()#calling function self.updating_student_names()#calling function self.deleting_student_names()#calling function def creating_Student_Details(self): # this function creates frame for user to enter students details self.createStudentsFrame = ctk.CTkFrame(self.parent.classFrame) self.createStudentsFrame.place(relx=0.05, rely=0.3) self.create_student_description = ctk.CTkLabel( self.createStudentsFrame, text="Create Student", font=ctk.CTkFont(size=20, weight="bold") ) self.create_student_description.grid(row=0,column=0) self.firstname_Label = ctk.CTkLabel(self.createStudentsFrame,text="First Name") self.firstname_Label.grid(row=1,column=0) self.firstname_Entry = ctk.CTkEntry(self.createStudentsFrame) self.firstname_Entry.grid(row=1,column=2) self.lastname_Label = ctk.CTkLabel(self.createStudentsFrame,text="Last Name") self.lastname_Label.grid(row=2,column=0) self.lastname_Entry = ctk.CTkEntry(self.createStudentsFrame) self.lastname_Entry.grid(row=2,column=2) self.save_students_btn = ctk.CTkButton(self.createStudentsFrame, text="Save") self.save_students_btn.grid(row=4,column=2) self.save_students_btn.bind("<Button-1>", self.students_to_database) for widget in self.createStudentsFrame.winfo_children(): widget.grid_configure(padx=10, pady=10) # for loop that loops through all widgets # all widgets have same amount of padding between them def students_to_database(self,event): # function is called when user wants to save student to database firstname = self.firstname_Entry.get() lastname = self.lastname_Entry.get() connection = sqlite3.connect("Teacher.db") cursor = connection.cursor() # creates table within database if it has not been created yet. createStudentTable = ''' CREATE TABLE IF NOT EXISTS Students ([First Name] TEXT, [Last Name] TEXT, StudentID INTEGER PRIMARY KEY UNIQUE) ''' # making user of primary keys to make links between the tables cursor.execute(createStudentTable) # code below ensures that the same name cannot be added to database # if it does an error message shows query_students_table = """SELECT * from Students""" cursor.execute(query_students_table) fetching_rows = cursor.fetchall() flag = False for i in fetching_rows: if (firstname,lastname) == (i[0],i[1]): flag = True break if flag: messagebox.showerror("Error", "This name already exists!") elif firstname=="" or lastname == "": messagebox.showerror("Error", "All fields must be entered!") else: insertClassDetails = '''INSERT INTO Students([First Name],[Last Name])VALUES(?,?)''' insertToClassTable = (firstname, lastname) cursor.execute(insertClassDetails, insertToClassTable) connection.commit() connection.close() # code below is used to update/change a students name def updating_student_names(self): # function is called so user can change a students name if they made a mistake # function creates all labels, frames etc needed self.update_name_frame = ctk.CTkFrame(self.parent.classFrame) self.update_name_frame.place(relx=0.35, rely=0.3) self.update_name_description = ctk.CTkLabel( self.update_name_frame, text="Update Student Name", font=ctk.CTkFont(size=20, weight="bold") ) self.update_name_description.grid(row=0,column=0) self.original_name = ctk.CTkLabel(self.update_name_frame, text="Update Name:") self.original_name.grid(row=6,column=0) self.original_name_Options = ctk.CTkComboBox(self.update_name_frame, state="readonly")#, values=self.optionNames) self.original_name_Options.bind("<Button-1>", self.names)#binding combobox to function self.original_name_Options.grid(row=6,column=1) self.change_firstname_label = ctk.CTkLabel(self.update_name_frame, text="Change first name to:") self.change_firstname_label.grid(row=7,column=0) self.change_firstname_entry = ctk.CTkEntry(self.update_name_frame) self.change_firstname_entry.grid(row=7,column=1) self.change_lastname_label = ctk.CTkLabel(self.update_name_frame, text="Change last name to:") self.change_lastname_label.grid(row=8,column=0) self.change_lastname_entry = ctk.CTkEntry(self.update_name_frame) self.change_lastname_entry.grid(row=8,column=1) self.save_updated_name_btn = ctk.CTkButton(self.update_name_frame, text="Save Updated Name") self.save_updated_name_btn.grid(row=9,column=1) self.save_updated_name_btn.bind("<Button-1>", self.update_name)#binding button to function for widget in self.update_name_frame.winfo_children(): widget.grid_configure(padx=10, pady=10) # for loop that loops through all widgets # all widgets have same amount of padding between them def names(self,event): # function ensures combobox holds all names from database connection = sqlite3.connect("Teacher.db") cursor = connection.cursor() # checks to ensures that the students table exists # if not it tells te user that they need to enter the students name into the database first checking_table_existence = """SELECT name from sqlite_master WHERE type='table' AND name='Students'""" cursor.execute(checking_table_existence) exists = cursor.fetchone() if exists: query_students = """SELECT [First Name], [Last Name] from Students""" cursor.execute(query_students) selectingNames = cursor.fetchall() optionNames = [] # using for loop to loop through all the names. # since my database has 'first name' and 'last name' as different columns I am using the for loop to concatonate the first and last names together. # this ensures that the Combobox shows the names together for y in selectingNames: optionNames.append(str(y[0])+ str(y[1])) self.original_name_Options.configure(values=optionNames) #Combobox now holds the full names of students else: messagebox.showerror("Error", "There are no students to select from!") nameTable = "SELECT [First Name], [Last Name] FROM Students" cursor.execute(nameTable) def students_id(self,event): # function is used to get the selected students id getting_name = self.original_name_Options.get() connection = sqlite3.connect("Teacher.db") cursor = connection.cursor() query_student = """SELECT * from Students""" cursor.execute(query_student) selecting_rows = cursor.fetchall() for i in selecting_rows: if getting_name == str(i[0]) + " " + str(i[1]): self.student_id = i[2] print(self.student_id)#checking to see if correct id is printed def update_name(self,event): # function is called so updated name updates in the database original_name = self.original_name_Options.get() updated_fname = self.change_firstname_entry.get() updated_lname = self.change_lastname_entry.get() connection = sqlite3.connect("Teacher.db") cursor = connection.cursor() if original_name=="" or updated_fname=="" or updated_lname == "": messagebox.showerror("Error", "All fields must be entered!") else: query_students ="""UPDATE Students SET [First Name]=? , [Last Name]=? WHERE StudentID=?""" update_name = (updated_fname, updated_lname, self.student_id) cursor.execute(query_students, update_name) connection.commit() # code below is to delete a student def deleting_student_names(self): # function is called so that it deletes user from database # it deletes everything about that student (including test results etc.) self.delete_student_frame = ctk.CTkFrame(self.parent.classFrame) self.delete_student_frame.place(relx=0.7, rely=0.3) self.delete_description = ctk.CTkLabel( self.delete_student_frame, text="Delete Student", font=ctk.CTkFont(size=20, weight="bold") ) self.delete_description.grid(row=0,column=0) self.delete_student_label = ctk.CTkLabel(self.delete_student_frame, text="Delete Student:") self.delete_student_label.grid(row=1,column=0) self.delete_student_option = ctk.CTkComboBox(self.delete_student_frame, state="readonly")#, values=(self.optionNames)) self.delete_student_option.grid(row=1,column=1) self.delete_student_option.bind("<Button-1>", self.delete_student_list)#binding button to function # self.delete_student_option.bind("<<ComboboxSelected>>", self.delete_student_id)#function is called when value of combobox changes self.delete_student_btn = ctk.CTkButton(self.delete_student_frame, text="Delete") self.delete_student_btn.grid(row=2,column=1) # self.delete_student_btn.bind("<Button-1>", self.delete_student) for widget in self.delete_student_frame.winfo_children(): widget.grid_configure(padx=10, pady=10) # for loop that loops through all widgets # all widgets have same amount of padding between them def delete_student_list(self,event): # code below is used to get the student names from database connection = sqlite3.connect("Teacher.db") cursor = connection.cursor() checking_table_existence = """SELECT name from sqlite_master WHERE type='table' AND name='Students'""" cursor.execute(checking_table_existence) exists = cursor.fetchone() if exists: query_students = """SELECT [First Name], [Last Name] from Students""" cursor.execute(query_students) selectingNames = cursor.fetchall() optionNames = [] # using for loop to loop through all the names. # since my database has 'first name' and 'last name' as different columns I am using the for loop to concatonate the first and last names together. # this ensures that the Combobox shows the names together for y in selectingNames: optionNames.append(str(y[0])+ " " + str(y[1])) self.delete_student_option.configure(values=optionNames) #Combobox now holds the full names of students else: messagebox.showerror("Error", "There are no students to select from!") nameTable = "SELECT [First Name], [Last Name] FROM Students" cursor.execute(nameTable)
问题分析
核心原因是你将加载选项的逻辑绑定在了<Button-1>点击事件上:
- 首次点击下拉箭头时,下拉菜单先触发展开动作,随后才执行
names/delete_student_list方法加载选项。此时下拉菜单已经渲染完成,无法显示刚加载的内容。 - 第二次点击时,选项已经提前加载完成,所以下拉菜单能正常显示。
另外还存在一个隐藏bug:names方法中拼接名字时没有加空格(str(y[0])+str(y[1])),但students_id方法中匹配时却加了空格(str(i[0]) + " " + str(i[1])),会导致后续无法正确匹配学生ID。
解决方案
1. 初始化时预加载选项
在创建Combobox后直接调用加载方法,不需要等点击事件触发:
def updating_student_names(self): # ... 原有代码 ... self.original_name_Options = ctk.CTkComboBox(self.update_name_frame, state="readonly") # 移除<Button-1>绑定 self.original_name_Options.grid(row=6,column=1) # 初始化加载选项 self.names(None) # ... 原有代码 ... def deleting_student_names(self): # ... 原有代码 ... self.delete_student_option = ctk.CTkComboBox(self.delete_student_frame, state="readonly") self.delete_student_option.grid(row=1,column=1) # 移除<Button-1>绑定 # 初始化加载选项 self.delete_student_list(None) # ... 原有代码 ...
2. 统一名字拼接格式
修改names方法中的名字拼接逻辑,添加空格,和students_id保持一致:
def names(self,event): # ... 原有代码 ... for y in selectingNames: optionNames.append(f"{str(y[0])} {str(y[1])}") # ... 原有代码 ...
3. 数据更新后刷新选项
在保存、修改学生数据后,重新加载Combobox选项,确保数据实时同步:
def students_to_database(self,event): # ... 原有代码 ... else: # ... 原有保存代码 ... connection.commit() connection.close() # 刷新两个Combobox的选项 self.names(None) self.delete_student_list(None) def update_name(self,event): # ... 原有代码 ... else: # ... 原有更新代码 ... cursor.execute(query_students, update_name) connection.commit() connection.close() # 刷新选项 self.names(None) self.delete_student_list(None)
内容的提问来源于stack exchange,提问作者Govind
相关产品推荐
相关产品推荐

