You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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>点击事件上:

  1. 首次点击下拉箭头时,下拉菜单先触发展开动作,随后才执行names/delete_student_list方法加载选项。此时下拉菜单已经渲染完成,无法显示刚加载的内容。
  2. 第二次点击时,选项已经提前加载完成,所以下拉菜单能正常显示。

另外还存在一个隐藏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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 09:15:55