12

Currently, I'm trying to convert my tkinter Python script to an EXE file using cx_freeze. It is somehow not working when I try to add another file. You can see the method I've used in the minimum example I'm using below.

import tkinter as tk

import numpy.core._methods, numpy.lib.format 

class Main(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.geometry("700x400")
        self.wm_iconbitmap('test.ico')

        container = tk.Frame(self)

        container.pack(side="top", fill="both", expand = True)

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        for F in (StartPage, PageOne):

            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(StartPage)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()        
        frame.update_page() # <-- update data on page when you click button

    def get_page(self, page_class):
        return self.frames[page_class]


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller 

        label1 = tk.Label(self, text="What are the sizes?")
        label1.pack()

        L1 = tk.Label(self, text="Length :")
        L1.pack()

        self.E1 = tk.Entry(self)
        self.E1.pack()

        button = tk.Button(self, text="Next", command=lambda: controller.show_frame(PageOne))
        button.pack()

    def update_page(self): # empty method but I need it
        pass   

class PageOne(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        label1 = tk.Label(self, text="You have insert")
        label1.pack()

        # create empty label at start
        self.label2 = tk.Label(self, text="")
        self.label2.pack()

        button = tk.Button(self, text="Back", command=lambda: controller.show_frame(StartPage))
        button.pack()

    def update_page(self):
        # update label when page is changed
        page1 = self.controller.get_page(StartPage) 
        var = page1.E1.get()
        self.label2['text'] = var


app = Main()
app.mainloop() 

The second script:

import cx_Freeze
import sys
import matplotlib 
import os 
import numpy.core._methods
import numpy.lib.format

base = None 

if sys.platform=='win32':
    base = "Win32GUI"
    

executables = [cx_Freeze.Executable("Show_file.py")]    

cx_Freeze.setup(
        name = "Name",
        options = {
            "build_exe": {
                "packages": ["tkinter","matplotlib"],
                "include_files": ["test.ico"]
            }
        },
        version="0.01",
        executables=executables) 

It works when I do not add an icon when I try to build the EXE file. However, the EXE does not open anymore when I try to add an icon. Furthermore, when I try to add a database Excel file, I get the message that such a file does not exist. All the files are in the correct folder. That is not the problem.

Community
  • 1
  • 1
Beertje
  • 519
  • 2
  • 5
  • 14
  • 1
    your EXE file is self-uncompressing ZIP file - when you start program then system uncompresses it to random folder and it doesn't have to run it in this folder but it uses full path to run it from different folder - and then program can search other files in wrong folder. There were questions/answers how to use `sys.argv[0]` to get full path to folder with script - and then you can use `os.path.join()` to create full path to other files. – furas Jan 17 '18 at 13:26
  • 1
    run exe file in console/terminal/cmd.exe and you should see useful error message which you should put in question (as text, not screenshot) – furas Jan 17 '18 at 13:27

2 Answers2

30

As the title says Converting tkinter to exe I believe pyinstaller is worth mentioning in this case.

There are some debates on which is better pyinstaller or cx_Freeze on the Internet, but I found pyinstaller simpler and it worked for me out of the box with tkinter. A one-liner in cmd:

pyinstaller.exe --onefile --icon=myicon.ico main.py

--onefile option produces, well, one output file instead of many.

--icon will connect an icon of your choice.

main.py is your main file with the main function.

Saikat
  • 14,222
  • 20
  • 104
  • 125
akarilimano
  • 1,034
  • 1
  • 10
  • 17
  • 1
    I tried pyinstaller and it creates .exe but when I run it, nothing happens. Can not see my GUI what could be the reason? – Naazneen Jatu Apr 08 '20 at 14:00
  • @Naazneen probably you should post another question with some code example and maybe more info on that (like what packages you use, your OS, Python version, etc), as it is hard to tell what is wrong now. – akarilimano Apr 09 '20 at 07:22
  • I tried running it from already opened command prompt and it showed what's the error. And in such case, eror is probably with the .py rather than .exe so running from cmd show the error and it can be solved out. – Naazneen Jatu Apr 09 '20 at 09:11
  • @Naazneen, indeed. One more thing is if your app uses some external files it may be necessary to recreate the exact structure of folders alongside with the executable. Or move executable to the same location as main.py and look if it works. If yes, you can determine the minimum set of needed files to distribute with the exe. – akarilimano Apr 09 '20 at 11:39
  • 2
    Be careful with the --onefile option, as according to this answer, it can make the program quite slow to start : https://stackoverflow.com/a/9470393/4942149 – Brikowski Jun 11 '20 at 06:28
  • 5
    Use the flag --windowed, so that black console window won't show up. – Adarsh Patel Jun 11 '20 at 13:54
3

The tkinter runtimes and libraries are missing. To include those I would suggest using os.environ()and include the runtimes using the include_files argument as they (briefly) described here.

Using os.environ() is easy. For example it can be done like this:

os.environ["TCL_LIBRARY"] = "<PathToPython>\\Python\\Python36-32\\tcl\\tcl8.6"
os.environ["TK_LIBRARY"] = "<PathToPython>\\Python\\Python36-32\\tcl\\tk8.6"

Next include the runtimes (DLLs) in the include files arguement:

    options = {"build_exe":{"packages":["tkinter","matplotlib"],"include_files":["test.ico", "<PathToPython>\\Python\\Python36-32\\DLLs\\tcl86t.dll", "<PathToPython>\\Python\\Python36-32\\DLLs\\tk86t.dll"]}},

Now your whole setup script should look like this:

import sys # Imports are automatically detected (normally) in the script to freeze
import os 

base = None 

os.environ["TCL_LIBRARY"] = "<PathToPython>\\Python\\Python36-32\\tcl\\tcl8.6"
os.environ["TK_LIBRARY"] = "<PathToPython>\\Python\\Python36-32\\tcl\\tk8.6"

if sys.platform=='win32':
    base = "Win32GUI"


executables = [cx_Freeze.Executable("Show_file.py")]    

cx_Freeze.setup(
        name = "Name",
        options = {"build_exe":{"packages":["tkinter","matplotlib"],"include_files":["test.ico", "<PathToPython>\\\\Python\\Python36-32\\DLLs\\tcl86t.dll", "<PathToPython>\\\\Python\\Python36-32\\DLLs\\tk86t.dll"]}},
        version="0.01",
        executables=executables) 

You don't need all the imports you're going to use in the setup script, cx_Freeze automatically detects them.

Xantium
  • 11,201
  • 10
  • 62
  • 89