如何阻止Tkinter Treeview中Combobox重复堆叠显示
问题描述
我在代码中参考了某Treeview列筛选方案,每次通过Treeview加载新Excel文件时,筛选用的Combobox会出现堆叠显示的问题。我仅将该方案代码复制粘贴到Ttree.insert("", "end", values=row)之后,相关代码如下:
def clear_tree(): Ttree.delete(*Ttree.get_children()) def open_file(): namefile= filedialog.askopenfilename( initialdir="C:", title="Open a File", filetype=(("xlsx files", "*.xlsx"), ("All files", "*.*")) ) namefile= r"{}".format(namefile) df = pd.read_excel(namefile) # clears the treeview clear_tree() # Set up new tree Ttree['column'] = list(df.columns) Ttree['show'] = "headings" # loop in the column list for column in Ttree['column']: Ttree.heading(column, text=column) # putting data in the treeview df_columns = df.to_numpy().tolist() # loop the data and putting them on the screen for row in df_columns: Ttree.insert("", "end", values=row) def create_column_combox(): # Creating the select field and value Combobox def populate_values_combo(e=None): # populate values combobox with unique column values state = 'normal' combo_values = [] if col_combo.get() == 'All': # if option All selected state = 'disabled' # disabling the option to select values select_value() else: combo_values = ['All'] + df[ col_combo.get()].unique().tolist() # adding option All to values col_values_combo.config(values=combo_values, state=state) def select_value(e=None): # cleaning tree and populating with df values Ttree.delete(*Ttree.get_children()) if col_combo.get() == 'All' or col_values_combo.get() == 'All': # if All selected as column # or value col_values_combo.current(0) # setting values to All [Ttree.insert("", "end", text=str(index), values=list(row)) for index, row in df.iterrows()] else: for index, row in df.loc[df[col_combo.get()].eq(col_values_combo.get())].iterrows(): Ttree.insert("", "end", text=str(index), values=list(row)) columns = ['All'] + list(df.columns) # Adding option All to list of columns column_combo_label = Label(Fframe, text='Selecionar Coluna') column_combo_label.pack() col_combo = ttk.Combobox(Fframe, values=columns, state='readonly') col_combo.pack() col_combo.bind("<<ComboboxSelected>>", populate_values_combo) volume_combo_label = Label(Fframe, text='Selecionar Valor') volume_combo_label.pack() col_values_combo = ttk.Combobox(Fframe, state='disabled') col_values_combo.pack() col_values_combo.bind("<<ComboboxSelected>>", select_value) create_column_combox()
请问每次打开新文件时先删除Combobox再重新创建能否解决堆叠问题?该如何彻底阻止它们堆叠?
解决方案
问题答案
是,先删除旧的Combobox及关联标签再重新创建,完全可以解决堆叠问题。此外还有更高效的方案可以从根源避免这个问题,以下是两种可行方案:
方案一:创建新控件前清理旧控件
每次调用create_column_combox()时,先遍历Fframe中的子组件,删除之前创建的筛选相关控件(标签、Combobox),再生成新的控件。这种方式可以精准清理旧控件,避免堆叠。
修改后的create_column_combox()函数如下:
def create_column_combox(): # Creating the select field and value Combobox # 先清理Fframe中旧的筛选控件(标签和Combobox) for widget in Fframe.winfo_children(): if isinstance(widget, (Label, ttk.Combobox)): widget.destroy() def populate_values_combo(e=None): # populate values combobox with unique column values state = 'normal' combo_values = [] if col_combo.get() == 'All': # if option All selected state = 'disabled' # disabling the option to select values select_value() else: combo_values = ['All'] + df[ col_combo.get()].unique().tolist() # adding option All to values col_values_combo.config(values=combo_values, state=state) def select_value(e=None): # cleaning tree and populating with df values Ttree.delete(*Ttree.get_children()) if col_combo.get() == 'All' or col_values_combo.get() == 'All': # if All selected as column # or value col_values_combo.current(0) # setting values to All [Ttree.insert("", "end", text=str(index), values=list(row)) for index, row in df.iterrows()] else: for index, row in df.loc[df[col_combo.get()].eq(col_values_combo.get())].iterrows(): Ttree.insert("", "end", text=str(index), values=list(row)) columns = ['All'] + list(df.columns) # Adding option All to list of columns column_combo_label = Label(Fframe, text='Selecionar Coluna') column_combo_label.pack() col_combo = ttk.Combobox(Fframe, values=columns, state='readonly') col_combo.pack() col_combo.bind("<<ComboboxSelected>>", populate_values_combo) volume_combo_label = Label(Fframe, text='Selecionar Valor') volume_combo_label.pack() col_values_combo = ttk.Combobox(Fframe, state='disabled') col_values_combo.pack() col_values_combo.bind("<<ComboboxSelected>>", select_value)
如果Fframe中还有其他无关控件,可以给筛选控件添加自定义标签,清理时只删除带标签的控件:
# 创建控件时添加标签 column_combo_label = Label(Fframe, text='Selecionar Coluna', tags="filter_widget") # ...其他控件同理添加tags="filter_widget" # 清理时 for widget in Fframe.winfo_children(): if "filter_widget" in widget.bindtags(): widget.destroy()
方案二:提前创建控件,仅更新选项值
不要每次打开文件都创建新的控件,而是在程序初始化阶段就把筛选用的标签、Combobox创建好,每次打开新文件时只更新Combobox的选项列表和状态。这种方式更高效,从根源避免了控件堆叠问题。
修改后的代码示例:
import tkinter as tk from tkinter import ttk, filedialog import pandas as pd # 全局变量声明 root = tk.Tk() Fframe = tk.Frame(root) Fframe.pack() Ttree = ttk.Treeview(Fframe) Ttree.pack() # 初始化时创建筛选控件 column_combo_label = Label(Fframe, text='Selecionar Coluna') column_combo_label.pack() col_combo = ttk.Combobox(Fframe, state='readonly') col_combo.pack() volume_combo_label = Label(Fframe, text='Selecionar Valor') volume_combo_label.pack() col_values_combo = ttk.Combobox(Fframe, state='disabled') col_values_combo.pack() df = None # 全局存储DataFrame def populate_values_combo(e=None): global df state = 'normal' combo_values = [] if col_combo.get() == 'All': state = 'disabled' select_value() else: combo_values = ['All'] + df[col_combo.get()].unique().tolist() col_values_combo.config(values=combo_values, state=state) def select_value(e=None): global df Ttree.delete(*Ttree.get_children()) if col_combo.get() == 'All' or col_values_combo.get() == 'All': col_values_combo.current(0) [Ttree.insert("", "end", text=str(index), values=list(row)) for index, row in df.iterrows()] else: for index, row in df.loc[df[col_combo.get()].eq(col_values_combo.get())].iterrows(): Ttree.insert("", "end", text=str(index), values=list(row)) # 绑定事件 col_combo.bind("<<ComboboxSelected>>", populate_values_combo) col_values_combo.bind("<<ComboboxSelected>>", select_value) def clear_tree(): Ttree.delete(*Ttree.get_children()) def open_file(): global df namefile= filedialog.askopenfilename( initialdir="C:", title="Open a File", filetype=(("xlsx files", "*.xlsx"), ("All files", "*.*")) ) if not namefile: # 用户取消选择文件时返回 return namefile= r"{}".format(namefile) df = pd.read_excel(namefile) # 刷新Treeview clear_tree() Ttree['column'] = list(df.columns) Ttree['show'] = "headings" for column in Ttree['column']: Ttree.heading(column, text=column) df_columns = df.to_numpy().tolist() for row in df_columns: Ttree.insert("", "end", values=row) # 仅更新Combobox的选项和状态 columns = ['All'] + list(df.columns) col_combo.config(values=columns) col_combo.set('All') # 重置选择为All col_values_combo.config(values=[], state='disabled') # 重置值选择框 # 添加打开文件按钮 open_btn = ttk.Button(root, text="打开文件", command=open_file) open_btn.pack() root.mainloop()
方案推荐
优先选择方案二,因为它避免了重复创建和销毁控件的开销,代码结构更清晰,也从根本上杜绝了控件堆叠的可能。
内容的提问来源于stack exchange,提问作者henry
相关产品推荐
相关产品推荐

