How do i make a customtkinter button run a function without freezing

Question:

right now am trying to make a customtkinter GUI for my code
the problem that am running in which I cannot really find an answer to it is as follows,
I am trying to make a button run a specific bit of code and I want to be able to do it multiple times, normally that’s not a problem but in this case the code takes a bit of time to finish (1hr)

during this 1hr the GUI is not responsive till the code has been executed

so i tried to run it by threading just as follows

btn = customtkinter.CTkButton(misc_frame, text="Press",command =threading.Thread(target=main).start)

this fixes the problem of the GUI/ application freezing but it produces 2 new problems

1-once I hit the button 1 time I cannot hit it again to run the same bit of code
as it is a thread

2-if I close the GUI am trying to have it so the code just terminates and I have no idea how to implement that

btn = customtkinter.CTkButton(misc_frame, text="Press",command =threading.Thread(target=main).start)

so i ran this code it solved 1 problem but brought about 2 other problems which are

1- I cannot hit the same button again to run the same function after it finished it the first time

2- if I close the GUI the code keeps on running and I would like that to happen

import tkinter
import customtkinter
import multiprocessing as mp
import os
import threading
import time




def main(button_loc):
    customtkinter.CTkButton.configure(button_loc,state="disabled",fg_color="red",text= "Running" , font=('Helvetica', 18, 'bold'))
    x = 0
    while x <=10 :
        x += 1
        print("hello")
        time.sleep(1)
    customtkinter.CTkButton.configure(button_loc,state="normal",fg_color="green",text= "START" , font=('Helvetica', 18, 'bold'))
    print("done")

#run app
if __name__ == "__main__":
    # System Setting
    customtkinter.set_appearance_mode("Dark")
    customtkinter.set_default_color_theme("blue")

    # App Frame
    app = customtkinter.CTk()
    app.geometry("1100x580")
    app.title("Program 2.0")

    #frames
    Prices_frame = customtkinter.CTkFrame(app, width=500, height=260)
    Prices_frame.place(x = 10, y = 10)

    misc_frame = customtkinter.CTkFrame(app, width=200, height=260) 
    misc_frame.place(x = 520, y= 10)

    #prices frame Labels
    label_Runes = customtkinter.CTkLabel(Prices_frame, text="Runes", width = 10, height = 10, font=('Helvetica', 18, 'bold'))
    label_Runes.place(x = 20, y=10)

    label_Souls = customtkinter.CTkLabel(Prices_frame, text="Souls", width = 10, height = 10, font=('Helvetica', 18, 'bold'))
    label_Souls.place(x = 140, y=10)

    label_Relics = customtkinter.CTkLabel(Prices_frame, text="Relics", width = 10, height = 10, font=('Helvetica', 18, 'bold'))
    label_Relics.place(x = 260, y=10)

    label_Shards = customtkinter.CTkLabel(Prices_frame, text="Shards", width = 10, height = 10, font=('Helvetica', 18, 'bold'))
    label_Shards.place(x = 380, y=10)



    #misc_frame Labels
    tax_rate = customtkinter.CTkLabel(misc_frame, text="TAX Rate:", width = 10, height = 10, font=('Helvetica', 12, 'bold'))
    tax_rate.place(x = 10, y = 18 )
    profit_margin = customtkinter.CTkLabel(misc_frame, text="Profit Margin:", width = 10, height = 10, font=('Helvetica', 12, 'bold'))
    profit_margin.place(x = 10, y = 57 )



    #prices frame Entries

    entry_t4_runes = customtkinter.CTkEntry(Prices_frame, placeholder_text="T4 runes",width=100)
    entry_t4_runes.place(x = 20 , y = 55)

    entry_t4_souls = customtkinter.CTkEntry(Prices_frame, placeholder_text="T4 souls",width=100)
    entry_t4_souls.place(x = 140 , y = 55)

    entry_t4_relics = customtkinter.CTkEntry(Prices_frame, placeholder_text="T4 relics",width=100)
    entry_t4_relics.place(x = 260 , y = 55)

    entry_t4_shards = customtkinter.CTkEntry(Prices_frame, placeholder_text="T4 shards",width=100)
    entry_t4_shards.place(x = 380 , y = 55)



    entry_t5_runes = customtkinter.CTkEntry(Prices_frame, placeholder_text="T5 runes",width=100)
    entry_t5_runes.place(x = 20 , y = 95)

    entry_t5_souls = customtkinter.CTkEntry(Prices_frame, placeholder_text="T5 souls",width=100)
    entry_t5_souls.place(x = 140 , y = 95)

    entry_t5_relics = customtkinter.CTkEntry(Prices_frame, placeholder_text="T5 relics",width=100)
    entry_t5_relics.place(x = 260 , y = 95)

    entry_t5_shards = customtkinter.CTkEntry(Prices_frame, placeholder_text="T5 shards",width=100)
    entry_t5_shards.place(x = 380 , y = 95)



    entry_t6_runes = customtkinter.CTkEntry(Prices_frame, placeholder_text="T6 runes",width=100)
    entry_t6_runes.place(x = 20 , y = 135)

    entry_t6_souls = customtkinter.CTkEntry(Prices_frame, placeholder_text="T6 souls",width=100)
    entry_t6_souls.place(x = 140 , y = 135)

    entry_t6_relics = customtkinter.CTkEntry(Prices_frame, placeholder_text="T6 relics",width=100)
    entry_t6_relics.place(x = 260 , y = 135)

    entry_t6_shards = customtkinter.CTkEntry(Prices_frame, placeholder_text="T6 shards",width=100)
    entry_t6_shards.place(x = 380 , y = 135)



    entry_t7_runes = customtkinter.CTkEntry(Prices_frame, placeholder_text="T7 runes",width=100)
    entry_t7_runes.place(x = 20 , y = 175)

    entry_t7_souls = customtkinter.CTkEntry(Prices_frame, placeholder_text="T7 souls",width=100)
    entry_t7_souls.place(x = 140 , y = 175)

    entry_t7_relics = customtkinter.CTkEntry(Prices_frame, placeholder_text="T7 relics",width=100)
    entry_t7_relics.place(x = 260 , y = 175)

    entry_t7_shards = customtkinter.CTkEntry(Prices_frame, placeholder_text="T7 shards",width=100)
    entry_t7_shards.place(x = 380 , y = 175)



    entry_t8_runes = customtkinter.CTkEntry(Prices_frame, placeholder_text="T8 runes",width=100)
    entry_t8_runes.place(x = 20 , y = 215)

    entry_t8_souls = customtkinter.CTkEntry(Prices_frame, placeholder_text="T8 souls",width=100)
    entry_t8_souls.place(x = 140 , y = 215)

    entry_t8_relics = customtkinter.CTkEntry(Prices_frame, placeholder_text="T8 relics",width=100)
    entry_t8_relics.place(x = 260 , y = 215)

    entry_t8_shards = customtkinter.CTkEntry(Prices_frame, placeholder_text="T8 shards",width=100)
    entry_t8_shards.place(x = 380 , y = 215)

    entry_tax_rate = customtkinter.CTkEntry(misc_frame, placeholder_text="Tax rate (%)",width=125)
    entry_tax_rate.place(x = 70, y = 10 )
    entry_profit_margin = customtkinter.CTkEntry(misc_frame, placeholder_text="Profit Margin(%)",width=104)
    entry_profit_margin.place(x = 90, y = 50)

    btn = customtkinter.CTkButton(misc_frame, text="START",command=lambda: mp.Process(target=main,args=(btn,)).start(),fg_color="green",font=('Helvetica', 18, 'bold'))
    btn.place(x = 30 , y = 90)


    app.mainloop()
