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

Tkinter自定义类中按钮无法显示图片的问题求助

问题

使用Python3 + Tkinter构建界面时,Window1包含三列控件:中间列布局独特,左右列布局一致仅内容不同。为实现复用,用Column类封装左右列,但Column类内的按钮无法显示图标,Window1内的按钮却正常显示。已尝试给图片绑定self但无效,修改Column类结构虽能解决问题,但会破坏列内控件的并排布局。需在保留现有类结构的前提下解决图标不显示的问题。

简化代码如下:

import tkinter as tk
from tkinter import ttk

"""define basics"""
class app(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)

        self.state('zoomed') #open app in an expanded window
        #distribute 3 columns evenly
        for i in range(3):
            self.grid_columnconfigure(i, weight=1)

        #create a frame where more windows can be stacked if needed
        containerFrame = tk.Frame(self)
        containerFrame.grid()
        self.frames = {}

        #create window to place widgets
        frame = Window1(parent=containerFrame, window = self)
        self.frames["window1"] = frame #store frame in dict
        frame.grid(row=0, column=0, sticky="n") #place frame on the app window

"""define the window"""
class Window1(tk.Frame):

    def __init__(self, parent, window):
        tk.Frame.__init__(self, parent)  # create working frame

        columnLeft = Column(window) # create left column
        columnLeft.frame.grid(row=0, column=0) # place frame of left column
        columnLeft.place() # place widgets of the column

        columnRight = Column(window) # create left column
        columnRight.frame.grid(row=0, column=2) # place frame of left column
        columnRight.place() # place widgets of the column

        # this button is displayed with an icon
        self.iconDetail1 = (tk.PhotoImage(file=r"icons/detail.png")).subsample(10, 10)
        self.button1 = ttk.Button(window, image=self.iconDetail1, command=lambda: print("Yes"))
        self.button1.grid(row=0, column=1)


"""define the columns"""
class Column():

    def __init__(self, root):
        self.frame = tk.Frame()  # create frame for placing widgets
        for i in range(3): # main column consists of 3 sub columns
            self.frame.grid_columnconfigure(i, weight=1)

        """define widgets"""
        #this button is placed right, but without an icon
        self.iconFolder2 = (tk.PhotoImage(file=r"icons/folder.png")).subsample(4, 4) #get image
        self.button2 = ttk.Button(self.frame, image=self.iconFolder2, command=lambda: print("no....")) #create button
        self.text = tk.Label(self.frame, text= "show text received from button")
    
    def place(self): #method to place widgets independently from creating an instance
        self.button2.grid(row=0, column=0, sticky=tk.W)
        self.text.grid(row=0, column=2, sticky=tk.E)

if __name__ == "__main__":
    app = app()
    app.mainloop()
解决方案

问题根源

Tkinter的PhotoImage对象会被Python垃圾回收机制回收:

  • Window1内的图标绑定在类实例属性self.iconDetail1上,Window1实例存活期间该属性不会被回收,因此按钮图标正常显示。
  • Column类的self.iconFolder2虽绑定了图片,但Window1中创建的Column实例是局部变量(columnLeft/columnRight),__init__方法执行完毕后,这些局部变量会被销毁,Column实例被回收,连带PhotoImage也被回收,导致按钮无图标。
  • 此外,Column类的frame初始化时未指定父容器,默认绑定到主窗口,存在布局混乱的潜在问题。

修复步骤

  1. 在Window1中,将Column实例赋值给self的属性,让Window1实例持有Column实例,避免其被垃圾回收。
  2. 给Column类的frame指定父容器为传入的root,确保布局层级正确。

修改后的代码

import tkinter as tk
from tkinter import ttk

"""define basics"""
class app(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)

        self.state('zoomed') #open app in an expanded window
        #distribute 3 columns evenly
        for i in range(3):
            self.grid_columnconfigure(i, weight=1)

        #create a frame where more windows can be stacked if needed
        containerFrame = tk.Frame(self)
        containerFrame.grid()
        self.frames = {}

        #create window to place widgets
        frame = Window1(parent=containerFrame, window = self)
        self.frames["window1"] = frame #store frame in dict
        frame.grid(row=0, column=0, sticky="n") #place frame on the app window

"""define the window"""
class Window1(tk.Frame):

    def __init__(self, parent, window):
        tk.Frame.__init__(self, parent)  # create working frame

        # 修改:将Column实例赋值给self的属性,避免被垃圾回收
        self.columnLeft = Column(window) # create left column
        self.columnLeft.frame.grid(row=0, column=0) # place frame of left column
        self.columnLeft.place() # place widgets of the column

        self.columnRight = Column(window) # create right column
        self.columnRight.frame.grid(row=0, column=2) # place frame of right column
        self.columnRight.place() # place widgets of the column

        # this button is displayed with an icon
        self.iconDetail1 = (tk.PhotoImage(file=r"icons/detail.png")).subsample(10, 10)
        self.button1 = ttk.Button(window, image=self.iconDetail1, command=lambda: print("Yes"))
        self.button1.grid(row=0, column=1)


"""define the columns"""
class Column():

    def __init__(self, root):
        # 修改:指定frame的父容器为传入的root,确保布局层级正确
        self.frame = tk.Frame(root)  # create frame for placing widgets
        for i in range(3): # main column consists of 3 sub columns
            self.frame.grid_columnconfigure(i, weight=1)

        """define widgets"""
        #this button is placed right, but without an icon
        self.iconFolder2 = (tk.PhotoImage(file=r"icons/folder.png")).subsample(4, 4) #get image
        self.button2 = ttk.Button(self.frame, image=self.iconFolder2, command=lambda: print("no....")) #create button
        self.text = tk.Label(self.frame, text= "show text received from button")
    
    def place(self): #method to place widgets independently from creating an instance
        self.button2.grid(row=0, column=0, sticky=tk.W)
        self.text.grid(row=0, column=2, sticky=tk.E)

if __name__ == "__main__":
    app = app()
    app.mainloop()

内容的提问来源于stack exchange,提问作者53B45T14N

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:15:55