python - Why is Tkinter Button unpressable? (No errors raised, not disabled) - Stack Overflow

admin2025-04-19  0

I was working with Tkinter in OOP and I came across an odd bug: the button here goes completely unresponsive, despite being fully active and the mainloop running.

Code:

import tkinter as tk

def main():
    global root, button
    root = tk.Tk()
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                     image=pixel,
                     compound="c")

    button.pack()

main()
root.mainloop()

I used the hidden image as I need to set the specific pixel height and width of the button, but when I remove the image=pixel the button works fine.

This button is unpressable, both with mouse and with tab+enter:

I was working with Tkinter in OOP and I came across an odd bug: the button here goes completely unresponsive, despite being fully active and the mainloop running.

Code:

import tkinter as tk

def main():
    global root, button
    root = tk.Tk()
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                     image=pixel,
                     compound="c")

    button.pack()

main()
root.mainloop()

I used the hidden image as I need to set the specific pixel height and width of the button, but when I remove the image=pixel the button works fine.

This button is unpressable, both with mouse and with tab+enter:

Share edited Mar 6 at 10:18 iglebov 2471 silver badge11 bronze badges asked Mar 4 at 3:43 User 12692182User 12692182 1,0017 silver badges18 bronze badges 0
Add a comment  | 

4 Answers 4

Reset to default 2

The bug here is subtle, but it happens because the image pixel gets garbage collected by python when the main function finishes. The button no longer works if pixel doesn't exist (Not entirely sure why, likely due to tkinter internals), but never raises an error or tells you it's broken.

The solution is to save pixel in memory anywhere, so it never gets GCed or deleted:

import tkinter as tk

def main():
    global root, button, pixel
    root = tk.Tk()
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                     image=pixel,
                     compound="c")

    button.pack()

main()
root.mainloop()

If you want to use OOP, use class:

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        # use attribute to store the reference of the image
        # to avoid from garbage collection
        self.pixel = tk.PhotoImage(width=1, height=1)
        self.button = tk.Button(self, text='Hmm', image=self.pixel, compound='c')
        self.button.pack()

App().mainloop()

TkInter Button Unpressable - No Errors Raised, Not Disabled

@acw1668 emphasizes that globals like pixel, button, and root are not necessary.

I observed that you placed root.mainloop() outside of a function. This is due to the fact that you initialized tk.Tk() within a function. Consequently, the widget is not responsive.

Place tk.tk() outside of the function.

The problem can be fixed.

Include a button.picture = button before button.pack().

Code (no minor alterations):

import tkinter as tk


root = tk.Tk() 
def main():
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                     image=pixel,
                     compound="c")
    
    button.image = pixel
    button.pack()

main()
root.mainloop()

Problem with the image

Most likely, there is an error, but this error is not caught by the tkinter wrapper. As you find out, the image object is garbage-collected. The image is no longer in the image list for your root.

If we try to change some button option using configure, we will get the error _tkinter.TclError: image "pyimage1" doesn't exist.

import tkinter as tk


def main():
    global root, button
    root = tk.Tk()
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                       image=pixel,
                       compound="c")

    button.pack()
    print("Before:", root.tk.call("image", "names"))


main()

print("After:", root.tk.call("image", "names"))

# _tkinter.TclError: image "pyimage1" doesn't exist
button.configure(text="New")

root.mainloop()

Output:

Before: ('pyimage1', '::tk::icons::information', '::tk::icons::error', '::tk::icons::warning', '::tk::icons::question')
After: ('::tk::icons::information', '::tk::icons::error', '::tk::icons::warning', '::tk::icons::question')
Traceback (most recent call last):
...
_tkinter.TclError: image "pyimage1" doesn't exist

We can create something similar to what you get (with Python code) by intentionally deleting the image after creating the button in a Tcl script.

package require Tk


proc go {} {
    .msg configure -text "works for ttk button"
    #.b configure -width 1; # Error: image "img" doesn't exist
}

proc main {} {
    image create photo img -width 1 -height 1
    button .b -image img -text "Hello" -compound "center" -command go
    #ttk::button .b -image img -text "Hello" -compound "center" -command go
    pack .b
    label .msg -text ""
    pack .msg
}

main

puts "Before:"
puts [image names]

image delete img

puts "After:"
puts [image names]

#.b configure -width 1; # Error: image "img" doesn't exist

Output:

Before:
img ::tk::icons::information ::tk::icons::error ::tk::icons::warning ::tk::icons::question
After:
::tk::icons::information ::tk::icons::error ::tk::icons::warning ::tk::icons::question

When I run this script, the tk button fails. It does not handle events. If I hover over or click on the button, I get an application error, Error: image "img" doesn't exist.

I didn't dig into why. This goes beyond Python and tkinter.





Most likely, your Python code example is silently failing because of something like this.

ttk button executes a command after being clicked unless we change the button options using configure.

Solutions

As suggested in other answers, the most reliable solution is to have an additional reference to the image object (global variable, button attribute, or class instance attribute).


We can also recreate the image under the same name to make it work.

import tkinter as tk


def go():
    print("ok")
    
def main():
    global root, button
    root = tk.Tk()
    pixel = tk.PhotoImage(width=1, height=1)
    button = tk.Button(root, text="Hmm",
                       image=pixel,
                       compound="c",
                       command=go)

    button.pack()


main()

print(button["image"]) # pyimage1

# recreate image
root.tk.call("image", "create", "photo", "pyimage1", "-width", "1", "-height", "1")
button.configure(bg="blue")
print(root.tk.call("pyimage1", "cget", "-width")) # 1

root.mainloop()

Out of curiosity, we can explore other options, such as creating our own button bindings and/or using ttk.Button with ttk.Style.

However, this limits your ability to change button options. Calling button.configure(text="New") or button["text"] = "New" will result in errors. I doubt such workarounds will be necessary.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745062378a282777.html

最新回复(0)