Window Icon in Tk (tkinter)

Both main windows (created via the tk.Tk class) and child windows (tk.Toplevel) have the Tcl/Tk logo icon by default. Setting custom window icons will give our application a more professional look. A window icon is also often displayed by the operating system on the taskbar or application bar. In this post we will see the various ways for configuring window icons on the different platforms supported by Tk (Windows, Linux and Mac).

Tk window objects provide three methods to set an icon:

  • iconbitmap()

  • iconmask()

  • iconphoto()

The iconmask() function has the goal of allowing transparent areas in our window icon. However, the various image formats (PNG, GIF, JPG, etc.) and icon formats (especially .ico) have long supported transparency. Therefore, in most cases iconmask() is unnecessary. If we have our application icon in an image file, the easiest way to load it into the window is via iconphoto():

import tkinter as tk
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
# Load the image file from disk.
icon = tk.PhotoImage(file="icon-16.png")
# Set it as the window icon.
root.iconphoto(True, icon)
root.mainloop()

You can download our test icons from here to run the code in this post. It is a ZIP file that contains:

File

Preview

Notes

icon-16.png

preview16

16x16 px

icon-32.png

preview32

32x32 px

icon.ico

Contains both 16x16 and 32x32 icons. Windows only.


The icons must be in the same folder (or the current working directory) as our program.

After running the code, we get the following result:

/images/window-icon-in-tk-tkinter/window-with-icon.png

The code is simple: we load an image file via the tk.PhotoImage class and then assign it as the window icon via the iconphoto() method. The first argument is a boolean indicating whether that same icon should be applied to child windows. This procedure of loading the window icon from an image file is supported by all operating systems.

In addition to being displayed on the window, the icon is also often displayed on the task or application bar. Generally, the proper size for the window icon is 16x16 pixels. We'll call this image the "small icon." Instead, a "large icon" is displayed on the taskbar, which in Windows is usually a 32x32 image. These values may change, however, according to the display settings of each system. Since we have loaded a small icon, we will see that the operating system automatically enlarges the 16x16 image on the taskbar, producing a pixelated effect:

/images/window-icon-in-tk-tkinter/icon-in-taskbar.png

If we load a large icon instead of a small icon, it will look fine on the taskbar, but still pixelated on the window or title bar. Fortunately, the iconphoto() method can receive two icons of different sizes (a small and a large one) to display properly in one case and the other:

import tkinter as tk
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
small_icon = tk.PhotoImage(file="icon-16.png")
large_icon = tk.PhotoImage(file="icon-32.png")
root.iconphoto(False, large_icon, small_icon)
root.mainloop()

In this way, the operating system shows the large icon on the taskbar and the small icon on the window:

/images/window-icon-in-tk-tkinter/icon-in-window-and-taskbar.png

Note that we have changed the first argument of iconphoto() from True to False, since, for some unknown reason (perhaps a Tk bug), if the first argument is True, in Windows the small icon is ignored and the large one is shown both in the window and in the taskbar. Similarly, some operating systems consider only the large icon (and there's really nothing we can do to change this).

Image sizes may vary, as long as one icon is larger than the other. The small icon could be 24x24 and the large one 64x64, or any other combination. The ideal sizes for each context (the window's titlebar or the system taskbar) depend on the operating system and, in Linux distributions, on each particular distribution. On Ubuntu, for example, the ideal size for the large icon is 64x64. On Windows, the appropriate sizes are generally 16x16 and 32x32, as we have been using them.

On Windows we can also use the iconbitmap() method to set the icon of the window from an .ico file. This Microsoft format is more convenient because it allows you to have multiple images of various sizes (16x16, 32x32, 64x64, etc.) embedded in a single .ico file. Some programs to create and edit this type of file are Greenfish Icon Editor (free) and icofx. Thus, for example, the following code sets the icon.ico file (available in the download above) as the window icon:

import tkinter as tk
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
root.iconbitmap("icon.ico")
root.mainloop()

Since the icon.ico file contains a 16x16 image and a 32x32 image, the small icon is automatically displayed in the window and the large icon is automatically displayed in the taskbar.

Packing Icons Into an Executable File

The above codes require icons to be present in the file system in order to loaded into the window. However, if we pack our GUI application into an executable file using tools like PyInstaller or cx_Freeze, we may want the executable to have no dependencies, so it can be moved from one place to another without having to carry around a bunch of files. Or maybe we just don't want to have to distribute our icons as separate files. In these cases, where do we load the icon from? There are two solutions for this.

If our application is only targeting Windows systems, then packing the icons into the executable is very easy because Tk supports loading icons directly from executable files (.exe) via iconbitmap(). Suppose we convert our code to an executable using PyInstaller:

pyinstaller --icon=icon.ico --noconsole app.py

app.py is the name of our Python file, --noconsole indicates that it is a desktop application, --icon passes the name of the .ico file that we want to include in the final executable.

Now we can use the following code to load the icon packed inside the Windows executable into the window:

import sys
import tkinter as tk
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
# Load the current executable icon into the window.
root.iconbitmap(sys.executable)
root.mainloop()

On Windows, the iconbitmap() method also accepts as an argument, in addition to a .ico file, a .exe file whose icon you want to use as window icon. Since sys.executable contains the path of our executable, by passing it as an argument we always load the icon from the executable, without the need to ship .ico or .png files separately. Note that this procedure implies that when the code is executed from the .py file, the window receives the Python icon, since sys.executable contains the path of the interpreter.

A second, less elegant, but functional option for all operating systems, is to base64-encode the content of the icons and include them in our source code file. The image data can then be loaded directly from a variable via tk.PhotoImage. To do this, we first need some code that reads the content of an image and encodes it with Base64. This can be easily achieved with a small Python program:

# encode_icon.py
from base64 import b64encode
import sys
import pyperclip
filename = sys.argv[1]
with open(filename, "rb") as f:
    pyperclip.copy(b64encode(f.read()).decode("ascii"))
    print("Copied to clipboard.")

This little program receives the name of an icon by console, encodes it with base64 and copies it to the clipboard. The Pyperclip module is required (installable via pip install pyperclip). Now let's start by enocding the small icon by running on the terminal:

python encode_icon.py icon-16.png

Next, let's paste the data copied to the clipboard into a variable and load it via tk.PhotoImage:

from base64 import b64decode
import tkinter as tk
small_icon_data = "paste-data-here"
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
small_icon = tk.PhotoImage(data=b64decode(small_icon_data))
root.iconphoto(False, small_icon)
root.mainloop()

Let's do the same with the large icon:

python encode_icon.py icon-32.png

And finally:

from base64 import b64decode
import tkinter as tk
small_icon_data = "paste-small-icon-data-here"
large_icon_data = "paste-large-icon-data-here"
root = tk.Tk()
root.title("Window With Icon")
root.geometry("300x200")
small_icon = tk.PhotoImage(data=b64decode(small_icon_data))
large_icon = tk.PhotoImage(data=b64decode(large_icon_data))
root.iconphoto(False, large_icon, small_icon)
root.mainloop()

Great! Our icon data is embedded in the code, so there is no need to distribute additional .png files.