Starting and stopping a thread from a gui - python

I'm currently developing a program with a GUI to gather data from a sensor and visualise it within the GUI.
The data from the sensor is stored in a list for further calculations.
What I'm currently trying to do, is starting the logging process in a new thread.
This should stop the GUI from freezing.
My current code:
import tkinter as tk
import time
import threading
class GUI:
def __init__(self, tk_object):
self.gui = tk_object
self.gui.title("Logger")
self.gui.resizable(width=True, height=True)
self.testlabel = tk.Label(self.gui, text="Recording in process")
self.testlabel.grid(row = 7, column = 0)
self.testlabel.config(bg='white')
btn1 = tk.Button(master, text="Start Recording", width=16, height=5, command=lambda: self.start_functions())
btn1.grid(row=2,column=0)
btn2 = tk.Button(master, text="Stop Recording", width=16, height=5, command=lambda: self.stop_functions())
btn2.grid(row=3,column=0)
def start_functions(self):
"""Calls the method get_sample in a new thread and changes the bg-color of the label to red"""
Thread_1 = threading.Thread(target=self.get_sample(), name='Thread1')
Thread_1.start()
self.testlabel.config(bg='red')
def stop_functions(self):
"""Stops all active threads and changes bg-color of the label back to white"""
#stop_threads = threading.Event()
#stop_threads.set()
threading.Event().set()
self.testlabel.config(bg='white')
def get_sample(self):
"""get data and append it to a list"""
while not threading.Event().is_set():
time.sleep(1)
res_cel.append(res_cel[-1]+1)
x_value.append(x_value[-1]+1)
print(res_cel)
print(x_value)
master = tk.Tk()
master_object = GUI(master)
master.mainloop()
Currently, the get_sample method contains a placeholder.
I'm trying to stop the Thread1 (where the get_sample method is running in) via the Event() handler.
while not threading.Event().is_set():
This doesn't seem to work in a proper way.
Are there better ways to do this?
Before this approach I tried using a class to handle the threads (This was found on stackoverflow, but I can't seem to find it anymore, sorry):
class Controller(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def run(self):
.. do somehting
and starting / stopping the threads via:
def starting_thread(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.run)
self.thread1.start()
def stopping_thread(self):
self.stop_threads.set()
self.thread1 = None
Note: These functions are within the Controller Class.
With this solution I wasn't able to alter the lables background color in the GUI Class, since it doesn't know what object it is referencing to.
Im fairly new to programming python so I'd be glad if someone could explain to me what I'm missing here.
Thanks
xSaturn

You'd better create your own Thread object with a method to stop it. A thread stop running when it goes of its run method. Look at the example bellow
import time
import tkinter as tk
from threading import Thread
class MyThread(Thread):
thread_running = False
def run(self):
k = 0
self.thread_running = True
while self.thread_running:
print(k)
k +=1
time.sleep(1)
print("thread ended")
def stop(self):
self.thread_running = False
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.thread = None
tk.Button(self, text="launch thread", command=self.launch_thread)\
.grid(row=1, column=0)
tk.Button(self, text="stop thread", command=self.stop_thread)\
.grid(row=2, column=0)
def launch_thread(self):
if self.thread:
print("thread already launched")
else:
print("thread launched")
self.thread = MyThread()
self.thread.start()
def stop_thread(self):
if self.thread:
self.thread.stop()
self.thread = None
else:
print("no thread running")
if __name__ == '__main__':
win = Window()
win.mainloop()
See this topic for more information
Is there any way to kill a Thread?

Related

python tkinter- more than one windows to show and do different processes

I have a GUI application. It has more than one windows. Main windows is updating every second with new values. I am using threading and queue here. There is a button in the main window. Pressing it will open a new window. What I want is, when new window is shown, stop the process in the main window (updating the values process) and some new process will be shown in the new window. Closing the new window should revoke the main window again and continue the process. Do i need to use multi threading or multi processing?
Unfortunately, I am asking this question for the second time. sorry for that.
import tkinter
import time
import threading
import random
import queue
class GuiPart:
def __init__(self, master, queue, endCommand,newWindow):
self.queue = queue
self.pause = False
# Set up the GUI
console = tkinter.Button(master, text='Done', command=endCommand)
console.pack()
console2 = tkinter.Button(master, text='New', command=newWindow)
console2.pack()
self.output = tkinter.StringVar()
#output.set(1)
output_1_label = tkinter.Label(master, textvariable= self.output, height=2, width=12)
output_1_label.pack()
# Add more GUI stuff here
self.temp_process()
def temp_process(self):
if not self.pause:
print ("handling messages")
else:
print ("Not handling messages")
root.after(1000,self.temp_process)
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
print (msg)
self.output.set(msg)
except queue.Empty:
pass
class ThreadedClient:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.endApplication,self.create_window)
self.running = 1
self.thread1 = threading.Thread(target=self.workerThread1) #this is for sending data to queue.
# what about second window?
self.thread1.start()
self.periodicCall()
def on_quit(self):
self.gui.pause = False
self.window.destroy()
def create_window(self):
self.window = tkinter.Toplevel(root)
self.gui.pause = True
self.window.protocol("WM_DELETE_WINDOW",self.on_quit)
def periodicCall(self):
self.gui.processIncoming()
if not self.running:
import sys
sys.exit(1)
self.master.after(1000, self.periodicCall)
def workerThread1(self):
while self.running:
time.sleep(rand.random() * 1)
msg = rand.random()
self.queue.put(msg)
def endApplication(self):
self.running = 0
rand = random.Random()
root = tkinter.Tk()
client = ThreadedClient(root)
root.mainloop()
You can have your function processIncoming check for a flag, and pause/resume when required.
class GuiPart:
def __init__(self, master, queue, endCommand,newWindow):
self.queue = queue
self.pause = False
....
def processIncoming(self):
while self.queue.qsize() and not self.pause:
try:
msg = self.queue.get(0)
print (msg)
self.output.set(msg)
except queue.Empty:
pass

What is the best way to repeatedly execute a function every few seconds, without intterupting other functions, for a Python gui?

I want to repeatedly run a function that retrieves data from an API every 60 seconds. However, I've realized that I can't put it to sleep, or use a while loop, without interrupting the other functions.
My question is, how do I repeatedly retrieve the data from the API/rerun the code that gets the data in the first place, without interrupting the other functions?
below a complete script that retrieve ISS position longitude and latitude from Open Notify and print it on a thread.
I' ve set a 1 second time. You can start and stop the thread. Try now you to add your funcions.
Notice that you have to import this modules
import requests
import json
import tkinter as tk
import threading
import queue
import datetime
import time
import requests
import json
class MyThread(threading.Thread):
def __init__(self, queue,):
threading.Thread.__init__(self)
self.queue = queue
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
response = requests.get("http://api.open-notify.org/iss-now.json")
data = response.json()
time.sleep(1)
self.queue.put(data)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Button(w, text="Start", command=self.launch_thread).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def launch_thread(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if self.my_thread is not None:
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
print("The ISS is currently over: {0} ".format(ret['iss_position']))
except queue.Empty:
pass
def on_close(self):
if self.my_thread is not None:
if(threading.active_count()!=1):
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Tkinter: Separating Processes

I am trying to build a Tkinter program that supports multiprocessing. I need to read from multiple Modbus devices and display the output onto the GUI.
I have successfully done this with a command line by using processes, but in Tkinter, my GUI freezes every time I attempt to read.
Here is my code:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.process)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def reader():
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
When I press the start button the program will freeze until the process is done. How do I correctly set up a system to have the GUI operational while having processes run?
The simplest solution would be to put this all on a separate thread. The reason your GUI freezes up is because the process method has consumed the Main Thread and until it completes, Tkinter won't update anything.
Here's a simple example:
self.timer_button = tk.Button(self, text='Start', command=lambda: threading.Thread(target=self.process).start())
However, this doesn't stop the user from clicking the button twice. You could create a new method which controls this. Example:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
THREAD_LOCK = threading.Lock()
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.start_thread)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def check_thread(self):
if self.thread.is_alive():
root.after(50, self.check_thread)
else:
# Thread completed
self.timer_button.config(state='normal')
def start_thread(self):
self.timer_button.config(state='disabled')
self.thread = threading.Thread(target=self.process)
self.thread.start()
root.after(50, self.check_thread)
def reader(self):
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
with THREAD_LOCK:
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
The Lock is to ensure no two threads are running at the same time. I've also disables the button so that the user can't click until it's done. By using the root.after method, you can create callbacks that wait for a period of time before running.
As far as the multiprocessing, you are running the processes on a separate process, but only one at a time. If you want to run many at one time, then you need to move the join call somewhere else. I'm not sure how many processes are running at once but you could do something like this:
processes = []
for sen in self.gas_list:
proc = Process(target=self.reader, args=(sen,))
processes.append(proc)
proc.start()
[x.join() for x in processes]
In this implementation, I've removed assigning proc as a class variable.
I do not have the libraries or data to properly test all this, but it should work...
EDIT
This will initiate a pool of 6 processes that loop through self.gas_list, passing the item to self.reader. When those complete, it will check to make sure a second has gone by (waiting if it hasn't) and restarting the above process. This will run forever or until an exception is raised. You will need to import Pool from the multiprocessing module.
def process(self):
with THREAD_LOCK:
pool = Pool(6)
while 1:
start_time = time.time()
pool.map(self.reader, self.gas_list)
execution_time = time.time() - start_time
if execution_time < 1:
time.sleep(1-execution_time)

Create a tkinter window every x minutes and then automatically close it after y seconds

I'm trying to build a simple program to remind me to take breaks while using the computer. I have a reasonable understanding of python but have never played with GUI programming or threading before, so the following is essentially copying/pasting from stackoverflow:
import threading
import time
import Tkinter
class RepeatEvery(threading.Thread):
def __init__(self, interval, func, *args, **kwargs):
threading.Thread.__init__(self)
self.interval = interval # seconds between calls
self.func = func # function to call
self.args = args # optional positional argument(s) for call
self.kwargs = kwargs # optional keyword argument(s) for call
self.runable = True
def run(self):
while self.runable:
self.func(*self.args, **self.kwargs)
time.sleep(self.interval)
def stop(self):
self.runable = False
def microbreak():
root = Tkinter.Tk()
Tkinter.Frame(root, width=250, height=100).pack()
Tkinter.Label(root, text='Hello').place(x=10, y=10)
threading.Timer(3.0, root.destroy).start()
root.mainloop()
return()
thread = RepeatEvery(6, microbreak)
thread.start()
This gives me the first break notification but fails before giving me a second break notification.
Tcl_AsyncDelete: async handler deleted by the wrong thread
fish: Job 1, “python Documents/python/timer/timer.py ” terminated by
signal SIGABRT (Abort)
Any ideas? I'm happy to use something other than tkinter for gui-stuff or something other than threading to implement the time stuff.
Based on the answers below, my new working script is as follows:
import Tkinter as Tk
import time
class Window:
def __init__(self):
self.root = None
self.hide = 10 #minutes
self.show = 10 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#Tk.Button(text = 'click to end', command = self.close).pack()
self.root.after(self.show*1000, self.loop)
def loop(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().loop()
I think it would be easier for you to achieve this without threads, which Tkinter does not integrate with very well. Instead, you can use the after and after_idle methods to schedule callbacks to run after a certain timeout. You can create one method that shows the window and schedules it to be hidden, and another that hides the window and schedules it to be shown. Then they'll just call each other in an infinite loop:
import tkinter
class Reminder(object):
def __init__(self, show_interval=3, hide_interval=6):
self.hide_int = hide_interval # In seconds
self.show_int = show_interval # In seconds
self.root = Tkinter.Tk()
tkinter.Frame(self.root, width=250, height=100).pack()
tkinter.Label(self.root, text='Hello').place(x=10, y=10)
self.root.after_idle(self.show) # Schedules self.show() to be called when the mainloop starts
def hide(self):
self.root.withdraw() # Hide the window
self.root.after(1000 * self.hide_int, self.show) # Schedule self.show() in hide_int seconds
def show(self):
self.root.deiconify() # Show the window
self.root.after(1000 * self.show_int, self.hide) # Schedule self.hide in show_int seconds
def start(self):
self.root.mainloop()
if __name__ == "__main__":
r = Reminder()
r.start()
I agree with dano. I thought i'd also contribute though, as my way is somewhat smaller than dano's, but uses time for the gap between when the window is visible. hope this helps #vorpal!
import Tkinter
import time
root = Tkinter.Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'click to shutdown', command = close)
def hide():
root.withdraw()
time.sleep(10)
show()
button = Tkinter.Button(text = 'click for hide for 10 secs', command = hide)
button.pack()
root.mainloop()

Python TKinter Threading Help Required

I am trying to create a GUI that communicates with a RS232 serial object. I'll present an analogous scenario to the problem that I am facing. I want to create a frame with 2 buttons, Start and Stop. The start button calls the 'foo' function:
status = True
def foo():
n = 0
while(getStatus()):
print n
n += 1
sleep(0)
This foo function keeps running until I press stop.
def getStatus():
return status
def stop():
status = False
I understand Tkinter is single-threaded and once I press 'Start', the GUI will freeze. I know this is possible with the after function, but i strictly want to use threading. Is this possible with threading? If so can you please provide a sample code? Thank you.
here is some (not yet perfect) code:
What is missing/broken, but you did not ask for this, I added links:
It does not use locks => the calls to set might brake since they can occur at the same time. read the docs (this is quite easy)
It updates the gui from another thread. see 1 2
possibly more (not a threading guru)
Also for stopping threads look here
import time
import tkinter
from tkinter import ttk
import threading
#gui
root = tkinter.Tk()
root.title("Threading demo")
status = tkinter.StringVar()
elapsed = tkinter.StringVar()
error = tkinter.StringVar()
#thread
class timer(threading.Thread):
def __init__(self):
super().__init__()
self.stopped = False
#your code here, don't need init if you have no code
def run(self):
status.set('running')
while not self.isStopped():
time.sleep(1)
try:
oldtime = int(elapsed.get())
except ValueError:
oldtime = 0
elapsed.set(oldtime+1)
status.set('stopped')
time.sleep(2)
def isStopped(self):
return self.stopped
def stop(self):
self.stopped = True
#starts/stops thread (manages it)
class threadedOp(object):
def __init__(self):
self.thread = None
def run(self):
if self.thread == None:
self.thread = timer()
status.set('starting')
self.thread.start()
else:
error.set('Thread already running')
def stop(self):
if self.thread != None:
status.set('stopping')
self.thread.stop()
self.thread.join()
error.set('Join complete')
self.thread = None
else:
error.set('No thread to stop')
op = threadedOp()
#remaining gui
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
ttk.Label(mainframe, textvariable=elapsed).grid(column=1, row=1, sticky=(tkinter.W, tkinter.E))
ttk.Label(mainframe, textvariable=status).grid(column=2, row=1, sticky=(tkinter.W, tkinter.E))
ttk.Label(mainframe, textvariable=error).grid(column=1, row=3, sticky=(tkinter.W, tkinter.E))
ttk.Button(mainframe, text="Start", command=op.run).grid(column=1, row=2, sticky=tkinter.W)
ttk.Button(mainframe, text="Stop", command=op.stop).grid(column=2, row=2, sticky=tkinter.W)
root.mainloop()

Categories