Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Customtkinter code remains running after GUI window is closed

I'm new to coding/Python started a few weeks ago and am learning by creating a basic app with Customtkinter (for the aesthetic) on Pycharm. The app has 2 Customtkinter windows, a login window (class LoginWindow) and a main window (window) that opens via a button press in the login window. Closing the app on the login window without going to the main window works, however closing the app from the main window or going back to the login window and closing it then does not work and Pycharm shows the code as still running.

Here's the code below minus some widgets, etc that are unrelated to the issue:

#Imports
import tkinter as tk
from tkinter import LEFT
import customtkinter as ctk
from tkinter import messagebox

#CTk preference
ctk.set_appearance_mode('dark')
ctk.set_default_color_theme('green')

class MainFrame(ctk.CTkFrame):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.configure(fg_color='transparent')

        self.tabframe = ctk.CTkFrame(self)
        self.tabframe.pack(side=LEFT, fill='both', expand=False)

        # Create buttons
        self.create_buttons()

    def create_buttons(self):
        self.logoutbutton = ctk.CTkButton(self.tabframe,
                                          text='Logout',
                                          font=('JetBrains Mono', 20, 'bold'),
                                          text_color='white',
                                          fg_color='#FF5555',
                                          hover_color='#FF3333',
                                          command=self.logout)
        self.logoutbutton.grid(row=5, column=0, padx=20, pady=20, sticky='news')

    def logout(self):
        if messagebox.askyesno("Logout", "Are you sure you want to logout?"):
            self.master.destroy()
            login = LoginWindow()
            login.run()

class LoginWindow:
    def show_main_window(self):
        window = ctk.CTk()
        window.geometry("1920x1080")
        window.state('zoomed')
        window.resizable(False, False)
        window.title('Your Finances')
        try:
            window.iconbitmap('logo.ico')
        except:
            pass

        main_frame = MainFrame(window)
        main_frame.pack(fill='both', expand=True)
        window.mainloop()

    def __init__(self):
        self.window = ctk.CTk()
        self.window.geometry('1920x1080')
        self.window.state('zoomed')
        self.window.resizable(width=False, height=False)
        self.window.title('Login')

        try:
            self.window.iconbitmap('logo.ico')
        except:
            pass

        self.frame = ctk.CTkFrame(self.window, fg_color='transparent')

        self.title = ctk.CTkLabel(self.frame,
                                 text='Login',
                                 font=('JetBrains Mono', 40, 'bold'),
                                 text_color='#2FA572',
                                 padx=20, pady=20)
        self.title.grid(row=0, column=0, padx=40, pady=40, columnspan=2, sticky='news')

        self.entryquestion = ctk.CTkLabel(self.frame,
                                         text='Password',
                                         font=('JetBrains Mono', 20),
                                         text_color='#2FA572',
                                         padx=20, pady=20)
        self.entryquestion.grid(row=1, column=0, padx=20, pady=20)

        self.user_input = tk.StringVar(self.window)
        self.password = "password"

        self.entrypassword = ctk.CTkEntry(self.frame,
                                         textvariable=self.user_input,
                                         font=('JetBrains Mono', 20),
                                         show='*')
        self.entrypassword.grid(row=1, column=1, pady=20)

        self.submitbutton = ctk.CTkButton(self.frame,
                                         text='Submit',
                                         font=('JetBrains Mono', 20, 'bold'),
                                         command=self.submit)
        self.submitbutton.grid(row=2, column=0, padx=20, pady=40)

        self.exitbutton = ctk.CTkButton(self.frame,
                                        text='Exit',
                                        font=('JetBrains Mono', 20, 'bold'),
                                        text_color='white',
                                        fg_color='#FF5555',
                                        hover_color='#FF3333',
                                        command=self.exit)
        self.exitbutton.grid(row=2, column=1, padx=20, pady=40)

        self.frame.pack()

    def submit(self):
        entered_password = self.user_input.get().strip()
        if not entered_password:
            messagebox.showwarning("Error", "Please enter a password")
            return
        if entered_password == self.password:
            self.window.withdraw()
            self.window.quit()
            self.show_main_window()
        else:
            messagebox.showerror("Error", "Incorrect password")

    def run(self):
        self.window.mainloop()

    def exit(self):
        self.window.quit()
        self.window.destroy()

if __name__ == "__main__":
    app = LoginWindow()
    app.run()

Currently stopping the code requires pressing the 'Stop' button twice in the Pycharm IDE, on first click it shows the error code below:

