Tkinter按钮布局、图像报错及多按钮异常问题求助
问题描述
我想要实现Tkinter按钮的以下布局:文本居中显示,图像固定在按钮左上角,但现有代码无法实现该效果。按照建议修改代码后,出现了_tkinter.TclError: image "pyimageXX" doesn't exist的报错;添加第二个按钮后,主窗口异常,第一个按钮占满整个屏幕。以下是各阶段的相关代码及报错信息:
初始布局代码
import tkinter as tk from tkinter import messagebox from tkinter import font as tkFont from PIL import Image, ImageTk # Create the master object root = tk.Tk() # Sets the window size as "300x300" root.geometry("300x300") helv32 = tkFont.Font(family='Helvetica', size=32, weight='bold') # This is the button callback function # This must be visible to the button, so we must define it before the button widget! def buttonCallback(): messagebox.showinfo("Message", "You have clicked the Button!") # Create a tk.PhotoImage my_image = Image.open("./debian.png") # Resize it to fit button dimensions my_image = my_image.resize((25, 25), Image.Resampling.LANCZOS) # Finally, get the PhotoImage object my_image = ImageTk.PhotoImage(my_image) # Create a Button widget button = tk.Button(root, text=" Click", image=my_image, command=buttonCallback, compound=tk.LEFT, height = 300, width = 300, bg="lightblue", font=helv32, anchor=tk.NW) # Use the grid geometry manager to put the widgets in the respective position button.grid(row=1, column=0) # The application mainloop root.mainloop()
修改后报错的代码
#!/usr/bin/env python import tkinter as tk from PIL import Image, ImageTk, ImageColor root = tk.Tk() root.update_idletasks() root.wm_state("zoomed") buttonWidth = root.winfo_width() buttonHeight = root.winfo_height() screenWidth = root.winfo_screenwidth() screenHeight = root.winfo_screenheight() screenText = f'Screen Width: {screenWidth}\nScreen Height: {screenHeight}' def OnEnter(event, color: str): event.widget['background'] = color def OnLeave(event, color): event.widget['background'] = color def OnButtonResize(event, logoFile: str, imageWidth: int, imageHeight: int): buttonWidth = event.width buttonHeight = event.height # load the required image and resize it to the desired size logo = Image.open(logoFile).resize((imageWidth, imageHeight)) # create a larger transparent image (around the size of the button) img = Image.new("RGBA", (buttonWidth, buttonHeight), (0, 0, 0, 0)) # paste the small image at the top-left corner of this large image img.paste(logo) imgWidth, imgHeight = img.size buttonText = f'Button Width: {imgWidth}\nButton Height: {imgHeight}\n{screenText}' #print(buttonText) tkimg = ImageTk.PhotoImage(img) event.widget['text'] = buttonText event.widget['image'] = tkimg def Rgb2Hex(rgbTuple: tuple) -> str: return (f"#{rgbTuple[0]:02x}{rgbTuple[1]:02x}{rgbTuple[2]:02x}") def Hex2Rgb(hexcode: str) -> tuple: return(ImageColor.getcolor(hexcode, "RGB")) def clamp(x: int): return max(0, min(x, 255)) def LightenHexColor(hexColorCode: str , percent: int = 10) -> str: inputColorTuple = Hex2Rgb(hexColorCode) ligtherColorTuple = () for i in inputColorTuple: ligtherColorTuple = ligtherColorTuple + (clamp(int(i*(100+percent)/100)),) return(Rgb2Hex(ligtherColorTuple)) helv32 = ("Helvetica", 32, "bold") global logo global img global tkimg global button logoFilename = "debian.png" logoWidth = logoHeight = 300 # load the required image and resize it to the desired size logo = Image.open(logoFilename).resize((logoWidth, logoHeight)) # create a larger transparent image (around the size of the button) #img = Image.new("RGBA", (imageWidth, imageHeight), (0, 0, 0, 0)) # paste the small image at the top-left corner of this large image #img.paste(logo, (int(imageSize/2), int(imageSize/2))) tkimg = ImageTk.PhotoImage(logo) bgColorCursorOut = "#F00000" bgColorCursorIn = LightenHexColor(bgColorCursorOut) button = tk.Button(root, text=screenText, image=tkimg, compound="c", bg=bgColorCursorOut, font=helv32, width=buttonWidth, height=buttonHeight) #button = tk.Button(root, text=screenText, bg="lightblue", font=helv32, width=screenWidth, height=screenHeight) button.pack(padx=10, pady=10) button.bind("<Configure>", lambda event: OnButtonResize(event, logoFilename, logoWidth, logoHeight)) button.bind("<Enter>", lambda event: OnEnter(event, bgColorCursorIn)) button.bind("<Leave>", lambda event: OnLeave(event, bgColorCursorOut)) root.mainloop()
添加第二个按钮后异常的代码
import tkinter as tk from PIL import Image, ImageTk, ImageColor, ImageFont root = tk.Tk() root.update_idletasks() root.wm_state("zoomed") buttonWidth = root.winfo_width() buttonHeight = root.winfo_height()/2 screenWidth = root.winfo_screenwidth() screenHeight = root.winfo_screenheight() screenText = f'Screen Width: {screenWidth}\nScreen Height: {screenHeight}' def OnEnter(event, color: str): event.widget['background'] = color def OnLeave(event, color): event.widget['background'] = color def OnButtonResize(event, logo: str, imageWidth: int, imageHeight: int) -> None: buttonWidth = event.width buttonHeight = event.height #print(f"Button resized to {buttonWidth}x{buttonHeight}") # load the required image and resize it to the desired size # logo = Image.open(logoFile).resize((imageWidth, imageHeight)) # create a larger transparent image (around the size of the button) img = Image.new("RGBA", (buttonWidth, buttonHeight), (0, 0, 0, 0)) # paste the small image at the top-left corner of this large image img.paste(logo, (int(buttonWidth/50), int(buttonHeight/30))) imgWidth, imgHeight = img.size buttonText = f'Button Width: {imgWidth}\nButton Height: {imgHeight}\n{screenText}' #print(buttonText) tkimg = ImageTk.PhotoImage(img) event.widget['text'] = buttonText event.widget['image'] = tkimg event.widget.image = tkimg # This line is to make the image persistent and avoid errors due to garbage collector def Rgb2Hex(rgbTuple: tuple) -> str: return (f"#{rgbTuple[0]:02x}{rgbTuple[1]:02x}{rgbTuple[2]:02x}") def Hex2Rgb(hexcode: str) -> tuple: return(ImageColor.getcolor(hexcode, "RGB")) def clamp(x: int): return(max(0, min(x, 255))) def LightenHexColor(hexColorCode: str , percent: int = 10) -> str: inputColorTuple = Hex2Rgb(hexColorCode) ligtherColorTuple = () for i in inputColorTuple: ligtherColorTuple = ligtherColorTuple + (clamp(int(i*(100+percent)/100)),) return(Rgb2Hex(ligtherColorTuple)) def GetZoneNameImage(zoneName: str = "Z1", fontColor: str = "#000000") -> str: imageFilename = f"{zoneName}.png" font_size = 128 font_filepath = "C:/Windows/Fonts/calibrii.ttf" color = Hex2Rgb(LightenHexColor(fontColor, -25)) font = ImageFont.truetype(font_filepath, size=font_size) mask_image = font.getmask(zoneName, "L") img = Image.new("RGBA", mask_image.size) img.im.paste(color, (0, 0) + mask_image.size, mask_image) # need to use the inner `img.im.paste` due to `getmask` returning a core img.save(imageFilename, quality=100) return(imageFilename) helv32 = ("Helvetica", 32, "bold") logoWidth = 150 logoHeight = 110 bgColorCursorOut1 = "#38bab6" bgColorCursorIn1 = LightenHexColor(bgColorCursorOut1) logoFilename1 = GetZoneNameImage(zoneName="Z1", fontColor=bgColorCursorOut1) # load the required image and resize it to the desired size logo1 = Image.open(logoFilename1).resize((logoWidth, logoHeight)) tkimg1 = ImageTk.PhotoImage(logo1) bgColorCursorOut1 = "#38bab6" bgColorCursorIn1 = LightenHexColor(bgColorCursorOut1) button1 = tk.Button(root, text=screenText, image=tkimg1, compound="c", bg=bgColorCursorOut1, font=helv32)#, width=buttonWidth, height=buttonHeight) #button = tk.Button(root, text=screenText, bg="lightblue", font=helv32, width=screenWidth, height=screenHeight) #button1.pack(padx=10, pady=10) button1.pack(fill=tk.BOTH, expand=tk.TRUE) button1.bind("<Configure>", lambda event: OnButtonResize(event, logo1, logoWidth, logoHeight)) button1.bind("<Enter>", lambda event: OnEnter(event, bgColorCursorIn1)) button1.bind("<Leave>", lambda event: OnLeave(event, bgColorCursorOut1)) bgColorCursorOut2 = "#FCBA03" bgColorCursorIn2 = LightenHexColor(bgColorCursorOut2) logoFilename2 = GetZoneNameImage(zoneName="Z2", fontColor=bgColorCursorOut2) # load the required image and resize it to the desired size logo2 = Image.open(logoFilename2).resize((logoWidth, logoHeight)) tkimg2 = ImageTk.PhotoImage(logo2) button2 = tk.Button(root, text=screenText, image=tkimg2, compound="c", bg=bgColorCursorOut2, font=helv32)#, width=buttonWidth, height=buttonHeight) #button = tk.Button(root, text=screenText, bg="lightblue", font=helv32, width=screenWidth, height=screenHeight) #button2.pack(padx=10, pady=10) button2.pack(fill=tk.BOTH, expand=tk.TRUE) button2.bind("<Configure>", lambda event: OnButtonResize(event, logo2, logoWidth, logoHeight)) button2.bind("<Enter>", lambda event: OnEnter(event, bgColorCursorIn2)) button2.bind("<Leave>", lambda event: OnLeave(event, bgColorCursorOut2)) root.mainloop()
报错信息
Exception in Tkinter callback Traceback (most recent call last): File "E:\Programs\Anaconda3\lib\tkinter\__init__.py", line 1921, in __call__ return self.func(*args) File "C:\Users\psestini\AppData\Local\Temp\ipykernel_40344\3920381195.py", line 27, in <lambda> button.bind("<Enter>", lambda event: OnEnter(event, bgColorCursorIn)) File "C:\Users\psestini\AppData\Local\Temp\ipykernel_40344\1042462112.py", line 2, in OnEnter event.widget['background'] = color File "E:\Programs\Anaconda3\lib\tkinter\__init__.py", line 1686, in __setitem__ self.configure({key: value}) File "E:\Programs\Anaconda3\lib\tkinter\__init__.py", line 1675, in configure return self._configure('configure', cnf, kw) File "E:\Programs\Anaconda3\lib\tkinter\__init__.py", line 1665, in _configure self.tk.call(_flatten((self._w, cmd)) + self._options(cnf)) _tkinter.TclError: image "pyimage55" doesn't exist
解决方案
1. 核心问题分析
- 图像不存在报错:Python垃圾回收机制回收了
ImageTk.PhotoImage对象,导致Tkinter无法找到图像引用。 - 布局异常:窗口未完全初始化时获取宽高数值不准确,加上
pack布局参数配置不当,导致按钮占满屏幕。 - 布局需求未实现:错误使用
compound参数,未正确分离图像和文本的布局位置。
2. 修正后的完整代码
import tkinter as tk from PIL import Image, ImageTk, ImageColor, ImageFont root = tk.Tk() root.wm_state("zoomed") screenWidth = root.winfo_screenwidth() screenHeight = root.winfo_screenheight() screenText = f'Screen Width: {screenWidth}\nScreen Height: {screenHeight}' def OnEnter(event, color): event.widget['background'] = color def OnLeave(event, color): event.widget['background'] = color def OnButtonResize(event, logo, imageWidth, imageHeight): buttonWidth = event.width buttonHeight = event.height # 创建透明画布,将logo固定在左上角(留10px边距) img = Image.new("RGBA", (buttonWidth, buttonHeight), (0, 0, 0, 0)) img.paste(logo, (10, 10)) tkimg = ImageTk.PhotoImage(img) event.widget['image'] = tkimg event.widget.image = tkimg # 绑定到按钮属性,防止被垃圾回收 event.widget['text'] = f'Button Width: {buttonWidth}\nButton Height: {buttonHeight}\n{screenText}' def Rgb2Hex(rgbTuple): return f"#{rgbTuple[0]:02x}{rgbTuple[1]:02x}{rgbTuple[2]:02x}" def Hex2Rgb(hexcode): return ImageColor.getcolor(hexcode, "RGB") def clamp(x): return max(0, min(x, 255)) def LightenHexColor(hexColorCode, percent=10): inputColorTuple = Hex2Rgb(hexColorCode) ligtherColorTuple = tuple(clamp(int(i*(100+percent)/100)) for i in inputColorTuple) return Rgb2Hex(ligtherColorTuple) def GetZoneNameImage(zoneName="Z1", fontColor="#000000"): imageFilename = f"{zoneName}.png" font_size = 128 font_filepath = "C:/Windows/Fonts/calibrii.ttf" color = Hex2Rgb(LightenHexColor(fontColor, -25)) font = ImageFont.truetype(font_filepath, size=font_size) mask_image = font.getmask(zoneName, "L") img = Image.new("RGBA", mask_image.size) img.im.paste(color, (0, 0) + mask_image.size, mask_image) img.save(imageFilename, quality=100) return imageFilename helv32 = ("Helvetica", 32, "bold") logoWidth = 150 logoHeight = 110 # 第一个按钮 bgColorCursorOut1 = "#38bab6" bgColorCursorIn1 = LightenHexColor(bgColorCursorOut1) logoFilename1 = GetZoneNameImage(zoneName="Z1", fontColor=bgColorCursorOut1) logo1 = Image.open(logoFilename1).resize((logoWidth, logoHeight)) button1 = tk.Button(root, text=screenText, image=None, compound=tk.CENTER, bg=bgColorCursorOut1, font=helv32, justify=tk.CENTER) button1.pack(fill
相关产品推荐
相关产品推荐

