You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter按钮图片自适应问题:多按钮时首个按钮占满窗口

问题分析与解决

问题现象

编写了一个实现按钮尺寸变化时自动调整图片的函数,需求是将logo置于按钮左上角、文字居中。当主窗口仅创建一个按钮时功能正常,但创建第二个按钮后,首个按钮会占满整个主窗口并遮挡第二个按钮。

问题根源

  1. 布局冲突:两个按钮都使用pack(fill=tk.BOTH, expand=tk.TRUE),该配置会让按钮试图占据所有可用空间。当首个按钮因事件循环不断调整尺寸时,会抢占全部窗口空间,导致第二个按钮被完全遮挡。
  2. 无限事件循环:<Configure>事件会在按钮尺寸变化时触发,而在OnButtonResize函数中设置按钮的image和text会再次引发尺寸变化,形成无限循环,导致按钮持续膨胀。

修复方案

  1. 调整布局方式:改用grid布局,让两个按钮平分窗口空间,避免空间抢占。
  2. 阻止无限事件循环:给每个按钮添加尺寸记录属性,仅当按钮实际尺寸发生变化时才更新图片和文字,避免重复触发<Configure>事件。
  3. 优化图片叠加逻辑:确保生成的透明背景图尺寸与按钮当前尺寸完全一致,避免因图片尺寸差异导致按钮尺寸反复变化。

完整修正代码

#!/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:39:58