How to use a string for icon bitmap?

4.5k Views Asked by At

Is there a way to use a string for the iconbitmap in the Tkinter (Python 2.7.9) module?

I know that you can prodive a file path (even though I haven't understood where the difference between default and bitmap as parameters is.

The reason I am asking is because I want to create out of a Python script an .exe with py2exe (which works), but I would need to create a icon file then to be able to use an icon.

Any workaround or other method is appreciated.

2

There are 2 best solutions below

4
On BEST ANSWER

(Note to folks using Python 3, see my supplemental answer for an alternative that only works in that version.)

I don't know of any way to pass iconbitmap() anything other than a file path in Python 2.x, so here's a workaround that creates a temporary file from a string representation of icon file's contents to pass it. It also shows a way to ensure the temporary file gets deleted.

import atexit
import binascii
import os
import tempfile
try:
    import Tkinter as tk
except ModuleNotFoundError:  # Python 3
    import tkinter as tk


iconhexdata = '00000100010010100000010018006803000016000000280000001000000020' \
              '000000010018000000000040030000130b0000130b00000000000000000000' \
              'ffffff6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c' \
              '6c6d6c6c6d6c6c6d6c6c6d6c6c6dffffffffffff6c6c6d6c6c6d6c6c6d6c6c' \
              '6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d' \
              'ffffffffffff6c6c6d6c6c6dffffffffffffffffffffffffffffffffffffff' \
              'ffffffffffffffffffffff6c6c6d6c6c6dffffffffffff6c6c6d6c6c6dffff' \
              'ff6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6dffffff6c6c6d' \
              '6c6c6dffffffffffff6c6c6d6c6c6dffffff6c6c6d6c6c6d6c6c6d6c6c6d6c' \
              '6c6d6c6c6d6c6c6d6c6c6dffffff6c6c6d6c6c6dffffffffffff6c6c6d6c6c' \
              '6dfffffffffffffffffffffffffffffff2f4f7d6dfe9b8cadb95b2cfedf2f6' \
              '6c6c6d6c6c6dfffffffffffffffffffffffff4f7fac0d4e69bb9d6739dc657' \
              '89ba3e78b03f78af4177ad4276abd2deeaffffffffffffffffffffffffffff' \
              'ffffffffdfe9f24178ad4178ad4178ad5081b17398be9db8d3bed4e6bbd7ec' \
              'add7f3fffffffffffffffffffffffffffffffffffff8fafcaac2dac4d3e4df' \
              'e8f1f9fbfdfffffff4fafd91cff520a3f10297eee4f4feffffffffffffffff' \
              'ffffffffffffffffffffffffffffffffffffffe7f4fd7fcaf6159def0595ec' \
              '179fec82c7f4bad6f7fdfefffffffffffffffffffffffffffffffffdfeffdb' \
              'f0fd7bc8f6119bed0695eb1a9ded7ecaf5f0f8febfd3f73165e495b1f1ffff' \
              'fffffffffffffffffffffffffff6fbfe2fa6ee0695eb1b9eed86ccf5e8f6fd' \
              'ffffffd2dff93468e5326ae5c7d6f8ffffffffffffffffffffffffffffffff' \
              'ffff96d2f784cbf5eaf6fdffffffffffffe3eafb4275e72c66e4b6caf6ffff' \
              'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' \
              'f3f6fd5784ea2c66e499b5f2ffffffffffffffffffffffffffffffffffffff' \
              'fffffffffffffffffffffffffffffdfeff7097ed2c66e47a9eeeffffffffff' \
              'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfeff' \
              '85a7ef2c66e4608cebf9fbfeffffffffffffffffffffffff00000000000000' \
              '00000000000000000000000000000000000000000000000000000000000000' \
              '0000000000000000000000000000000000000000000000000000'

def on_closing(iconfile):
    try:
        os.remove(iconfile.name)
    except Exception:
        pass

with tempfile.NamedTemporaryFile(delete=False) as iconfile:
    iconfile.write(binascii.a2b_hex(iconhexdata))

# Register a clean-up function.
atexit.register(lambda file=iconfile: on_closing(file))

root = tk.Tk()
root.title('stackoverflow!')
root.iconbitmap(iconfile.name)

tk.Label(root, text='Note the custom icon').pack()
tk.Button(root, text='OK', bg='lightgreen', command=root.quit).pack()

root.mainloop()

The window displayed will have a custom icon as shown below:

screenshot of tkinter window created

You didn't ask how to do it, but here's the code I used to convert the original .ico file into the Python string variable used in my example:

from __future__ import print_function
import binascii
try:
    from itertools import izip_longest as zip_longest
except ImportError:
    from itertools import zip_longest

iconfile = 'stackoverflow.ico'  # Path to icon file.
VAR_NAME = 'iconhexdata'
VAR_SUFFIX = ' = '
INDENTATION = ' ' * len(VAR_NAME+VAR_SUFFIX)
MAX_LINE_LENGTH = 80
EXTRA_CHARS = '"" \\'  # That get added to each group of hex digits.
LINE_LENGTH = MAX_LINE_LENGTH - len(INDENTATION) - len(EXTRA_CHARS)


def grouper(chunk_size, iterable):
    """ Collect data into fixed-length chunks or blocks.
        s -> (s0,s1,...sn-1), (sn,sn+1,...s2n-1), (s2n,s2n+1,...s3n-1), ...
    """
    return zip_longest(*[iter(iterable)]*chunk_size, fillvalue='')

with open(iconfile, 'rb') as imgfile:
    hexstr = [chr(x) for x in bytearray(binascii.b2a_hex(imgfile.read()))]

hexlines = (''.join(str(x) for x in group) for group in grouper(LINE_LENGTH, hexstr))

print(VAR_NAME + VAR_SUFFIX, end='')
print((' \\\n' + INDENTATION).join((repr(line) for line in hexlines)))
1
On

This doesn't answer your Python 2.7 question, but may be of interest to others nowadays who are using Python 3. In the later version of Python the tkinter module has an alternative to the iconbitmap() function—named iconphoto()—that can be used to set the title-bar icon of any tkinter/toplevel window. However, unlike the former method, the image should be the instance of the tkinter.PhotoImage class, not only a file path string — and there are ways to create a PhotoImage object from a byte string in the program so there would be no need to use a file at all, even a temporary one as was the case in my original answer.

#!/usr/bin/env python3
iconimgdata = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d' \
              b'2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXpJREFUeNpi+I8V/Pv7fnnrkz' \
              b'Sd1z0J/37/RJZhYsAKGJkEwis4DV1+3jrzYXEjsgwODRA98c1sctrfTmz6vG0' \
              b'WQhzNLd8vTft6uuXvt1cQ7u9Xj5+XOgHd9u3UNogIioZ/v7+9W6b/eirb23nS' \
              b'X8+0/f32Aij48/6lpxkmT7OMflw7ju4HRmZ2fq813MalDH+/fTvZ8GG50bfT9' \
              b'aySckLZ3f9///95+xxIDcgWDPDv64uf12b8vDzt748PDFxCHBrZzPwWHBrOQI' \
              b'8hNPz9/fPeiU1cglK8otI8wlJMLGz/fn/9cXXunyv9f788Eoh9xMgtDVTGAjf' \
              b'12/vnl7dNh7BZOPl5xZVFFbSEZXTZTGazM3yCqEZx0u8fX9/cPfPh6e0PT258' \
              b'efMEqP8/A+O//0z//jPaZ0wQVdRFaMjLzQWyJk2ejOyNH18/f3r95NPrR19e3' \
              b'FV3iCivqoeoYUFWBNGJCSb5ChER0zgAig1oriKgAZd70ADJTgIIMACVtvtL6F' \
              b'X2cAAAAABJRU5ErkJggg=='

import base64
import tkinter as tk  # Python 3

root = tk.Tk()
root.title('stackoverflow!')
root.geometry('225x50')
img = base64.b64decode(iconimgdata)

photo = tk.PhotoImage(data=img)
root.iconphoto(False, photo)

tk.Label(root, text='Note the custom icon').pack()
tk.Button(root, text='OK', bg='lightgreen', command=root.quit).pack()

root.mainloop()

Here's what the demo looks like running:

screenshot


Here again is the code I used to convert the 16x16 pixel .png image file I had into the Python bytestring variable used in the code in my example above. Here's a link to the small stackoverflow.png image file being used.

#!/usr/bin/env python3
import base64
from itertools import zip_longest

imgfilepath = 'stackoverflow.png'  # Path to an image file.
VAR_NAME = 'iconimgdata'
VAR_SUFFIX = ' = '
INDENTATION = ' ' * len(VAR_NAME+VAR_SUFFIX)
MAX_LINE_LENGTH = 79
EXTRA_CHARS = '"" \\'  # That get added to each group of hex digits.
LINE_LENGTH = MAX_LINE_LENGTH - len(INDENTATION) - len(EXTRA_CHARS)


def grouper(chunk_size, iterable):
    """ Collect data into fixed-length chunks or blocks.
        s -> (s0,s1,...sn-1), (sn,sn+1,...s2n-1), (s2n,s2n+1,...s3n-1), ...
    """
    return zip_longest(*[iter(iterable)]*chunk_size, fillvalue='')

with open(imgfilepath, 'rb') as file:
    hexstr = [chr(x) for x in base64.b64encode(file.read())]

hexlines = (''.join(str(x) for x in group) for group in grouper(LINE_LENGTH, hexstr))

print(VAR_NAME + VAR_SUFFIX  + 'b', end='')
print((' \\\n' + INDENTATION  + 'b').join((repr(line) for line in hexlines)))