1

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:

  • Not use a window icon.
  • withdraw() the root immediately after creation so that it is not seen, and show it again after the call to the prompt so that it appears that the prompt came first.
  • Don't use the prompt functions at all (tkinter.messagebox, tkinter.dialog, etc), and question the user from elements inside the root (tkinter.Tk) object.

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?

toxicantidote
  • 168
  • 2
  • 10
  • 1
    Expected behavior. *Nothing* in Tkinter will work until an instance of `Tk()` exists; one is automatically created for you if you call any Tk functions (such as a messagebox). And all Tk objects, such as images or vars, exist in one of those instances, and simply will not work with widgets that belong to a different instance. You either have to insure that only one `Tk()` exists, or you have to explicitly specify a containing instance (generally via a `master=` parameter) when creating objects. – jasonharper Mar 29 '23 at 21:57
  • its pretty easy to make a window with just ctypes and/or pywin32 if you are targeting windows and its totally separate from tk stack – Joran Beasley Mar 29 '23 at 21:58
  • @jasonharper Thanks for the quick answer. If you repost your comment as an answer, I would be happy to accept it – toxicantidote Mar 29 '23 at 22:25
  • @JoranBeasley In this case, I am targeting Windows and Linux, but developing on Windows. My chosen workaround has been to draw a minimal root window (frames, common UI elements), show the prompt, and then continue drawing the root window based on the prompt response. – toxicantidote Mar 29 '23 at 22:28
  • Typo error `tkinter.PhotoImage(data=` should be `tkinter.PhotoImage(file=` But it worked for me If I use this icon_window_b64 ='x.png'. – toyota Supra Mar 30 '23 at 09:21
  • 1
    @toyota Supra, no, it's not a typo. See [this](https://www.tcl.tk/man/tcl/TkCmd/photo.html#M6). – relent95 Mar 30 '23 at 10:22
  • @relent95.I didn't realize that. But thank for link. – toyota Supra Mar 30 '23 at 10:55

1 Answers1

1

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.)

relent95
  • 3,703
  • 1
  • 14
  • 17