Tkinter按钮图片自适应问题:多按钮时首个按钮占满窗口
问题分析与解决
问题现象
编写了一个实现按钮尺寸变化时自动调整图片的函数,需求是将logo置于按钮左上角、文字居中。当主窗口仅创建一个按钮时功能正常,但创建第二个按钮后,首个按钮会占满整个主窗口并遮挡第二个按钮。
问题根源
- 布局冲突:两个按钮都使用
pack(fill=tk.BOTH, expand=tk.TRUE),该配置会让按钮试图占据所有可用空间。当首个按钮因事件循环不断调整尺寸时,会抢占全部窗口空间,导致第二个按钮被完全遮挡。 - 无限事件循环:
<Configure>事件会在按钮尺寸变化时触发,而在OnButtonResize函数中设置按钮的image和text会再次引发尺寸变化,形成无限循环,导致按钮持续膨胀。
修复方案
- 调整布局方式:改用
grid布局,让两个按钮平分窗口空间,避免空间抢占。 - 阻止无限事件循环:给每个按钮添加尺寸记录属性,仅当按钮实际尺寸发生变化时才更新图片和文字,避免重复触发
<Configure>事件。 - 优化图片叠加逻辑:确保生成的透明背景图尺寸与按钮当前尺寸完全一致,避免因图片尺寸差异导致按钮尺寸反复变化。
完整修正代码
#!/usr/bin/env python # coding: utf-8 import tkinter as tk from PIL import Image, ImageTk, ImageColor, ImageFont root = tk.Tk() root.wm_state("zoomed") # 配置grid布局,让两个按钮平分垂直空间 root.grid_rowconfigure(0, weight=1) root.grid_rowconfigure(1, weight=1) root.grid_columnconfigure(0, weight=1) 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: Image.Image, extraText: str = "") -> None: eventWidth = event.width eventHeight = event.height # 检查当前尺寸是否与上次记录一致,避免无限循环 if hasattr(event.widget, 'last_size') and event.widget.last_size == (eventWidth, eventHeight): return event.widget.last_size = (eventWidth, eventHeight) # 创建与按钮尺寸一致的透明背景图 img = Image.new("RGBA", (eventWidth, eventHeight), (0, 0, 0, 0)) # 将logo粘贴到左上角位置 img.paste(logo, (int(eventWidth/50), int(eventHeight/30))) buttonText = f"Button Width: {eventWidth}\nButton Height: {eventHeight}\n{extraText}" tkimg = ImageTk.PhotoImage(img) event.widget['text'] = buttonText event.widget['image'] = tkimg 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 MinMax(x: int) -> int: return max(0, min(x, 255)) def LightenHexColor(hexColorCode: str , percent: int = 10) -> str: inputColorTuple = Hex2Rgb(hexColorCode) ligtherColorTuple = tuple(MinMax(int(i*(100+percent)/100)) for i in inputColorTuple) 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) 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)) tkimg1 = ImageTk.PhotoImage(logo1) button1 = tk.Button(root, text=screenText, image=tkimg1, compound="c", bg=bgColorCursorOut1, font=helv32) button1.grid(row=0, column=0, sticky="nsew") button1.last_size = None # 初始化尺寸记录属性 button1.bind("<Configure>", lambda event: OnButtonResize(event, logo1, screenText)) 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) 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) button2.grid(row=1, column=0, sticky="nsew") button2.last_size = None # 初始化尺寸记录属性 button2.bind("<Configure>", lambda event: OnButtonResize(event, logo2)) button2.bind("<Enter>", lambda event: OnEnter(event, bgColorCursorIn2)) button2.bind("<Leave>", lambda event: OnLeave(event, bgColorCursorOut2)) root.mainloop()
内容的提问来源于stack exchange,提问作者Paolo Sestini
相关产品推荐
相关产品推荐