Traceback (most recent call last):
  File "C:UserspcAppDataLocalProgramsPythonPython311Libtkinter__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "C:UserspcDesktopcodeprogram 2.0venv002Libsite-packagescustomtkinterwindowswidgetsctk_button.py", line 554, in _clicked
    self._command()
  File "c:UserspcDesktopcodeprogram 2.0test.py", line 138, in <lambda>
    btn = customtkinter.CTkButton(misc_frame, text="START",command=lambda: mp.Process(target=main,args=(btn,)).start(),fg_color="green",font=('Helvetica', 18, 'bold'))
                                                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingprocess.py", line 121, in start
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingcontext.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingcontext.py", line 336, in _Popen
    return Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingpopen_spawn_win32.py", line 94, in __init__
    reduction.dump(process_obj, to_child)
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingreduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
TypeError: cannot pickle '_tkinter.tkapp' object
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingspawn.py", line 122, in spawn_main
    exitcode = _main(fd, parent_sentinel)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:UserspcAppDataLocalProgramsPythonPython311Libmultiprocessingspawn.py", line 132, in _main
    self = reduction.pickle.load(from_parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
EOFError: Ran out of input
Asked By: banom

||

Answers:

tkinter widgets cannot be shared across processes, so you need to disable and enable the button in current process. You can use .after() to check periodically whether the child process has finished or not.

Below is an example:

# function to be executed in child process
def main():
    import time

    for i in range(10):
        print("hello")
        time.sleep(1)

    print("done")

if __name__ == "__main__":
    import customtkinter as ctk
    import multiprocessing as mp

    def start(btn):
        # disable the button
        btn.configure(state="disabled", fg_color="red", text="Running")

        # start the child process
        p = mp.Process(target=main)
        p.start()

        # function to check whether child process has terminated or not
        def wait_for_process():
            if p.is_alive():
                # child process is still running, schedule next checking
                btn.after(100, wait_for_process)
            else:
                # child process has terminated, enable the button
                btn.configure(state="normal", text="START", fg_color="green")

        # start the periodic checking
        wait_for_process()

    root = ctk.CTk()
    btn = ctk.CTkButton(root, text="START", fg_color="green",
                        font=("Helvetica", 18, "bold"),
                        command=lambda: start(btn))
    btn.pack()
    root.mainloop()
Answered By: acw1668