So I am making a stopwatch program with tkinter.
At the moment, I am in the process of implementing a stop button for the stopwatch.
I can start the stopwatch and it will update the Label fine but my end button isn't working and I can't seem to figure out why.
Here's my code so far:
from tkinter import *
from time import sleep
import threading
root = Tk()
root.title("Clock")
class Buttons:
def __init__(self, master):
self.menu = Menu(master)
master.config(menu=self.menu)
self.menu.add_command(label="Stopwatch")
self.menu.add_command(label="Timer")
self.menu.add_command(label="Quit", command=quit)
self.stopwatchStart = Button(master, text="Start", command=self.test_start)
self.stopwatchStart.config(height=2, width=5)
self.stopwatchStart.grid(row=0)
self.stopwatchEnd = Button(master, text="End", command=self.stopwatch_End)
self.stopwatchEnd.config(height=2, width=5)
self.stopwatchEnd.grid(row=1)
self.labelSeconds = Label(master, text="Seconds:")
self.labelSeconds.grid(row=0, column=1)
self.stopwatchSeconds = Label(master, text="0")
self.stopwatchSeconds.grid(row=0, column=2)
def test_start(self):
print("Starting thread")
t = threading.Thread(target=self.stopwatch_Start)
t.setDaemon(True)
t.start()
def stopwatch_Start(self, test=False):
print("Stopwatch started")
stopwatch_seconds = "0"
stopwatchBreak = test
print(f"stopwatchBreak == {stopwatchBreak}")
while not stopwatchBreak:
print(stopwatch_seconds)
stopwatch_seconds = int(stopwatch_seconds)
stopwatch_seconds += 1
self.stopwatchSeconds.config(text=stopwatch_seconds)
root.update()
sleep(1)
print("Stopping stopwatch")
return
def stopwatch_End(self):
Buttons.stopwatch_Start(self, True)
print("Attempting to end")
b = Buttons(root)
root.mainloop()
I am using threading to run the stop watch and the window from tkinter at the same time by the way.
Also I have put several print() across the functions to see what is succeeding and what isn't. I think the problem may have something to do with the thread in test_start(). The loop that won't end when I click the end button is in stopwatch_Start(). It just keeps counting up.
I have received no error messages
Does anyone have any suggestions/alternatives to stopping the stopwatch?
look at this code
def stopwatch_End(self):
Buttons.stopwatch_Start(self, True)
print("Attempting to end")
this is your problem =>
Buttons.stopwatch_Start(self, True)
correct this with:
self.stopwatch_Start(True)
here you are call "stopwatch" on the class Buttons, this is an unbound function!!!
"Buttons.stopwatch" is different than "self.stopwatch" the later is bound to the instance.
from tkinter import *
from time import sleep
from threading import Thread
from threading import Event
root = Tk()
root.title("Clock")
class Buttons:
def __init__(self, master):
self.evt = Event()
self.menu = Menu(master)
master.config(menu=self.menu)
self.menu.add_command(label="Stopwatch")
self.menu.add_command(label="Timer")
self.menu.add_command(label="Quit", command=quit)
self.stopwatchStart = Button(master, text="Start", command=self.test_start)
self.stopwatchStart.config(height=2, width=5)
self.stopwatchStart.grid(row=0)
self.stopwatchEnd = Button(master, text="End", command=self.stopwatch_End)
self.stopwatchEnd.config(height=2, width=5)
self.stopwatchEnd.grid(row=1)
self.labelSeconds = Label(master, text="Seconds:")
self.labelSeconds.grid(row=0, column=1)
self.stopwatchSeconds = Label(master, text="0")
self.stopwatchSeconds.grid(row=0, column=2)
def test_start(self):
print("Starting thread")
t = Thread(target=self.stopwatch_Start)
t.setDaemon(True)
t.start()
def stopwatch_Start(self, test=False):
print("Stopwatch started")
stopwatch_seconds = "0"
stopwatchBreak = test
print(f"stopwatchBreak == {stopwatchBreak}")
# self.evt.is_set() will force the loop to check its actually state if it True or not
while not self.evt.is_set():
print(stopwatch_seconds)
stopwatch_seconds = int(stopwatch_seconds)
stopwatch_seconds += 1
self.stopwatchSeconds.config(text=stopwatch_seconds)
root.update()
sleep(1)
print("Stopping stopwatch")
return
def stopwatch_End(self):
#we set self.evt to True so that the while loop will be broken
self.evt.set()
print("Attempting to end")
b = Buttons(root)
root.mainloop()
Related
In the code below, when I press Button 1, the application does not respond and I cannot use other Button 2.
Threading module was required for this.
I have created Threads for Threadingle main() and process(), but when the program opens and I press Button 1, the application does not respond and I cannot press Button 2.
What's the problem?
from tkinter import *
import threading
def process():
while True:
print("Hello World")
processThread = threading.Thread(target=process)
def main():
mainWindow = Tk()
mainWindow.resizable(FALSE, FALSE)
mainWindow.title("Text")
mainWindow.geometry("500x250")
recButton=Button(mainWindow)
recButton.config(text="Button 1", font=("Arial", "13"), bg="red",fg="white", width="15", command=processThread.run)
recButton.place(x=15,y=10)
stopButton=Button(mainWindow)
stopButton.config(text="Button 2", font=("Calibri", "13"), bg="orange",fg="white", width="15", command="")
stopButton.place(x=15,y=55)
textBox = Text(mainWindow, height="14", width="37")
textBox.place(x=180, y=10)
mainWindow.mainloop()
mainThread = threading.Thread(target=main)
mainThread.start()
use processThread.start to start the thread instead of processThread.run infact processThread.run will just call the process method and which will not return control to your event loop hence application goes wild
command=processThread.start
from tkinter import *
import threading
def process():
while True:
print("Hello World")
processThread = threading.Thread(target=process)
def main():
mainWindow = Tk()
mainWindow.resizable(FALSE, FALSE)
mainWindow.title("Text")
mainWindow.geometry("500x250")
recButton=Button(mainWindow)
recButton.config(text="Button 1", font=("Arial", "13"), bg="red",fg="white", width="15", command=processThread.start)
recButton.place(x=15,y=10)
stopButton=Button(mainWindow)
stopButton.config(text="Button 2", font=("Calibri", "13"), bg="orange",fg="white", width="15", command="")
stopButton.place(x=15,y=55)
textBox = Text(mainWindow, height="14", width="37")
textBox.place(x=180, y=10)
mainWindow.mainloop()
mainThread = threading.Thread(target=main)
mainThread.start()
Modified the above code for start and stop button functionality. Here we are using flag when this is true thread will be running if flag if false the thread will stop. Remember we can't start a stopped thread because of this we have to create new thread.
import time
from tkinter import *
import threading
flag = True
def printHello():
global flag
while flag:
print("Hello World")
time.sleep(1)
"""
starts the thread
"""
def startProcess():
global flag
flag = True
processThread = threading.Thread(target=printHello)
processThread.start()
"""
stops the thread
"""
def stopProcess():
global flag
flag = False
def main():
mainWindow = Tk()
mainWindow.resizable(FALSE, FALSE)
mainWindow.title("Text")
mainWindow.geometry("500x250")
recButton = Button(mainWindow)
recButton.config(text="Button 1", font=("Arial", "13"), bg="red", fg="white", width="15",
command=startProcess)
recButton.place(x=15, y=10)
stopButton = Button(mainWindow)
stopButton.config(text="Button 2", font=("Calibri", "13"), bg="orange", fg="white", width="15", command=stopProcess)
stopButton.place(x=15, y=55)
textBox = Text(mainWindow, height="14", width="37")
textBox.place(x=180, y=10)
mainWindow.mainloop()
mainThread = threading.Thread(target=main)
mainThread.start()
I have a problem with stopping the program. When I click exit button, the mainloop stops but program is still running. I am new to it and I have no idea what to do. I know the issue is the thread is still running and I don't know how to stop it.
This is my code:
from tkinter import *
import simulation as s
import graph as g
import numpy as np
from tkinter import filedialog
import threading
def main():
root = Tk()
root.title("Simulator")
switch = True
def get_filename():
return filedialog.askopenfilename(parent=root)
def play():
def run():
while switch:
s.simulation(s.particle, np.inf, s.initial_time_step, get_filename())
thread = threading.Thread(target=run)
thread.start()
def start_simulation():
global switch
switch = True
v.set('Simulation is running!')
play()
def stop_simulation():
global switch
v.set('Simulation is stopped!')
switch = False
def draw_graphs():
g.create_graphs()
start = Button(root, text='Start simulation', command=start_simulation, width=50)
start.pack()
finish = Button(root, text='Stop simulation', command=stop_simulation, width=50)
finish.pack()
graphs = Button(root, text='Graphs', command=draw_graphs, width=50)
graphs.pack()
exit_button = Button(root, text='Exit', command=root.destroy, width=50)
exit_button.pack()
v = StringVar()
statement = Label(root, textvariable=v)
statement.pack()
root.mainloop()
if __name__ == '__main__':
main()
Create a function for the exit button which also stops your thread
def exit():
switch = False
root.destroy()
And later:
exit_button = Button(root, text='Exit', command=exit, width=50)
You can also call the same method whenever the window is closed with the top right X button. Just bind your method to the event:
root.protocol("WM_DELETE_WINDOW", exit)
Ps: You dont need to use global here, because your nested functions have access to the outer functions variables
I have this program which beeps every second until it's stopped. The problem is that after I press "Start" and the beeps starts, I cannot click the "Stop" button because the window freezes. Any help is welcome.
#!/usr/bin/python
import Tkinter, tkMessageBox, time, winsound, msvcrt
running = True
Freq = 2500
Dur = 150
top = Tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100') # Size 200, 200
def start():
sec = 0
while running:
if sec % 1 == 0:
winsound.Beep(Freq, Dur)
time.sleep(1)
sec += 1
def stop():
running = False
startButton = Tkinter.Button(top, height=2, width=20, text ="Start", command = start)
stopButton = Tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)
startButton.pack()
stopButton.pack()
top.mainloop()
There are several things wrong with your code. First of all you shouldn't use time.sleep() in a Tkinter program because it interferes with the mainloop(). Instead one typically uses the universal widget method .after() to schedule a function to run after a specified delay.
Secondly you're not using global variables correctly. When you assign a value to a named variable in a function, it will create a local variable unless that name has been previous declared global. So for instance, your stop() function is creating a local variable named running and setting its value to 0, not changing the value of the global variable with the same name.
The previous rule doesn't apply to just referencing (reading) the current value of a variable. That is why it was OK to not have declared Freq and Dur globals in start().
Another problem is with the sec % 1 == 0 in your start() function. Any value % 1 is 0. To check odd/evenness use sec % 2.
Here's a working version which has also been reformatted to follow PEP 8 - Style Guide for Python Code more closely.
try:
import tkinter as tk
except ModuleNotFoundError:
import Tkinter as tk # Python 2.
import winsound
FREQ = 2500
DUR = 150
after_id = None
secs = 0
def beeper():
global after_id
global secs
secs += 1
if secs % 2 == 0: # Every other second.
winsound.Beep(FREQ, DUR)
after_id = top.after(1000, beeper) # Check again in 1 second.
def start():
global secs
secs = 0
beeper() # Start repeated checking.
def stop():
global after_id
if after_id:
top.after_cancel(after_id)
after_id = None
if __name__ == '__main__':
top = tk.Tk()
top.title('MapAwareness')
top.geometry('200x100')
startButton = tk.Button(top, height=2, width=20, text="Start", command=start)
stopButton = tk.Button(top, height=2, width=20, text="Stop", command=stop)
startButton.pack()
stopButton.pack()
top.mainloop()
Update
Since this answer has become fairly popular, I'd like touch on another slightly more advanced topic — namely how making the code more object-oriented would simplify things by eliminating the need almost all of the global variables.
try:
import tkinter as tk
except ModuleNotFoundError:
import Tkinter as tk # Python 2.
import winsound
FREQ = 2500
DUR = 150
class Application(tk.Frame, object):
def __init__(self, master=None):
super(Application, self).__init__(master) # Call base class initializer.
self.after_id = None
self.secs = 0
# Create widgets,
startButton = tk.Button(top, height=2, width=20, text="Start", command=self.start)
stopButton = tk.Button(top, height=2, width=20, text="Stop", command=self.stop)
startButton.pack()
stopButton.pack()
def beeper(self):
self.secs += 1
if self.secs % 2 == 0: # Every other second.
winsound.Beep(FREQ, DUR)
self.after_id = top.after(1000, self.beeper) # Check again in 1 second.
def start(self):
self.secs = 0
self.beeper() # Start repeated checking.
def stop(self):
if self.after_id:
top.after_cancel(self.after_id)
self.after_id = None
if __name__ == '__main__':
top = tk.Tk()
app = Application()
app.master.title('MapAwareness')
app.master.geometry('200x100')
app.mainloop()
You code have top.mainloop() which has a while loop running inside it and on top of that you also have a while loop inside def start():. So it is like loop inside loop.
You can create a function that does what you want for the body of the loop. It should do exactly one iteration of the loop. Once it is done, it needs to arrange for itself to be called again some time in the future using after. How far in the future defines how fast your loop runs.
And you can then use after_cancel to cancel the event. Below code worked for me
import Tkinter, tkMessageBox, time, winsound, msvcrt
Freq = 2500
Dur = 150
top = tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100') # Size 200, 200
def start():
global job1
if running == True:
winsound.Beep(Freq, Dur)
job1 = top.after(1000, start) # reschedule event in 1 seconds
def stop():
global job1
top.after_cancel(job1)
startButton = tkinter.Button(top, height=2, width=20, text ="Start", command = start)
stopButton = tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)
startButton.pack()
stopButton.pack()
#top.after(1000, start)
top.mainloop()
The problem is that the while loop in start() blocks the GUI handler mainloop(). Try using Tk.after() in start():
def start(force=True):
global running
if force:
running = True
if running:
winsound.Beep(Freq, Dur)
top.after(1000, start, False)
And change stop():
def stop():
global running
running = False
Beaten to the punch again but here goes nothing. As above use the after function to prevent the mainloop blocking.
See:
tkinter: how to use after method
#!/usr/bin/python
import Tkinter, tkMessageBox, time
Freq = 2500
Dur = 150
top = Tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100') # Size 200, 200
def start():
print ("Beep")
top.after(1000, start)
def stop():
print ("Stop")
top.quit()
startButton = Tkinter.Button(top, height=2, width=20, text ="Start", command = start)
stopButton = Tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)
startButton.pack()
stopButton.pack()
top.mainloop()
I used thread and global variable to fit your need. Not so complicated if you understand how they work. Just an addition of few lines and minor change to your existing line, and it works. Look through to see the changes made to your original code.
#!/usr/bin/python
import tkinter
from tkinter import messagebox
import time, winsound, msvcrt
from threading import Thread
running = True
Freq = 2500
Dur = 150
top = tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100') # Size 200, 200
def button_click():
global running #create global
running = True
# Create new thread
t = Thread(target = start)
# Start new thread
t.start()
def start():
sec = 0
while running:
if running == False:
break
if sec % 1 == 0:
winsound.Beep(Freq, Dur)
time.sleep(1)
sec += 1
def stop():
global running #create global
running = False
startButton = tkinter.Button(top, height=2, width=20, text ="Start", command = button_click) #Change to call button_click instead start
stopButton = tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)
startButton.pack()
stopButton.pack()
top.mainloop()
from threading import Thread
def stttart():
t1=Thread(target=start)
t1.start()
def start():
...
def stop():
...
startButton = Tkinter.Button(top, height=2, width=20, text ="Start", command = stttart)
stopButton = Tkinter.Button(top, height=2, width=20, text ="Stop", command = stop)
startButton.pack()
stopButton.pack()
https://www.geeksforgeeks.org/how-to-use-thread-in-tkinter-python/
last year, this had been my big problem for some months
I've been googeling all day, tried loads of different ways, but I can't get this code to work. I want a simple timer that run a function when you press "Start", and stops when you press "Stop".
Example:
When you press start, the function will print every second "Hello World", until you press stop.
My code with some comments so you can understand faster:
import sys
from Tkinter import *
from threading import Timer
# a boolean for telling the timer to stop
stop_timer = False
mgui = Tk()
def work(var):
# stop_timers sets to True or False with Start/Stop
stop_timer = var
# work_done will evaluate if timer should start or be canceled
def work_done(var2):
stop_timer = var2
# if stop was pressed t.start() will be ignored
if stop_timer == False:
t.start()
# if stop was pressed timer will stop
if stop_timer == True:
print "Stopped!"
t.cancel()
t = Timer(1, work, [False])
print "Something"
work_done(var)
mgui.geometry('450x450')
mgui.title('Test')
cmd1 = lambda: work(False)
btn = Button(mgui, text="Start", command =cmd1).place(x=50, y=50)
cmd2 = lambda: work(True)
btn2 = Button(mgui, text="Stop", command =cmd2).place(x=100, y=50)
mgui.mainloop()
As you can tell, I'm new to this shizzle!
Thanks, mates!
This is a generic timer, you can also implement one in tkinter using after:
import time
import Tkinter as tk
import threading
class MyTimer(threading.Thread):
def __init__(self, t):
super(MyTimer,self).__init__()
self.txt = t
self.running = True
def run(self):
while self.running:
self.txt['text'] = time.time()
mgui = tk.Tk()
mgui.title('Test')
txt = tk.Label(mgui, text="time")
txt.grid(row=0,columnspan=2)
timer = None
def cmd1():
global timer
timer = MyTimer(txt)
timer.start()
def cmd2():
global timer
if timer:
timer.running = False
timer= None
btn = tk.Button(mgui, text="Start", command =cmd1)
btn.grid(row=1,column=1)
btn2 = tk.Button(mgui, text="Stop", command =cmd2)
btn2.grid(row=1,column=2)
mgui.mainloop()
By editing some in xndrme's post, I finally got it to work. Thank you!
I'll post the code here for possible future googlers.
import sys
from Tkinter import *
from threading import Timer
mgui = Tk()
def cmd2():
global t
if t:
t.cancel()
def looper():
global t
t = Timer(1, looper)
t.start()
print "Hello World!"
mgui.geometry('450x450')
mgui.title('Test')
btn = Button(mgui, text="Start", command =looper).place(x=50, y=50)
btn2 = Button(mgui, text="Stop", command =cmd2).place(x=100, y=50)
mgui.mainloop()
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()