pythontkintertkinter-photoimage

tkinter - Can't assign window icon to root window if a prompt was created first


Environment: Windows 10, Python 3.9.5

If I try to display a prompt (using tkinter.messagebox functions) before creating the root window, I cannot assign a window icon to the root window. The iconphoto call fails with the error, even though the icon given is a valid PhotoImage object:

_tkinter.TclError: can't use "pyimage1" as iconphoto: not a photo image

In this instance, my objective is to display a prompt to the user before the program starts, or even potentially ask a question (with tkinter.messagebox.askyesno) that may affect the operation of the GUI.

For example, the following code will cause this error:

## this is a base64 GIF to use as a window icon
icon_window_b64 = 'R0lGODlhIAAgAKECAP8AAP///wAAAAAAACH5BAEKAAIALAAAAAAgACAAAAJrlI+gy4oPX5sgWkFzu0f7en0iCI1faY5JKq5TEGRw5jLw3NzxZMj6rvgBR8LijQUwCpFBJY6ZVEIXzmnTaI1ip84nq3v8gsOmJfVHw7zIZ++iZhvG073spmNnoLKWPGfKUccUKHFCGNFyUQAAOw=='
## import tkinter
import tkinter
## import the messagebox module so we can make the prompt
import tkinter.messagebox
## show the prompt
tkinter.messagebox.showinfo('A prompt', 'Dismiss this for an exception')
## create the root window
root = tkinter.Tk()
## set the size of the root window
root.geometry('200x20')
## create the photoimage object from the base64 GIF
icon_window = tkinter.PhotoImage(data = icon_window_b64)
## put a label in to the window
tkinter.Label(root, text='This is a test').pack()
## set the window icon for the root window
root.iconphoto(False, icon_window)
## let the GUI block forevermore
root.mainloop()

However, this code will not:

## this is a base64 GIF to use as a window icon
icon_window_b64 = 'R0lGODlhIAAgAKECAP8AAP///wAAAAAAACH5BAEKAAIALAAAAAAgACAAAAJrlI+gy4oPX5sgWkFzu0f7en0iCI1faY5JKq5TEGRw5jLw3NzxZMj6rvgBR8LijQUwCpFBJY6ZVEIXzmnTaI1ip84nq3v8gsOmJfVHw7zIZ++iZhvG073spmNnoLKWPGfKUccUKHFCGNFyUQAAOw=='
## import tkinter
import tkinter
## import the messagebox module so we can make the prompt
import tkinter.messagebox
## create the root window
root = tkinter.Tk()
## show the prompt
tkinter.messagebox.showinfo('A prompt', 'No exceptions here!')
## set the size of the root window
root.geometry('200x20')
## create the photoimage object from the base64 GIF
icon_window = tkinter.PhotoImage(data = icon_window_b64)
## put a label in to the window
tkinter.Label(root, text='This is a test').pack()
## set the window icon for the root window
root.iconphoto(False, icon_window)
## let the GUI block forevermore
root.mainloop()

The key difference in the second code block is that the prompt is created after the tkinter.Tk() object. Obviously, I have few workarounds I can use:

But, after all this, my question is: Is this expected behaviour, or is this a bug within tkinter? If it is expected behaviour, why is it?


Solution

  • It was a bug in the tkinter, fixed in Python 3.10.0. See the issue and the pull request for details.

    In summary, with that fix, a temporary Tk root instance will be properly cleared, causing no side effects after showing and closing a dialog. (It's fine to have multiple Tk root instances created if they are used one at a time.)