Search code examples
python-3.xtkintertreeviewcx-freezeshelve

My TreeView from my GUI app with tkinter doesn't work after I build the app using cx_freeze and executing the .exe file


I've created an app with tkinter to practise which resembles a virtual purse to keep track of your expenditures. You can add deposits and withdrawals of money in different categories, which is all then shown in a treeview using the tkinter ttk TreeView widget. Here is an image so it is easier to understand:

App treeview example

When I run it with my main.py file like any other python file it works perfectly, but when I build it into an executable file using cx_freeze, even when the build works and the app runs, the site of the TreeView widget, which is reached through de "Historial" menu, doesn't load and it shows completely empty, but it doesn't crash or raise any error:

App treeview error

Here is my setup.py file code:

import sys
import os
from cx_Freeze import setup, Executable


application_title = "Monedero App"
main_python_file = "main.py"

PYTHON_INSTALL_DIR = os.path.dirname(sys.executable)
os.environ['TCL_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tcl8.6')
os.environ['TK_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tk8.6')

base = None
if sys.platform == "win64":
    base = "Win64GUI"
elif sys.platform == "win32":
    base = "Win32GUI"

options = {
    "build_exe": {
        "packages": ["tkinter", "tkinter.ttk", "shelve", "datetime"],
        "include_files": [
            "D:\\Programacion\\Projects and exercices\\Monedero\\imagenes\\logo.ico",
            (os.path.join(PYTHON_INSTALL_DIR, 'DLLs', 'tk86t.dll'), os.path.join('lib', 'tk86t.dll')),
            (os.path.join(PYTHON_INSTALL_DIR, 'DLLs', 'tcl86t.dll'), os.path.join('lib', 'tcl86t.dll')),
        ],
    }
}

setup(
    name = application_title,
    version = "0.1",
    description = "Test 1",
    options = options,
    executables = [Executable(main_python_file, base=base)],
)

Another important part is that I use the shelve python library to save my data. I do it this way because first I used sqlite to do it but I got the same problem and I thought that sqlite might be the issue. Now I don't know what the issue might be.

I'll also attach the TreeView widget configuration function code, in the case I should change something from there:

def configure(self):
    self.parent.refresh()

    try:
        self.historial_frame.destroy()
    except:
        pass

    self.title.config(
        fg = "white",
        bg = "black",
        font = ("Arial", 30),
        padx = 110,
        pady = 20
    )
    self.title.pack(side = tk.TOP, fill = tk.X, expand = tk.YES)

    self.historial_frame = tk.Frame(self)

    style = ttk.Style()
    style.configure("mystyle.Treeview", highlightthickness = 0, bd = 0, font = ('Calibri', 11)) # Modify the font of the body
    style.configure("mystyle.Treeview.Heading", font = ('Calibri', 13,'bold')) # Modify the font of the headings
    style.layout("mystyle.Treeview", [('mystyle.Treeview.treearea', {'sticky': 'nswe'})]) # Remove the borders

    self.historial = ttk.Treeview(self.historial_frame, columns = ('Fecha', 'Cantidad', 'Descripción'), style = "mystyle.Treeview", height = 10)
    self.historial.column("#0", width = 150, stretch = tk.NO)
    self.historial.column("#1", width = 150, stretch = tk.NO)
    self.historial.column("#2", width = 100, stretch = tk.NO)
    self.historial.column("#3", width = 200, minwidth = 100, stretch = tk.YES)
    self.historial.heading("#0", text = "Categoría", anchor = tk.W)
    self.historial.heading("#1", text = "Fecha", anchor = tk.W)
    self.historial.heading("#2", text = "Cantidad", anchor = tk.W)
    self.historial.heading("#3", text = "Descripción", anchor = tk.W)
    self.historial.grid(row=0, column=0, columnspan=15, padx=10, pady=10, sticky="nsew")

    scrollbar = ttk.Scrollbar(self.historial_frame, orient = tk.VERTICAL, command = self.historial.yview)
    scrollbar.grid(row=0, column=15, sticky="nse", pady="10")
    self.historial.configure(yscrollcommand = scrollbar.set)

    self.parent.import_categories()
    total = self.fill_historial()

    totalLabel = tk.Label(self.historial_frame, text = "Balance total: ")
    totalLabel.config(font = ('Calibri', 13))
    totalLabel.grid(row = 1, column = 0, sticky = "w", pady = 10, padx = 5)

    total_dato = tk.StringVar()
    total_dato.set("{:.2f}".format(total) + " €")
    totalLabel_dato = tk.Label(self.historial_frame, textvariable = total_dato, justify = 'left')
    totalLabel_dato.config(font = ('Calibri', 13))
    totalLabel_dato.grid(row = 1, column = 1, sticky = "w", pady = 10, padx = 5)

    self.historial.tag_configure('odd', font = 'Calibri 13') 

    deleteLabel = tk.Label(self.historial_frame, text = "Eliminar registro: ")
    deleteLabel.grid(row = 1, column = 3, sticky = "w", pady = 10, padx = 5)
    deleteButton = tk.Button(self.historial_frame, text="Borrar", command=self.delete_register)
    deleteButton.grid(row = 1, column = 4, sticky = "w", pady = 10, padx = 5)
    

    self.historial_frame.pack()
    self.pack(fill = "both")

So, if anyone has an idea of what's happening please it will be very much appreciated. If more info is needed please ask. The spanish names and words on the code or images are because I'm from Spain :).

Thank you!


Solution

  • Here is an alternative, by using pyinstaller.

    In your terminal say:

    pip install pyinstaller
    

    and after that just use this command in the terminal, like

    pyinstaller -F -w -i"path.ico" name.py
    

    Here,

    • -w means the program should be windowed, since it uses GUI
    • -F means the program should be made into one file and not a directory
    • -i"path.ico" this can be used to set an icon for your exe

    and make sure to use the exe in the dist folder and if you use any image files then copy the exe to the project directory.

    Take a look at the documentation

    Hope it cleared your doubt, if any errors do let me know

    Cheers