如何解决Tkinter打开新窗口时的pyimage不存在错误
问题
使用Tkinter开发多窗口Python程序,为每个窗口设置图片页眉。第一个窗口图片显示正常,但点击“Explore Flights”按钮打开第二个窗口时,图片无法加载,报错:
Exception in Tkinter callback Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/__init__.py", line 1921, in __call__ return self.func(*args) File "<ipython-input-3-840b8d9f47f9>", line 114, in flights_menu bg_label = tk.Label(flights_men_win, image=bg_image) File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/__init__.py", line 3177, in __init__ Widget.__init__(self, master, 'label', cnf, kw) File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/__init__.py", line 2601, in __init__ self.tk.call( _tkinter.TclError: image "pyimage14" doesn't exist
试过用PIL导入图片但未解决,怀疑是图片被垃圾回收导致错误。
完整代码
import tkinter as tk from PIL import Image, ImageTk from tkinter import Label, messagebox, PhotoImage, ttk class Application: def __init__(self, main_window): self.main_window = main_window self.main_window.resizable(False, False) ## Login Window self.main_window.title("Login") self.main_window.geometry("300x150") self.main_window.iconbitmap('login_icon.png') self.label_usrname = tk.Label(self.main_window, text = "Username") self.label_passwrd = tk.Label(self.main_window, text = "Password") self.entry_usrname = tk.Entry(self.main_window) self.entry_passwrd = tk.Entry(self.main_window, show="*") self.login_button = tk.Button(self.main_window, text = "Login", command=self.login_button_clicked) self.exit_button = tk.Button(self.main_window, text = "Exit", command=self.main_window.quit()) self.label_usrname.grid(row=0, sticky='e', padx=10, pady=10) self.label_passwrd.grid(row=1, sticky='e', padx=10, pady=10) self.entry_usrname.grid(row=0, column=1, padx=10, pady=10) self.entry_passwrd.grid(row=1, column=1, padx=10, pady=10) self.login_button.grid(row=2, column=0, padx=10, pady=10) self.exit_button.grid(row=2, column=1, padx=10, pady=10) def login_button_clicked(self): username = self.entry_usrname.get() password = self.entry_passwrd.get() if username == "admin" and password == "password": self.main_window.destroy() self.agency_window() else: messagebox.showerror("Login Error", "Please enter the correct username and password") def agency_window(self): agency_win = tk.Tk() # Set the title and size of the main window agency_win.title("Prog2 Travel Agency") agency_win.geometry("600x280") agency_win.configure(bg="white") agency_win.resizable(False,False) img = tk.Image("photo", file="agency_icon.png") agency_win.iconphoto(True, img) #agency_win.tk.call('wm','iconphoto', root._w, img) # Background image (You can replace 'background.jpg' with your actual image path) bg_image = tk.PhotoImage(file="agency.png") bg_label = tk.Label(agency_win, image=bg_image) bg_label.pack(fill=tk.X) # Welcome message label welcome_label = tk.Label(agency_win, text="Hi Davey, welcome to the Prog2 Travel Agency", bg="white", fg="DeepSkyBlue2", font=("Arial", 20, "bold"), pady=20) welcome_label.pack(fill=tk.X) # Button styles using ttk (themed tk) style = ttk.Style() layout = style.layout('TButton') style.layout('Blue.TButton', layout) style.configure("Blue.TButton", font=("Arial", 20, "bold"), padding=5, background="DeepSkyBlue2", foreground="white") # Frame to hold buttons button_frame = tk.Frame(agency_win, background="DeepSkyBlue2") button_frame.pack(side=tk.BOTTOM, fill=tk.X) # Explore Flights button explore_flights_btn = ttk.Button(button_frame, text="Explore Flights", command=self.flights_menu) explore_flights_btn.grid(row=0, column=0, padx=5) # Explore Destinations button explore_destinations_btn = ttk.Button(button_frame, text="Explore Destinations", command=self.destinations_menu) explore_destinations_btn.grid(row=0, column=1, padx=5) # Book a Trip button book_trip_btn = ttk.Button(button_frame, text="Book a Trip", command=self.trips_menu) book_trip_btn.grid(row=0, column=2, padx=5) # Exit button exit_btn = ttk.Button(button_frame, text="Exit", command= agency_win.quit()) exit_btn.grid(row=0, column=3, padx=5) agency_win.mainloop() ### FLIGHTS MENU def flights_menu(self): flights_men_win = tk.Tk() # Set the title and size of the main window flights_men_win.title("Explore Flights") flights_men_win.geometry("600x400") # Adjusted the size to fit the design flights_men_win.configure(bg="white") flights_men_win.resizable(False, False) # Header image bg_image = tk.PhotoImage(file="flight.png") bg_label = tk.Label(flights_men_win, image=bg_image) bg_label.pack(fill=tk.X) # Welcome message label welcome_label = Label(flights_men_win, text="Hi Davey, welcome to the Flights section", bg="white", fg="DeepSkyBlue2", font=("Arial", 18)) welcome_label.place(x=140, y=70) # Button Frame button_frame = tk.Frame(flights_men_win, bg="white") button_frame.place(x=200, y=220) # Buttons inside the frame view_all_flights_btn = ttk.Button(button_frame, text="View All Flights", command=self.view_flights) view_all_flights_btn.grid(row=0, column=0, padx=5, pady=5) view_flights_by_country_btn = ttk.Button(button_frame, text="View Flights by Country", command=self.view_flights_filtered) view_flights_by_country_btn.grid(row=0, column=1, padx=5, pady=5) add_flight_btn = ttk.Button(button_frame, text="Add Flight", command=self.add_flight) add_flight_btn.grid(row=0, column=2, padx=5, pady=5) remove_flight_btn = ttk.Button(button_frame, text="Remove Flight", command=self.remove_flight) remove_flight_btn.grid(row=1, column=0, padx=5, pady=5) close_btn = ttk.Button(button_frame, text="Close", command=flights_men_win.destroy) # Using destroy to close the window close_btn.grid(row=1, column=1, padx=5, pady=5, columnspan=2) # Spanning two columns for the close button flights_men_win.mainloop() def view_flights(self): pass def view_flights_filtered(self): pass def add_flight(self): pass def remove_flight(self): pass
解决方案
问题根源有两个:
- 多Tk实例冲突:Tkinter程序应仅存在一个
tk.Tk()主窗口实例,后续窗口需用tk.Toplevel()创建,否则会导致Tcl解释器资源冲突,引发图片ID错误。 - 图片对象被垃圾回收:
PhotoImage对象如果没有持久引用,会被Python垃圾回收机制销毁,导致Tkinter无法找到图片资源。
修改步骤:
1. 替换多窗口创建方式
将flights_menu中的tk.Tk()改为tk.Toplevel(),并移除多余的mainloop()调用:
def flights_menu(self): # 用Toplevel创建子窗口,而非新的Tk实例 flights_men_win = tk.Toplevel() flights_men_win.title("Explore Flights") flights_men_win.geometry("600x400") flights_men_win.configure(bg="white") flights_men_win.resizable(False, False) # Header image bg_image = tk.PhotoImage(file="flight.png") bg_label = tk.Label(flights_men_win, image=bg_image) # 保留图片引用,避免被GC回收 bg_label.image = bg_image bg_label.pack(fill=tk.X) # ... 其余代码不变 ... # 移除该行,主窗口已在运行mainloop # flights_men_win.mainloop()
2. 保留图片对象引用
在所有创建PhotoImage的位置,将图片对象绑定到对应Label的属性上:
- 在
agency_window中:
bg_image = tk.PhotoImage(file="agency.png") bg_label = tk.Label(agency_win, image=bg_image) # 添加该行保留引用 bg_label.image = bg_image bg_label.pack(fill=tk.X)
3. 修正按钮command参数错误
代码中部分按钮的command直接调用了quit(),会导致程序启动时就执行退出操作,需改为传递函数引用:
# 原代码 self.exit_button = tk.Button(self.main_window, text = "Exit", command=self.main_window.quit()) # 修改后 self.exit_button = tk.Button(self.main_window, text = "Exit", command=self.main_window.quit) # 原代码 exit_btn = ttk.Button(button_frame, text="Exit", command= agency_win.quit()) # 修改后 exit_btn = ttk.Button(button_frame, text="Exit", command= agency_win.quit)
总结
通过以上修改,既解决了多窗口Tcl解释器冲突问题,又避免了图片对象被垃圾回收,可彻底解决图片加载失败的错误。
内容的提问来源于stack exchange,提问作者kenzie
相关产品推荐
相关产品推荐

