Tkinter is opening new windows when running a function in a thread - python

Hi all I am using python 2.7.15 and tkinter. It is a simple GUI with some buttons. Once a button is pressed I need to start a function in a thread (I do not need to open any new windows).
What is happening is for each thread a new GUI copy of the program is opened. Is there any way to start a function (that does some calculations) without popping up a new copy of the Tkinter gui?
I am making a thread like this:
thread = Process(target=functionName, args=(arg1, arg2))
thread.start()
thread.join()
EDIT: here is some code to reproduce. As you can see, all I am interested in below "sample" is to run one function. Not to clone the whole program.
from Tkinter import *
from multiprocessing import Process
window = Tk()
window.title("Test threadinng")
window.geometry('400x400')
def threadFunction():
sys.exit()
def start():
thread1 = Process(target=threadFunction)
thread2 = Process(target=threadFunction)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
btn = Button(window, text="Click Me", command=start, args=())
btn.grid(column=1, row=1)
window.mainloop()
Thank you.

Since the child process will inherit resource from parent process, that means it will inherit tkinter from parent process. Put the initialization of tkinter inside if __name__ == '__main__' block may solve the problem:
from tkinter import *
from multiprocessing import Process
import time
def threadFunction():
print('started')
time.sleep(5)
print('done')
def start():
thread1 = Process(target=threadFunction)
thread2 = Process(target=threadFunction)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
if __name__ == '__main__':
window = Tk()
window.title("Test threadinng")
window.geometry('400x400')
btn = Button(window, text="Click Me", command=start)
btn.grid(column=1, row=1)
window.mainloop()

Related

python tkinter: Calling a objects method on button click does not work

here is my sample code:
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(5)
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=action.do_something()).start())
button.grid(row=1, column=0)
root.mainloop()
What am I expecting?
I'm expecting that as soon as I click the button in the UI an new thread is running, which is looping in the background and does not interfere with the UI (or later maybe other threads doing tasks in the background)
What is really happening?
When running the code, the method of the class is executeds immdiately and locking the procedure. root.mainloop() is never reached and therefore no UI is drawn
Alternatively I tried the following change:
button = tk.Button(root, text='pressme harder', command=threading.Thread(target=lambda: action.do_something()).start())
This behaves in the following (imho wrong) way:
The method is also called immediately, without pressing the button. This time the UI is drawn but seems the be locked by the thread (UI is slow/stuttering, pressing the buttom does not work most of the time)
Any Idea whats wrong there? Or how do I handle this in a more stable way?
You shouldn't try to start a thread directly in the button command. I suggest you create another function that launches the thread.
from time import sleep
import tkinter as tk
import threading
class Action:
counter = 0
def do_something(self):
while True:
print('Looping')
sleep(2)
print("Finished looping")
def start_thread(self):
thread = threading.Thread(target=self.do_something, daemon=True)
thread.start()
action = Action()
root = tk.Tk()
button = tk.Button(root, text='pressme harder', command=action.start_thread)
button.grid(row=1, column=0)
root.mainloop()

Interrupting a script called within another script in tkinter

I have a program (say p1.py) which calls another python script (say p2.py) on click of a button. I would like to have a button which stops the execution of the p2.py but all the buttons freeze when it is running.
The only way to stop it is to use a keyboard interrupt in the console. I have read about the after() function but do I have to implement it in p1 or p2? Or is there any other way to do it without the after() function?
import tkinter
import os
window = tkinter.Tk()
window.title("Detecting")
def clicked():
os.system('python extract_frames.py')
bt = tkinter.Button(window,text="Start",command=clicked)
bt.pack()
stop = tkinter.Button(window,text="Stop",command="break") #also what command should I use for the interrupt?
stop.pack()
window.geometry('400x400')
window.mainloop()
You should use subprocess.Popen() instead of os.system():
import tkinter
import subprocess
proc = None
def clicked():
global proc
proc = subprocess.Popen(['python', 'extract_frames.py'])
def kill_task():
if proc and proc.poll() is None:
print('killing process ...')
proc.kill()
window = tkinter.Tk()
window.geometry('400x400')
window.title("Detecting")
bt = tkinter.Button(window, text="Start", command=clicked)
bt.pack()
stop = tkinter.Button(window, text="Stop", command=kill_task)
stop.pack()
window.mainloop()

