How to implement multithreading stop button with while loop in python tkinter - python

I am coding app for measure power during squat. Its taking data from arduino, then there is a method which calculating it on power. This calculations are in while loop. When i press start calculation button my app freeze. I am sure that i have to use Threads here. I was looking for some basic example how to do it, but without any success. This is how its looks atm:
1.Button:
btn = tk.Button(self, text="Zacznij pomiary", command=lambda: methods.clicked(self.txtEntry, self.txtEntry1, self.txtEntry2))
btn.grid(column=0, row=3)
Method: (read data is a function that making calculation each milisecond in while loop)
def clicked(a, b, c):
if len(a.get()) == 0 or len(b.get()) == 0 or len(c.get()) == 0:
popupmsg("Wprowadź poprawne dane!")
else:
readData(float(a.get()), float(b.get()), float(c.get()))
I am looking for some examples how to implement stop button here.

In clicked:
else:
global keepGoing
keepGoing = True
threading.Thread( target=readData, args=(float(a.get(),float(b.get(),float(c.get()),daemon=True)
In readData:
while keepGoing:
... do stuff ...
Then, make a stop button connected to:
def onStopButton():
global keepGoing
keepGoing = False

Related

Issue synchronizing multiple countdown timers with Python Tkinter

I'll start by saying that I'm completely new to tkinter and I've been trying to wrap my head around a minor issue I'm experiencing - it might be very simple/I'm approaching it incorrectly, but some insight would be very appreciated!
Essentially I have an app with multiple items, each item has a button. When you click a button for an item, it starts a countdown timer by calculating the duration in a function, then using .after(1000, ...) to refresh my label every second.
The issue is, when you click on a button and start a timer, it counts down every second from when the button is clicked. So if you click the next button "off cycle" from the last countdown, the countdown is NOT SYNCED up the way it should be.
Currently it counts down a second from when the button is pressed, instead of updating every "true second", if that makes sense. Any idea how I can approach this?
def handle_growth_time(n, harvest_time):
time_now = datetime.datetime.now()
time_now = time_now.replace(microsecond=0)
crop = allotments[n]
if not crop["reset"]:
if harvest_time >= time_now:
remaining_time = harvest_time - time_now
crop["remaining_label"].configure(text=remaining_time, font='ariel 12', foreground="black")
crop["remaining_label"].after(1000, handle_growth_time, n, harvest_time)
else:
crop["label_name"].configure(background="#86d474")
crop["active"] = False
else:
crop["reset"] = False
crop["active"] = False
I have tried a couple different approaches, such as when I capture time_now I grab time_now.microseconds/1000 and waiting for that before starting the countdown but I think working with time in that way is unreliable because I still can't really capture true :00.00
I also tried resetting all the timers when a new one is started, refreshing their 1 sec cycle, but I did so by going through an array and restarting them - as you can imagine this causes them to be offset still.
I recommend having only a single timekeeper that keeps track of passing seconds. Each timer can have a tick function that is called by this single timer. It is then responsible for updating all timers at the same time so that the update at the same time.
The following example illustrates the point. It's not production quality code, but it illustrates the point: multiple timer objects that all "tick" during a single time loop.
import tkinter as tk
class Timer(tk.Label):
def __init__(self, parent, seconds):
super().__init__(parent, text=str(seconds), width=8)
self.time = seconds
self.running = False
def tick(self):
self.time -= 1
self.configure(text=self.time)
if self.time == 0:
self.running = False
def start(self):
self.running = True
def stop(self):
self.running = False
class Timekeeper:
def __init__(self):
self.timers = []
def new_timer(self, seconds):
t = Timer(root, seconds)
t.pack(side="top", fill="x")
self.timers.append(t)
t.start()
def tick(self):
root.after(1000, self.tick)
# update existing timers
for timer in self.timers:
if timer.running:
timer.tick()
timekeeper = Timekeeper()
root = tk.Tk()
root.geometry("200x600")
button_15 = tk.Button(root, text="New 15 second timer", command=lambda: timekeeper.new_timer(15))
button_30 = tk.Button(root, text="New 30 second timer", command=lambda: timekeeper.new_timer(30))
button_15.pack(side="top")
button_30.pack(side="top")
# add a few timers as an example
timekeeper.new_timer(15)
timekeeper.new_timer(30)
timekeeper.new_timer(45)
timekeeper.new_timer(60)
root.after(1000, timekeeper.tick)
root.mainloop()

Program Crashing When Using Stop Button

I'm pretty new to python, but I have a working version of this in MATLAB but struggling to get the start/stop buttons to work in python.
I have tried to simplify the code I have posted below as much as I think I can, but basically I want to run a series of bleeps for a fitness test. There is a set order the beeps need to occur in order to signify different intensities of exercise.
I have used functions to write the different types of exercise (I haven't included all of that in the code below to save space), and then another to combine them into the desired protocol. Then I have created buttons to start and stop the protocol running.
The only thing I want to include that is not in the example I have put in what I have already tried is it must include a timer. The exercise sections are variable length but need to fit into a time period rather than a certain number of iterations.
I have already tried several answers on the site, the nearest one to my problem I think is;
Previous Answer
The first solution here posted by Mike - SMT works on my PC, it also looks a lot like what I am trying to achieve but I have tried to match it as closely as I can but the start button still remains depressed and the stop button crashes the work.
# Import all modules
import time
import winsound
import tkinter
# Define Global Variables
runTime = 20
numberBlocks = 5
atime = 1
tracker = False
# Define Movement Functions
def walk()
def jog()
def cruise()
def sprint()
def ar()
def rest()
# callback functions
def start():
global numberBlocks, runTime, atime, tracker
tracker = False
t = time.time()
i = 0
while i < numberBlocks - 1 and tracker == False:
while (time.time() - t) < runTime and tracker == False:
if tracker == False and (time.time() - t) < runTime:
walk()
if tracker == False and (time.time() - t) < runTime:
sprint()
if tracker == False and (time.time() - t) < runTime:
ar()
if tracker == False and (time.time() - t) < runTime:
jog()
if tracker == False and (time.time() - t) < runTime:
cruise()
rest()
i += 1
def stop():
global tracker
tracker = True
# run GUI
root = tkinter.Tk()
tracker = False
root.title('LIST')
# create all of the main containers
bottom_center_frame = tkinter.Frame(root)
bottom_center_frame.pack(side="top", fill="x")
# create widgets for the button frame
button_stop = tkinter.Button(bottom_center_frame, text='Stop', command=stop).pack(side="bottom", fill="x")
button_start = tkinter.Button(bottom_center_frame, text='Start', command=start).pack(side="bottom", fill="x")
# loop gui
root.mainloop()
The bleeps all work ok it just crashes when I try and stop it.
So, the main problem here is that once you click start and launch a command in the mainloop, tkinter will wait until that command is over before allowing you to do something else (in this case, press stop).
A possible solution is using .after() like this:
import tkinter as tk
class FitnessApp(tk.Tk):
def __init__(self, tracker):
tk.Tk.__init__(self)
self.runTime = 20
self.numberBlocks = 5
self.atime = 1
self.tracker = tracker
# create all of the main containers
self.bottom_center_frame = tk.Frame(self)
self.bottom_center_frame.pack(side="top", fill="x")
# create widgets for the button frame
self.button_stop = tk.Button(self.bottom_center_frame, text='Stop', command=self.stop)
self.button_stop.pack(side="bottom", fill="x")
self.button_start = tk.Button(self.bottom_center_frame, text='Start', command=self.start)
self.button_start.pack(side="bottom", fill="x")
def walk(self):
print("walking")
def start(self):
self.tracker = False
self.move()
def stop(self):
print("Stopping run")
self.tracker = True
def move(self):
if self.tracker is False:
self.walk()
self.after(1000, self.move)
# run GUI
tracker = False
root = FitnessApp(tracker)
root.title('LIST')
# loop gui
root.mainloop()
When start is clicked, the tracker is set to False, and then the move function is called, which prints "walking".
Then the self.after(1000, self.move) recursively calls the move function every 1000 ms, which allows you to press the stop button, set the tracker to True and stop the simulation.
For more complicated situations, I recommend that you use the threading module, which allows you to launch your simulation in a secondary thread while keeping control over the main loop. This way you can use a variable to track the progress of your simulation and act accordingly.

How to break out of while loop with button release

I have a button that when pressed is supposed to keep printing a certain phrase. However I have an if statement that's supposed to break out of the while loop if it reads that the button is released. Essentially, I'm trying to create a gui that, when a specific button is pressed it continues to act out a function until that button is released in tkinter. I believe that there should be a statement which reads the state of the button and knows when the button is released, but I don't know what it is.
self.button.pack(side="top")
self.vsb.pack(side="right", fill="y")
self.text.pack(side="bottom", fill="x")
self.button.bind("<ButtonPress>", self.on_press)
self.button.bind("<ButtonRelease>", self.on_release)
def on_press(self, event):
while True:
time.sleep(1)
self.log("button was pressed")
if (what do I put here):
break
you can't enter a dead loop while handling an even. try using threads instead.
here is a VERY simple example:
def on_press(self, event):
self._running = True
threading.Thread(self.my_loop).start()
def my_loop(self):
while self.running:
time.sleep(1)
print("loop is done")
def on_release(self, event):
self._running = False # this will stop the loop in the other thread...

How to interrupt a thread/process by making a correct "Stop" button in Tkinter (Python)?

I want some basics on the problem of making some sort of "Stop" button that in my case terminates the series of beeps:
from tkinter import *
import winsound
from random import randint
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.widgets()
def widgets(self):
self.beep = Button(self, text = "Beep", command = play_beep)
self.beep.pack()
self.stop = Button(self, text = "Stop", command = stop_beep)
self.stop.pack()
go_on = True
def play_beep():
count = 10
while go_on == True and count != 0:
winsound.Beep(randint(100, 2500), 200)
count -= 1
def stop_beep():
go_on = False
root = Tk()
app = App(root)
root.mainloop()
When I press the "Beep" button it gets stuck as well as all the GUI until the beeps end. Could anyone tell me how to fix it?
I don't use TKinter, but I believe your button press is not creating a separate thread or process. The reason why your button gets stuck is because your play_beep loop is blocking your GUI execution loop. So we use threading. The thread executes at the same time as your GUI, so you can basically do two things at once (listen for GUI events and play beep noises).
import threading
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.is_playing = False
self.pack()
self.widgets()
def widgets(self):
self.beep = Button(self, text = "Beep", command = self.play_beep)
self.beep.pack()
self.stop = Button(self, text = "Stop", command = self.stop_beep)
self.stop.pack()
def play_beep(self):
self.is_running = True
self.beep_th = threading.Thread(target=self.run)
self.beep_th.start()
def run(self):
count = 10
while self.is_running == True and count != 0:
winsound.Beep(randint(100, 2500), 200)
count -= 1
def stop_beep(self):
try:
self.is_running = False
self.beep_th.join(0)
self.beep_th = None
except (AttributeError, RuntimeError): # beep thread could be None
pass
def closeEvent(self, event): # This is a pyside method look for a TKinter equivalent.
"""When you close the App clean up the thread and close the thread properly."""
self.stop_beep()
super().closeEvent(event)
First off, your question has nothing to do about threads or processes. Tkinter is single-threaded.
If you want to run some function periodically in a tkinter program, you must give the event loop a chance to process events. The typical solution is to do it like this:
def play_beep(count=10):
if go_on and count != 0:
winsound.Beep(randint(100, 2500), 200)
root.after(1000, play_beep, count=1)
This will cause the beep to play every second (1000ms) for ten iterations. In between each call, the event loop will have a chance to process other events.
Now, if the code you are running takes a long time, you're going to have to run that code in a separate thread or process. I know nothing about winsound.Beep so I don't know if that's necessary or not.
Second, to be able to interrupt it, you need to make go_on global, otherwise you're simply setting a local variable that never gets used.
def stop_beek():
global go_on
go_on = False

Run an infinite loop in the backgroung in Tkinter

I would like the code to run in the background and to update my GUI periodically. How can I accomplish this?
For example, suppose I want to execute something like this in the background of the GUI code you can see below:
x = 0
while True:
print(x)
x = x + 1
time.sleep(1)
This is the GUI code:
class GUIFramework(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.title("Volume Monitor")
self.grid(padx=10, pady=10,sticky=N+S+E+W)
self.CreateWidgets()
def CreateWidgets(self):
textOne = Entry(self, width=2)
textOne.grid(row=1, column=0)
listbox = Listbox(self,relief=SUNKEN)
listbox.grid(row=5,rowspan=2,column=0,columnspan=4,sticky=N+W+S+E,pady=5)
listbox.insert(END,"This is an alert message.")
if __name__ == "__main__":
guiFrame = GUIFramework()
guiFrame.mainloop()
It is a little unclear what your code at the top is supposed to do, however, if you just want to call a function every second (or every the amount of seconds you want), you can use the after method.
So, if you just want to do something with textOne, you'd probably do something like:
...
textOne = Entry(self, width=2)
textOne.x = 0
def increment_textOne():
textOne.x += 1
# register "increment_textOne" to be called every 1 sec
self.after(1000, increment_textOne)
You could make this function a method of your class (in this case I called it callback), and your code would look like this:
class Foo(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.x = 0
self.id = self.after(1000, self.callback)
def callback(self):
self.x += 1
print(self.x)
#You can cancel the call by doing "self.after_cancel(self.id)"
self.id = self.after(1000, self.callback)
gui = Foo()
gui.mainloop()
If you truly want to run a distinct infinite loop you have no choice but to use a separate thread, and communicate via a thread safe queue. However, except under fairly unusual circumstances you should never need to run an infinite loop. Afte all, you already have an infinite loop running: the event loop. So, when you say you want an infinite loop you are really asking how to do an infinite loop inside an infinite loop.
#mgilson has given a good example on how to do that using after, which you should consider trying before trying to use threads. Threading makes what you want possible, but it also makes your code considerably more complex.

Categories