Search code examples
pythontkintermarkdownexecutable

Including Icon In Tkinter Window When Made In To An Executable


I'm using Python 3.11 and I've made a Markdown reader program.

I've put an icon on a Tkinter window using the iconbitmap thing and when I run the Python file it all works well.

However, when I ran it as an executable (made using cxfreeze) and opened a markdown file it returned this error to me:

Error

It looks like it can't invoke the iconbitmap thing or it can't access icon.ico.

Here's my code:

import tkinter as tk
import tkinter.filedialog as FileDialog
from tkhtmlview import HTMLLabel
import markdown
import sys
import os

dir_name = os.path.dirname(__file__)
dir_name = dir_name.replace("\\", "\\\\")
icon_dir = dir_name + "\\\\icon.ico"

def display_markdown(markdown_text):
    # Convert Markdown to HTML
    html_content = markdown.markdown(markdown_text)

    # Create the Tkinter window
    window = tk.Tk()
    window.iconbitmap(icon_dir)
    window.title("Markdown Reader")

    # Create an HTMLLabel to display the HTML content
    html_label = HTMLLabel(window, html=html_content)
    html_label.pack(expand=True, fill="both")

    # Run the Tkinter main loop
    window.mainloop()

def display_markdown_from_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        markdown_text = file.read()
    display_markdown(markdown_text)

if __name__ == "__main__":
    if len(sys.argv) == 2:
        display_markdown_from_file(sys.argv[1])
    else:
        file = FileDialog.askopenfile(mode="r", filetypes=[("Markdown Files", "*.md *.markdown")], title="Open Markdown File")
        if file is not None:
            content = file.read()
            display_markdown(content)


Solution

  • The problem becomes clearer if we print the paths.

    ...
    import sys
    import os
    
    dir_name = os.path.dirname(__file__)
    dir_name = dir_name.replace("\\", "\\\\")
    icon_dir = dir_name + "\\\\icon.ico"
    
    print(__file__)
    print("dir_name", dir_name)
    print("icon_dir", icon_dir)
    ...
    

    Outputs

    Note: The icon file (icon.ico) exists in the same directory as the Python script. I've simplified your program so that it only takes arguments and shows a GUI.


    Python file:

    (env) F:\cxexe>test.py qwerty
    F:\cxexe\test.py
    dir_name F:\\cxexe
    icon_dir F:\\cxexe\\icon.ico
    

    Output for exe:

    (env) F:\cxexe>cxfreeze -c test.py --target-dir dist --include-files icon.ico
    
    (env) F:\cxexe>cd dist
    
    (env) F:\cxexe\dist>test.exe qwerty
    test.py
    dir_name
    icon_dir \\icon.ico
    Traceback (most recent call last):
        ...
        return self.tk.call('wm', 'iconbitmap', self._w, bitmap)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    _tkinter.TclError: bitmap "\\icon.ico" not defined
    

    As we can see, the dir_name is now an empty string.


    Backslashes become redundant in the file name.

    >>> import os
    >>> path = "test.py" # __file__
    >>> os.path.split(path)
    ('', 'test.py')
    >>> os.path.dirname(path)
    ''
    >>> print(os.path.dirname(path) + "\\\\icon.ico")
    \\icon.ico
    

    os.path.dirname


    Solution


    Docs recommendation

    ...
    import sys
    import os
    
    
    icon_file = "icon.ico"
    
    def find_data_file(filename):
        if getattr(sys, "frozen", False):
            # The application is frozen
            datadir = os.path.dirname(sys.executable)
        else:
            # The application is not frozen
            # Change this bit to match where you store your data files:
            datadir = os.path.dirname(__file__)
        return os.path.join(datadir, filename)
    
    
    def display_markdown(markdown_text):
        ...
        window.iconbitmap(find_data_file(icon_file))
        ...
    

    Docs

    Another sample