invalid command name "2673024703360_click_animation"
    while executing
"2673024703360_click_animation"
    ("after" script)
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\suraj\AppData\Local\Programs\Python\Python313\Lib\tkinter\__init__.py", line 2068, in __call__
    return self.func(*args)
           ~~~~~~~~~^^^^^^^
  File "C:\Users\suraj\PycharmProjects\PythonProject\.venv\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 554, in _clicked
    self._command()
    ~~~~~~~~~~~~~^^
  File "C:\Users\suraj\PycharmProjects\PythonProject\Beta.py", line 208, in submit
    self.show_main_window()
    ~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\suraj\PycharmProjects\PythonProject\Beta.py", line 144, in show_main_window
    window.mainloop()
    ~~~~~~~~~~~~~~~^^
  File "C:\Users\suraj\PycharmProjects\PythonProject\.venv\Lib\site-packages\customtkinter\windows\ctk_tk.py", line 165, in mainloop
    super().mainloop(*args, **kwargs)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "C:\Users\suraj\AppData\Local\Programs\Python\Python313\Lib\tkinter\__init__.py", line 1599, in mainloop
    self.tk.mainloop(n)
    ~~~~~~~~~~~~~~~~^^^
  File "C:\Users\suraj\AppData\Local\Programs\Python\Python313\Lib\tkinter\__init__.py", line 2063, in __call__
    def __call__(self, *args):
    
KeyboardInterrupt

Process finished with exit code -1

I even tried adding a specific 'Exit' button (self.exitbutton) on the login window with an 'exit' function, seen in the code just before if __name__ == "__main__" but it does not stop the issue. Edited the post to include the exit code I had missed in case relevant.

like image 544
Suraj Satish Avatar asked Jul 26 '26 02:07

Suraj Satish


2 Answers

From the documentation:

During the runtime of a program there should only be one instance of this class with a single call of the .mainloop() method, which starts the app. Additional windows are created using the CTkToplevel class.

You violate this multiple times in your code:

  • First you create a LoginWindow instance which creates a CTk instance and start its .mainloop() method with app.run() (so far this is fine).
  • Then you create a second CTk instance in .show_main_window() and execute its .mainloop() as well, when you should use CTkToplevel without .mainloop().
  • Finally, in MainFrame.logout(), you create yet another LoginWindow instance when you should restore the original one with .deiconify(). This requires you to pass the original LoginWindow instance to your MainFrame instance, such that you can reference it inside .logout().

I'm not 100% sure that this causes the error, but it looks like the most likely cause to me, especially since I don't get any error if I click the exit button before loggin in.

like image 176
Aemyl Avatar answered Jul 27 '26 18:07

Aemyl


@Aemyl is correct. Your problem is orphaned instances of Tk.

  1. First you instantiate Tk here: self.window = ctk.CTk()
  2. Then you do this in submit:
self.window.withdraw()  # hides the window
self.window.quit()  # exits the mainloop, but the instance is still live
print(self.entrypassword.get())  # add this line and you can see that the widgets are still accessible
  1. Then in show_main_window you start another instance of Tk: window = ctk.CTk(). Remember, though, that the first instance is still there.
  2. In logout, you correctly kill the new instance: self.master.destroy. Again, first instance is still live. You just killed the second one.
  3. Then you create a new instance of LoginWindow, which starts another instance of Tk. But the old instance is still there, orphaned, because you never killed it. You just exited the mainloop.

Solution 1 - You actually want new instances

Easy fix. Just replace

self.window.withdraw()
self.window.quit()

with

self.window.destroy()  # no more orphaned instance

Solution 2 - You want to reuse the first instance

  1. Replace
self.window.withdraw()
self.window.quit()

with

self.window.withdraw()
self.user_input.set('')  # clear the Entry
  1. Use Toplevel in show_main_window:
  • Replace window = ctk.CTk() with window = ctk.CTkToplevel()
  • Remove this line: window.mainloop()
  1. In logout, replace
login = LoginWindow()
login.run()

with

self.master.master.deiconify()
  1. You'll need to handle closing the second window without invoking logout, though, or you'll still have that hidden window hanging around if you close with the [X] button. Add this line just after creating the Toplevel window (in show_main_window:
window.protocol('WM_DELETE_WINDOW', self.exit)

I don't know how you're structuring your application, but either solution should fix the immediate issue.

like image 29
Sam Avatar answered Jul 27 '26 18:07

Sam