How to terminate two process with tkinter button in Python

I want to create two processes with python script. There is need to start and stop those processes via tkinter button. Processes start correctly, but terminate not. Which simplest way to correctly terminate processes with tkinter button?
Which best-practice way?
from tkinter import *
import multiprocessing
def print1():
global a
while a == True:
print('im process 1')
def print2():
global a
while a == True:
print('im process 2')
def start():
process1.start()
process2.start()
def stop():
global a
a = False
a = True
if __name__ == '__main__':
process1 = multiprocessing.Process(target = print1)
process2 = multiprocessing.Process(target = print2)
root = Tk()
root.title("Title")
root.geometry("200x200")
app = Frame(root)
app.grid()
start = Button(app, text="Start", command=start)
stop = Button(app, text="Stop", command=stop)
start.grid()
stop.grid()
root.mainloop()
process1.join()
process2.join()
The problem appears to be in the stop method.
I think I might know what the problem is, but I'm not 100% sure. The answer to why appears to be in the python documentation. This code here runs fine (I edited the syntax and used tkk for the buttons, it looks better):
from tkinter import *
from tkinter import ttk
import multiprocessing
def print1():
global a
while a is True:
print('im process 1')
def print2():
global a
while a is True:
print('im process 2')
def start():
process1.start()
process2.start()
def stop():
process1.kill()
process2.kill()
a = True
if __name__ == '__main__':
process1 = multiprocessing.Process(target=print1)
process2 = multiprocessing.Process(target=print2)
root = Tk()
root.title("Title")
root.geometry("200x200")
app = Frame(root)
app.grid()
start = ttk.Button(app, text="Start", command=start)
stop = ttk.Button(app, text="Stop", command=stop)
start.grid(padx=15, pady=20)
stop.grid(column=1, row=0)
root.mainloop()
process1.join()
process2.join()
Hope this helps!
Ordinary variables are not shared between instances of a multiprocessing.Process.
This means your:
global a
is a different global variable in each process, separate from the third global a in your main Python program. So when you use the tk button to set that last a to False, the a in the process remembered through process1 is still True, as is the a in the process remembered through process2.
You can share variables across processes. There are two ways to do that: via Manager instances, or via shared memory. Shared memory is more efficient, but more difficult to use, and sometimes has OS dependencies, so if you don't need particularly high performance, consider using a Manager.

Threads and tkinter

I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.

Tkinter: ProgressBar with indeterminate duration

I would like to implement a progress bar in Tkinter which fulfills the following requirements:
The progress bar is the only element within the main window
It can be started by a start command without the need of pressing any button
It is able to wait until a task with unknown duration is finished
The indicator of the progress bar keeps moving as long as the task is not finished
It can be closed by a stop command without the need of pressing any stop bar
So far, I have the following code:
import Tkinter
import ttk
import time
def task(root):
root.mainloop()
root = Tkinter.Tk()
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.after(0,task(root))
time.sleep(5) # to be replaced by process of unknown duration
root.destroy()
Here, the problem is that the progress bar does not stop after the 5s are over.
Could anybody help me finding the mistake?
Once the mainloop is active, the script wont move to the next line until the root is destroyed.
There could be other ways to do this, but I would prefer doing it using threads.
Something like this,
import Tkinter
import ttk
import time
import threading
#Define your Progress Bar function,
def task(root):
ft = ttk.Frame()
ft.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD = ttk.Progressbar(ft, orient='horizontal', mode='indeterminate')
pb_hD.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb_hD.start(50)
root.mainloop()
# Define the process of unknown duration with root as one of the input And once done, add root.quit() at the end.
def process_of_unknown_duration(root):
time.sleep(5)
print 'Done'
root.destroy()
# Now define our Main Functions, which will first define root, then call for call for "task(root)" --- that's your progressbar, and then call for thread1 simultaneously which will execute your process_of_unknown_duration and at the end destroy/quit the root.
def Main():
root = Tkinter.Tk()
t1=threading.Thread(target=process_of_unknown_duration, args=(root,))
t1.start()
task(root) # This will block while the mainloop runs
t1.join()
#Now just run the functions by calling our Main() function,
if __name__ == '__main__':
Main()
Let me know if that helps.

Categories