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

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
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:00:58