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 becaus
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()
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.
import Tkinter
import tkMessageBox
import time
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
top = Tkinter.Tk()
top.title('MapAwareness')
top.geometry('200x100')
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()
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()