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.
Related
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()
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
I am writing tkinter python program for turning on and off the publisher. I need to turn on and off the publisher using a button. If am writing using while loop, the entire gui freezes, I am unable to operate anything. Please give me some suggestions.
dummy = Button(master, text="dummy", relief='groove',command=self.pub_values)
def pub_values(self):
self.pub = rospy.Publisher('/gateway/RigPLC2Seq', Int32MultiArray, queue_size=10)
rospy.init_node('talker_int', anonymous=True)
rate = rospy.Rate(1) # 10hz
self.var = Int32MultiArray()
self.var.data = []
ls = self.extract_values()
self.var.data = ls
self.on = True
while self.on == True:
rospy.loginfo(self.var)
self.pub.publish(self.var)
rate.sleep()
Your solution is not far fetched.
you set self.on to True, then you have a while loop that checks to the value of self.on which if True should freeze(.sleep()) your GUI.
Your code is obeying just what you typed in.
Solution:
You must have a condition in your while loop that changes the value of self.on to False, if you don't the while loop will never stop being executed. Example:
counter=1
self.on = True
while self.on == True:
rospy.loginfo(self.var)
self.pub.publish(self.var)
rate.sleep()
if counter==10:
self.on=False
counter++
With the above code, I want the loop to run 10 times, after that self.on becomes False and the while loop exits, which also unfreezes your GUI(.sleep() no longer runs)
More importantly is that suggested by #j_4321 above, .sleep() freezes the GUI as long as it is running, you will be better served using the .after() method.
This should help you learn about the .after() method if you so desire to use it.
You can use after to replace the while loop. While self.on is true, publish is executed and rescheduled. The button command toggles self.on and relaunches publish when self.on is set back to true:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.on = False
self.button = tk.Button(self, text='Start', command=self.start_stop)
self.button.pack()
def start_stop(self):
if self.on:
self.button.configure(text='Start')
self.on = False # stop publish
else:
self.button.configure(text='Stop')
self.on = True
self.publish() # relaunch publish
def publish(self):
if self.on:
print('publish')
self.after(1000, self.publish)
App().mainloop()
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
so i have this code:
import thread
from Tkinter import *
import random
import time
Admin=Tk()
def moveit(number):
songas=Label(Admin,text=number,bg='red')
def ji():
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
thread.start_new_thread(ji,())
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()
And it starts to move to the left but if you press the 'press' button again it puts some more numbers on top of the old ones.
does any one know how to erase the old numbers to make it so there are only the knew ones?
Tkinter isn't thread safe -- you can't manipulate widgets in any thread except the main one or you'll get undefined results.
You don't need threads for this. Your code adds an infinite loop, but the application already has an infinite loop (the event loop) that you can take advantage of.
If you want to move some item create a function that does two things. First, it does whatever it is you want, such as move the item. Second, it uses the standard after method to call itself again in a short amount of time (for example, half a second or 500ms). This way you let your event loop drive the animation, you don't need threads, and your UI stays responsive.
Here's an example. I doubt it does exactly what you want because I'm not certain of exactly what you want.
import Tkinter as tk
import random
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
self._running = False
self._relx = None
tk.Tk.__init__(self, *args, **kwargs)
self.pack_propagate(False)
self.configure(width=400, height=400)
self.label = tk.Label(self, text="hello, world", background="red")
self.button = tk.Button(self, text="Start", command=self.toggle)
self.button.pack(side="top")
def toggle(self):
'''toggle animation on or off'''
self._running = not self._running
if self._running:
self.button.configure(text="Stop")
self.moveit()
else:
self.button.configure(text="Start")
def moveit(self):
'''Animate the label'''
if not self._running:
# animation has been stopped
# hide the label from view.
self.label.place_forget()
if self._running:
if not self.label.winfo_viewable():
# not visible; establish future locations
self._relx = [.5, .4, .3, .2, .1, 0]
relx = self._relx.pop(0)
self._relx.append(relx)
self.label.place(relx=relx, rely=0.7)
self.after(1000, self.moveit)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You must signal the old thread to exit somehow. It is probably easiest to perform with locks - you create a lock when creating a new thread and acquire it. And you release it when the thread is no longer needed. The thread then only needs to check in the main loop whether its lock is still locked - if it isn't it will remove the label and exit. Here the modified version of your code (replace "Remove label here" comment by suitable code):
import thread
from Tkinter import *
import random
import time
Admin=Tk()
lock = None
def moveit(number):
global lock
songas=Label(Admin,text=number,bg='red')
def ji(lock):
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
if not lock.locked():
# Remove label here
break
if lock:
# Signal old thread to exit
lock.release()
lock = thread.allocate_lock()
lock.acquire()
thread.start_new_thread(ji,(lock,))
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()