Tkinter TopLevel窗口删除循环创建按钮后重进出现重复按钮bug求解
问题描述
我创建了一个主窗口,可从主窗口进入TopLevel子窗口,子窗口会根据数据库数据循环创建按钮,默认示例会生成名称为Test1、Test2、Test3、Test4、Test5的5个按钮。
点击任意按钮后会显示类似0/4的计数器,点击「Pay selected」按钮后计数器会变为1/4,以此类推。当计数器达到对应总期数时,该按钮会被删除,此阶段运行正常。但如果我退出TopLevel窗口再从主窗口重新进入,被删除按钮旁边的按钮会占据已删除按钮的位置,同时还保留自身原有位置,最终按钮数量看起来和删除前一致,但出现重复名称的按钮。
我单独写了简化版代码可以正常运行没有这个bug,但主代码的bug一直找不到,下面附上两份代码。
主代码
from tkinter import * import sqlite3 conn = sqlite3.connect('Financije.db') c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS PRIMANJA ([ID] INTEGER PRIMARY KEY,[Opis] text, [Iznos] integer, [Datum] text)''') c.execute('''CREATE TABLE IF NOT EXISTS OTPLATA_NA_RATE ([ID] INTEGER PRIMARY KEY,[Opis] text, [Iznos_rate] integer, [Broj_rata] text, [Datum_kupnje] text)''') c.execute('''CREATE TABLE IF NOT EXISTS SKIDANJE_S_RAČUNA ([ID] INTEGER PRIMARY KEY,[Opis] text, [Iznos] integer, [Date] text)''') c.execute('''CREATE TABLE IF NOT EXISTS KASICA ([ID] INTEGER PRIMARY KEY, [Stanje_računa] integer)''') c.execute('SELECT * FROM KASICA') check = c.fetchone() if check is None: c.execute('INSERT INTO KASICA VALUES (?, ?);', (None, 0.0)) c.execute('SELECT * FROM OTPLATA_NA_RATE') check = c.fetchone() if check is None: c.execute('INSERT INTO OTPLATA_NA_RATE VALUES (?, ?, ?, ?, ?);', (None, "Test1", 600, "0/2", "12/08/2021")) c.execute('INSERT INTO OTPLATA_NA_RATE VALUES (?, ?, ?, ?, ?);', (None, "Test2", 600, "0/3", "12/08/2021")) c.execute('INSERT INTO OTPLATA_NA_RATE VALUES (?, ?, ?, ?, ?);', (None, "Test3", 600, "0/4", "12/08/2021")) c.execute('INSERT INTO OTPLATA_NA_RATE VALUES (?, ?, ?, ?, ?);', (None, "Test4", 600, "0/5", "12/08/2021")) c.execute('INSERT INTO OTPLATA_NA_RATE VALUES (?, ?, ?, ?, ?);', (None, "Test5", 600, "0/6", "12/08/2021")) # Save (commit) the changes conn.commit() class FinancialCalc: def __init__(self, master): self.lista_opisa_rata = list() self.dict_for_buttons = dict() # Start screen define: self.master = master self.master.title("Test") self.master.geometry("300x300") self.master.resizable(False, False) self.otplata_na_rate_button = Button(master, text="payment in installments", command=self.otplata_na_rate_deiconify) self.otplata_na_rate_button.place(x=95, y=155) # Otplata_na_rate TopLevel - definiranje self.onr_top = Toplevel() self.onr_top.withdraw() self.onr_top.protocol("WM_DELETE_WINDOW", lambda: (self.master.deiconify(), self.onr_top.withdraw())) self.onr_top.title("Test") self.onr_label = Label(self.onr_top, text="List of payments:", font=("Helvetica", 11, "underline", "bold")) self.onr_opis_label = Label(self.onr_top, text="Payment description:", font=("Helvetica", 11, "underline", "bold")) self.onr_oznacena_rata_label = Label(self.onr_top, text="Installment description:", font=("Helvetica", 11)) self.onr_broj_rata_label = Label(self.onr_top, text="Installments:", font=("Helvetica", 11)) self.onr_iznos_rate_label = Label(self.onr_top, text="Value:", font=("Helvetica", 11)) self.onr_datum_isplate_label = Label(self.onr_top, text="Final payment:", font=("Helvetica", 11)) self.onr_oznacena_rata_txt = Label(self.onr_top, text="", font=("Helvetica", 11)) self.onr_broj_rata_txt = Label(self.onr_top, text="", font=("Helvetica", 11)) self.onr_iznos_rate_txt = Label(self.onr_top, text="", font=("Helvetica", 11)) self.onr_datum_isplate_txt = Label(self.onr_top, text="", font=("Helvetica", 11)) self.onr_pl_pojed_btn = Button(self.onr_top, text="Pay selected", command=self.pay_selected) def pay_selected(self): p_description = self.onr_oznacena_rata_txt.cget("text") num_of_installments = self.onr_broj_rata_txt.cget("text").split("/") if int(num_of_installments[0])+1 >= int(num_of_installments[1]): c.execute('DELETE FROM OTPLATA_NA_RATE WHERE Opis = ?', (p_description,)) conn.commit() btn_to_del = self.dict_for_buttons[p_description] btn_to_del.destroy() self.dict_for_buttons.pop(p_description) self.lista_opisa_rata.clear() else: target_3_novo = str(int(num_of_installments[0])+1) + "/" + num_of_installments[1] c.execute('UPDATE OTPLATA_NA_RATE SET Broj_rata = ? WHERE Opis = ?', (target_3_novo, p_description,)) conn.commit() self.get_description(p_description) def get_description(self, opis): c.execute('SELECT * FROM OTPLATA_NA_RATE WHERE Opis=?;', (opis,)) for x in c: dodaj_godinu = 0 self.onr_oznacena_rata_txt.config(text=x[1]) self.onr_iznos_rate_txt.config(text=str(x[2]) + " kn") self.onr_broj_rata_txt.config(text=x[3]) datum_racunanje = int(x[4].split("/")[1]) + int(x[3].split("/")[1]) while datum_racunanje > 12: datum_racunanje -= 12 dodaj_godinu += 1 datum_isplate = str(datum_racunanje) + "/" + str(int(x[4].split("/")[2])+dodaj_godinu) self.onr_datum_isplate_txt.config(text=datum_isplate) def otplata_na_rate_deiconify(self): # Otplata_na_rate TopLevel - define window: self.master.withdraw() self.onr_top.deiconify() self.onr_label.grid(row=0, columnspan=3, sticky="NESW") self.onr_opis_label.grid(row=0, column=5, sticky="NESW") self.onr_oznacena_rata_label.grid(row=1, column=4, padx=5, sticky="E") self.onr_broj_rata_label.grid(row=2, column=4, padx=5, sticky="E") self.onr_iznos_rate_label.grid(row=3, column=4, padx=5, sticky="E") self.onr_datum_isplate_label.grid(row=4, column=4, padx=5, sticky="E") self.onr_oznacena_rata_txt.grid(row=1, column=5, padx=5, sticky="NSWE") self.onr_broj_rata_txt.grid(row=2, column=5, padx=5, sticky="NSWE") self.onr_iznos_rate_txt.grid(row=3, column=5, padx=5, sticky="NSWE") self.onr_datum_isplate_txt.grid(row=4, column=5, padx=5, sticky="NSWE") c.execute('SELECT Opis FROM OTPLATA_NA_RATE') for opis in c: self.lista_opisa_rata.append(opis[0]) column = 0 row = 1 for opis in self.lista_opisa_rata: # Append function to button function = lambda x=opis: self.get_description(x) rata_button = Button(self.onr_top, text=opis, width=10, command=function) rata_button.grid(row=row, column=column, padx=5, pady=5) self.dict_for_buttons[opis] = rata_button column += 1 if column == 3: row += 1 column = 0 # Define placement of Button widget - created with loop if row < 5: row = 5 if row >= 5: row += 1 self.onr_pl_pojed_btn.grid(row=row, column=5, padx=2, pady=5) x = self.master.winfo_x() y = self.master.winfo_y() self.onr_top.geometry(f"+{x}+{y}") root = Tk() my_gui = FinancialCalc(root) root.mainloop()
简化测试代码
from tkinter import * root = Tk() toplvl = Toplevel() toplvl.withdraw() toplvl.protocol("WM_DELETE_WINDOW", lambda: (root.deiconify(), toplvl.withdraw())) button_id = dict() def btn_destroy(): picked_btn = str(lbl_picked_btn.cget("text").split(":")[1]).rstrip() if picked_btn != "": txt = int(lbl.cget("text").split("/")[0]) + 1 lbl.config(text=str(txt) + "/3") if txt == 3: btn_to_del = button_id[int(picked_btn)] btn_to_del.destroy() lbl.config(text="0/3") def btn_pick(x): lbl_picked_btn.config(text="Button picked:"+str(x)) lbl_desc = Label(toplvl, text="Pick button and click Destroy 3 times to destroy it", font=("Helvetica", 11)) lbl_desc.grid(row=0, columnspan=4) lbl_picked_btn = Label(toplvl, text="Button picked:", font=("Helvetica", 11)) lbl_picked_btn.grid(row=1, column=1, columnspan=2) btn_d = Button(toplvl, width=10, text="Destroy", command=btn_destroy) btn_d.grid(row=1, column=0) lbl = Label(toplvl, text="0/3", font=("Helvetica", 11)) lbl.grid(row=1, column=3) row = 2 column = 0 for i in range(5): function = lambda x=i: btn_pick(x) btn = Button(toplvl, width=10, text=str(i), command=function) btn.grid(row=row, column=column) button_id[i] = btn column += 1 def top(): root.withdraw() toplvl.deiconify() start_btn = Button(root, text="Start", command=top) start_btn.pack() root.mainloop()
修复方案
核心问题原因
- 每次打开子窗口的时候,都没有先销毁上一次生成的旧按钮,也没有清空存储按钮的字典和列表,导致旧按钮残留和新创建的按钮叠加显示。
- 只有删除按钮的时候才清空
lista_opisa_rata列表,正常退出子窗口再进入时,会把上一次的列表数据和新从数据库读取的数据叠加,导致重复创建按钮。
具体修改步骤
- 修改
otplata_na_rate_deiconify方法,在读取数据库创建新按钮之前,先销毁所有旧按钮、清空存储结构:
def otplata_na_rate_deiconify(self): # 先销毁所有旧的按钮 for btn in self.dict_for_buttons.values(): btn.destroy() # 清空存储结构 self.dict_for_buttons.clear() self.lista_opisa_rata.clear() # 后面保留你原来的代码不变 self.master.withdraw() self.onr_top.deiconify() # ... 剩下的原有代码不变
- 删除
pay_selected方法里多余的清空逻辑,把self.lista_opisa_rata.clear()这行删掉即可,不需要在这里清空列表,统一在打开子窗口的时候处理即可。
修改完成后再测试,重复按钮的问题就会消失,每次打开子窗口都会按照最新的数据库数据生成按钮。
内容的提问来源于stack exchange,提问作者Freezy
相关产品推荐
相关产品推荐

