Tkinter程序运行一段时间后无法加载新窗口问题求助
Tkinter弹窗空白问题修复方案
问题背景
开发的Tkinter GUI程序可将界面录入条目记录到Access数据库,初始运行正常,但使用Stock或Pick按钮提交操作一段时间后,确认提示等新窗口无法正常加载,仅显示空白弹窗,需重启程序才能恢复,问题涉及safetyCheckStock()和safetyCheckPick()函数。
问题根源
- 数据库连接泄漏:所有数据库操作函数创建连接后未关闭,长时间运行导致连接资源耗尽,阻塞UI线程。
- 全局窗口变量冲突:所有弹窗共用同一个全局
newWindow,弹窗创建/销毁时序异常时,会导致Tkinter无法正确渲染新窗口。 - SQL注入风险:直接拼接SQL语句,不仅存在安全隐患,还可能因特殊字符导致查询失败,间接引发UI异常。
修复步骤
1. 修复数据库连接泄漏
使用with语句自动管理连接和游标,确保每次操作后关闭连接,同时改用参数化查询避免SQL注入:
# 修改findOldQty函数 def findOldQty(): tablename = 'tblPCB_Inventory' job = retrieveJob() pcb_type = retrievePcbType() # with语句自动关闭连接和游标 with get_dbconn(r'Y:\\Inventory\\INVENTORY TABLE.mdb') as dbconn: with dbconn.cursor() as cursor: sql = f"select * from [{tablename}] where job = ? and pcb_type= ?;" cursor.execute(sql, (job, pcb_type)) rows = cursor.fetchall() return int(rows[0].Qty) if rows else None # 修改stockPCB函数的数据库操作部分 def stockPCB(): if retrieveJob() == "" or retrieveLoc() == "" or str(retrievePcbType()) == "0" or retrieveQty() == "": checkFields() return oldQty = findOldQty() tablename = 'tblPCB_Inventory' job = retrieveJob() pcb_type = retrievePcbType() qty = retrieveQty() loc = retrieveLoc() with get_dbconn(r'Y:\\Location\\INVENTORY TABLE.mdb') as dbconn: with dbconn.cursor() as cursor: if oldQty is None: sql = f"insert into [{tablename}](job, pcb_type, qty, location) values (?, ?, ?, ?);" cursor.execute(sql, (job, pcb_type, qty, loc)) stockComplete(qty) else: newQty = oldQty + int(qty) sql = f"update [{tablename}] set qty = ? where job = ? and pcb_type = ?;" cursor.execute(sql, (newQty, job, pcb_type)) stockComplete(newQty) dbconn.commit() # 修改pickPCB函数的数据库操作部分 def pickPCB(): if retrieveJob() == "" or retrieveLoc() == "" or retrievePcbType() == "" or retrieveQty() == "": checkFields() return oldQty = findOldQty() if oldQty is None: wrongJob() return newQty = oldQty - int(retrieveQty()) if newQty < 0: lowQuantity() return tablename = 'tblPCB_Inventory' job = retrieveJob() pcb_type = retrievePcbType() with get_dbconn(r'Y:\\Inventory\\INVENTORY TABLE.mdb') as dbconn: with dbconn.cursor() as cursor: sql = f"update [{tablename}] set qty = ? where job = ? and pcb_type = ?;" cursor.execute(sql, (newQty, job, pcb_type)) dbconn.commit() pickComplete()
2. 避免全局窗口变量冲突
取消全局newWindow定义,改为每个弹窗函数内部创建局部变量,通过lambda传递窗口对象到关闭函数:
# 修改closeNewWindow函数,接收窗口参数 def closeNewWindow(window, event=None): window.destroy() app.focus_set() varJob.focus_set() # 示例:修改lowQuantity函数 def lowQuantity(): newWindow = Toplevel(app) newWindow.title("Low Quantity") newWindow.geometry("400x200") newWindow.focus_set() icon = PhotoImage(file='logo.png') newWindow.iconphoto(False, icon) oldQty = findOldQty() Label(newWindow, text = f"Inventory has only {oldQty},\n pick {oldQty} or less.",font=("Arial bold", 24) ,fg="red").pack() newWindow.bind("<Return>", lambda e: closeNewWindow(newWindow)) # 其余弹窗函数(wrongJob、pickComplete、checkFields、stockComplete、safetyCheckPick、safetyCheckStock)均按此方式修改,移除全局newWindow
3. 优化UI线程响应
将数据库操作放入子线程,避免阻塞Tkinter事件循环(针对耗时操作):
import threading # 修改safetyDecisionStock函数 def safetyDecisionStock(decision): newWindow.destroy() if decision: threading.Thread(target=stockPCB, daemon=True).start() # 修改safetyDecisionPick函数 def safetyDecisionPick(decision): newWindow.destroy() if decision: threading.Thread(target=pickPCB, daemon=True).start()
额外优化建议
- 用
Entry组件替代Text组件处理单行输入,简化取值逻辑。 - 增加输入类型校验(如Quantity必须为整数),避免数据库操作报错。
- 将数据库路径、表名等配置抽离为常量,便于后续维护。
内容的提问来源于stack exchange,提问作者ZenHombre
相关产品推荐
相关产品推荐

