I have a tkinter GUI, and am interested in adding a threading component to prevent my program from freezing. Here is my code:
from tkinter import *
import os
import threading
root = Tk()
root.title('Calculation Program')
root.geometry('700x525')
def param_log_export():
to_discover = disc_mode_choice.get()
def RunCalculation():
os.system('python command_center.py')
def combine_funcs(*funcs):
def combined_func(*args, **kwargs):
for f in funcs:
f(*args, **kwargs)
return combined_func
run_click_frame= Canvas(root, width= 450, height= 525)
save_button = Button(run_click_frame, width=450,height=1,text='Run Analysis!',fg='white',relief='flat',borderwidth=5,
bg='#2F4FAA',font=(Font_tuple,15),command = threading.Thread(target=combine_funcs(param_log_export,RunCalculation)).start())
save_button.pack(side=BOTTOM)
root.mainloop()
Essentially when the button is clicked, another script is called, and that script calls all of the smaller scripts for a certain calculation:
from target_list_create import mh_table
from ion_list_format import ion_mod_full
from theo_list_generation import theo_list_record
from precursor_fragment_matching import precursor_matches
Previously, I was just using command=combine_functs(param_log_export,RunCalculation), which worked fine, other than beginning to freeze as the size of my calculations grows. So, now I am trying the threading approach using the above code, but before I even have the opportunity to click the button which would command the threaded script, the program is running as though the button was clicked immediately. The console returns: UnboundLocalError: local variable 'fdr_algorithm_report' referenced before assignment, which indicates that there is a leak and the program triggers the combine_functs function as soon as the GUI is executed.
If anyone could help me understand what the issue is, so that when I click the button it executes the command without freezing the program, I would be very appreciative.
Related
I have problems setting up an app that uses a tkinter GUI and multiprocessing. It boils down to this example:
import tkinter
import time
import multiprocessing
import threading
def poolWorker(x):
time.sleep(x / 100)
def initPool():
for x in multiprocessing.Pool(32).imap_unordered(poolWorker, list(range(100))):
None
button["text"] = ("finished")
def buttonCommand():
button["text"] = "working"
threading.Thread(target = initPool).start()
if __name__ == "__main__":
root = tkinter.Tk()
button = tkinter.Button(root, text="Sleep", command = buttonCommand)
button.pack()
root.mainloop()
Here we basically just have a button that starts a new thread (so that the GUI stays responsive) which itself starts some "important" work on a multiprocessing pool.
This script works flawlessly when I run or debug it in VS Code, but when I use it as an .exe (created by Pyinstaller), clicking the button duplicates the number of windows by the number of processes in the pool which also never close, even after the pool finished.
The same behavior can also be seen in VS Code when the stuff from tkinter isn't inside the if __name__ == "__main__": block but outside. Unfortunately, except this information that doesn't apply to my problem I couldn't find anything about this behavior.
Why does in the code below button1 hang until the time.sleep(10) has completed.
I can only assume tKinter is waiting for the click event to finish before updating it's paint function.
I want on button1 click the state to change to DISABLED as in the code straight away, not when mainformbutton1press() has finished.
I have put time.sleep(10) to mimic rest of code functions - but the actual programme will be many minutes instead.
EDIT! - sleep is just there to show how tkinter hangs. My real programme has lots more code and no sleep function - and it takes a long time to process data with the hung GUI as mentioned. No more sleep suggestions please :)
import tkinter as tk
from tkinter import ttk
from tkinter.constants import DISABLED, NORMAL
import time
# ==================================================
class App:
def __init__(self, tk, my_w):
self.button1 = tk.Button(my_w, text="START", width=34, command = self.mainformbutton1press)
self.button1.grid(columnspan=3, row=6, column=1,padx=10,pady=20, ipadx=20, ipady=20)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(10)
print("doing...")
# ==================================================
if __name__ == "__main__":
my_w = tk.Tk()
my_w.geometry("430x380")
my_w.resizable(False, False)
app = App(tk, my_w)
my_w.mainloop() # Keep the window open
Tk.mainloop is a sort of while loop. time.sleep() stops the loop for a particular period of time. That makes the window unresponsive. You might use .after function:
class App:
def __init__(self, tk, my_w):
self.my_w=my_w
....
def continue_next(self):
print("Doing")
....
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
self.my_w.after(10000,self.continue_next)
The only change you need to make to your code is to insert an update to your button.
The 10 second delay might need to be shortened (10 seconds is a long time to wait)
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# INSERT UPDATE HERE
self.button1.update()
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(1)
print("doing...")
With this code I was able to create a TK Inter pop-up with a button to run a Sample_Function.
This Sample_Function destroys the tk pop-up, runs another python file, and then opens itself (the first pop-up) again.
How can I run the other_python_file and pop-up 'itself' at the same time — so I can be able to trigger many functions before each one gets completed?
import sys, os
from tkinter import *
import tkinter as tk
root = Tk()
def Sample_Function():
root.destroy()
sys.path.insert(0,'C:/Data')
import other_python_file
os.system('python this_tk_popup.py')
tk.Button(text='Run Sample_Function', command=Sample_Function).pack(fill=tk.X)
tk.mainloop()
I think this will do close to what you want. It uses subprocess.Popen() instead of os.system() to run the other script and rerun the pop-up which doesn't block execution while waiting for them to complete, so they can now execute concurrently.
I also added a Quit button to get out of the loop.
import subprocess
import sys
from tkinter import *
import tkinter as tk
root = Tk()
def sample_function():
command = f'"{sys.executable}" "other_python_file.py"'
subprocess.Popen(command) # Run other script - doesn't wait for it to finish.
root.quit() # Make mainloop() return.
tk.Button(text='Run sample_function', command=sample_function).pack(fill=tk.X)
tk.Button(text='Quit', command=lambda: sys.exit(0)).pack(fill=tk.X)
tk.mainloop()
print('mainloop() returned')
print('restarting this script')
command = f'"{sys.executable}" "{__file__}"'
subprocess.Popen(command)
Everyone! I first of all apologize for my lack of coding knowledge, I am currently attempting to learn Python on my own for "fun." My only formal education comes from a high-school Java AP course taken years ago.
I am currently using Python version 3.6 on the Windows 10 operating system, utilizing the PyCharm IDE.
On run my Tkinter GUI based application automatically executes an exit function that I defined under a class. The desired effect is for the window to close only when the user clicks the "terminate" button within the GUI window.
My code so far is as follows:
import webbrowser
import tkinter as ttk
from PIL import ImageTk, Image
##Application main window setup.
window = ttk.Tk()
window.maxsize(width=200, height=200)
window.minsize(width=200,height=200)
window.config(bg=("black"))
window.title("Hello World")
##Set a 'class' for exit function of application.
class Exit():
##Defines the countdown timer and sets parameters that need to be satisfied before exit.
def timer(self):
countdown = 3
self.x = int
for self.x in reversed(range(0,countdown + 1)):
print(self.x)
##When 'x' reahces -1 the application exits.
if self.x > -1:
print("Bye!")
window.destroy()
##Otherwise a label displaying a text message appears.
else:
swell = ttk.Label(text=("'Hello World!'"),bg=("black"),fg=("white"),font=("Times New Roman",12,"bold"))
swell.place(x=50,y=50)
##Retrieve the defined 'timer' function from the 'Exit' class.
exit=Exit()
exit.timer()
##Button with attahced command to execute the exit of application via user input.
quitButton=ttk.Button(
window,text=("Terminate"),bg=("red"),fg=("white"),font=("bold"),width=20,height=1,anchor=ttk.S,command=lambda: exit)
quitButton.place(x=6,y=150)
window.mainloop()
Any form of help is appreciated, and I thank you in advance.
*As a side note I can successfully issue a command from a button, however the retrieved function is only one line. It seems I cannot handle multiple lines of code.
I think what is happening is that you are destroying the window in the timer class method. After your for loop, x will equal 0. Therefore it is more than -1, and the window class is destroyed. Quitbutton trys to use window but it has been destroyed.
In the output I assume you are seeing 'Bye'
I got the correct result with the following:
import tkinter as ttk
from time import sleep
##Application main window setup.
window = ttk.Tk()
window.maxsize(width=200, height=200)
window.minsize(width=200, height=200)
window.config(bg=("black"))
window.title("Hello World")
##Set a 'class' for exit function of application.
class Exit():
"""
Defines the countdown timer and sets parameters
that need to be satisfied before exit.
"""
def __init__(self):
self.countdown = 3
swell = ttk.Label(text=("Hello World!"), bg=("black"),
fg=("white"), font=("Times New Roman", 12, "bold"))
swell.place(x=50,y=50)
def quit(self):
for iteration in reversed(range(0, self.countdown + 1)):
print(iteration)
sleep(1)
print("Bye!")
window.destroy()
##Retrieve the defined 'timer' function from the 'Exit' class.
exit=Exit()
##Button with attahced command to execute the exit of application via user input.
quitButton=ttk.Button(
window,text=("Terminate"), bg=("red"), fg=("white"), font=("bold"),
width=20, height=1, anchor=ttk.S, command=lambda: exit.quit())
quitButton.place(x=6,y=150)
window.mainloop()
You can see here I also used the init method in the exit class. Its a special kind of method which will auto run when the class is initiated.
It didn't require much changing. All I did was move the destroy window function into its own class method and had the second window instance command be set to run this method.
I want the method tryme to run only when I push the "snackPlay" button in the gui, but it runs as soon as I run the script. What can I do to make tryme run only on command?
Thanks.
import threading
from Tkinter import *
from tkSnack import *
class MyThread ( threading.Thread ):
def tryme ( self ):
print 'up uP UP'
root = Tk()
initializeSnack(root)
f = Frame(root)
f.pack()
Button(f, bitmap='snackPlay', command=MyThread().tryme()).pack(side='left')
root.mainloop()
I don't know a lot about threading, but you should try command = MyThread().tryme instead of command = MyThread().tryme() (it works for me after I remove all the tkSnack stuff).
Tkinter callbacks expect callable objects, not function results.