I am working in a program where I use Tkinter for the UI. I am writing a code to play an audio repeatedly. I am using pygame.mixer.music() for playing audio.
In the UI I created two buttons ("Start" and "Stop"). I attached a method which contains the loop structure to the start button, so that when the Start button is pressed the loop will be executed and starts playing audio repeatedly. Now I don't know how to attach the Stop button. Like, when Stop button is pressed the control should exit the loop. Can I use interrupts or some other thing like that? Iam totaly new to the concept of interrupts. To proceed with that, help me with what kind of interrupt, what is the library for that, etc. If not please help me how to proceed with the stop button.
Here is my code:
from pygame import *
from Tkinter import *
import time
root = Tk()
root.geometry("1000x200")
root.title("sampler")
m=1
n=1
mixer.init()
def play():
while m==1:
print 'playing'
mixer.music.load('audio 1.mp3')
mixer.music.play()
time.sleep(n)
start = Button(root, text="play", command = play)
start.pack()
stop = Button(root, text="Stop")
stop.pack()
mainloop()
n defines how long the audio should be played for each loop.
Python doesn't exactly support interrupts, the closest thing would probably be some sort of signal handler, which are supported via its signal library. However they may not work well with Tkinter (or pygame), so I don't think that would be a good approach—and they're not really necessary anyway because what you want to do can be handled within Tkinter's mainloop().
Although it may seem somewhat complex, the way I would suggest implementing it would be to encapsulate most of the playing control functionality within a single Python class. This will reduce the use of global variables, which will make the program easier to debug and develop further (because of the many advantages of Object-Oriented Programming — aka as OOP).
Below illustrates what I mean. Note, I'm using Python 3, so had to make a few additional changes to your code in order for it would work with that version. I'm not sure, but this version ought to work in Python 2, as well, except you'll need to change the import of the Tkinter module as indicated.
from pygame import *
from tkinter import * # Change to "from Tkinter import *" for Python 2.x.
class PlayController(object):
def __init__(self, mixer, music_filename, polling_delay):
self.mixer = mixer
self.music_filename = music_filename
self.polling_delay = polling_delay # In milliseconds.
self.playing = False
def play(self):
if self.playing:
self.stop()
self.mixer.music.load(self.music_filename)
self.mixer.music.play(-1) # -1 means to loop indefinitely.
self.playing = True
root.after(self.polling_delay, self.check_status) # Start playing check.
def stop(self):
if self.playing:
self.mixer.music.stop()
self.playing = False
def check_status(self):
if self.playing:
print('playing')
root.after(self.polling_delay, self.check_status) # Repeat after delay.
root = Tk()
root.geometry("1000x200")
root.title("Sampler")
mixer.init()
play_control = PlayController(mixer, 'tone.wav', 1000)
Button(root, text="Play", command=play_control.play).pack()
Button(root, text="Stop", command=play_control.stop).pack()
mainloop()
You need to add a command to your button... stop = Button(root, text="Stop", command=stop)
Just adding a stop command probably wont work because the way your infinite loop is structured, you can't interact with the tkinter interface while play is clicked. Try restructuring your program like this:
from Tkinter import *
from pygame import *
import time
import threading
switch = True
root = Tk()
n = 1
mixer.init()
root.geometry("1000x200")
root.title("sampler")
def play():
def run():
while switch:
print 'playing'
mixer.music.load('audio 1.mp3')
mixer.music.play()
time.sleep(n)
if not switch:
break
thread = threading.Thread(target=run)
thread.start()
def switch_on():
global switch
switch = True
play()
def switch_off():
global switch
switch = False
def kill():
root.destroy()
onbutton = Button(root, text="Play", command=switch_on)
onbutton.pack()
offbutton = Button(root, text="Stop", command=switch_off)
offbutton.pack()
killbutton = Button(root, text="Kill", command=kill)
killbutton.pack()
root.mainloop()
This way the tkinter buttons are running on separate threads, so while one is looping you can still interact with the other.
Related
I am building an interface in Tkinter in which the main window (let's call it 'root') contains a button (say, 'create'). Furthermore, assume I have already defined a function 'f'. I would like to create the following effect: clicking on 'create' would execute 'f' in the background and at the same time open an indeterminate progress bar in a new window. Moreover, and this is the tricky part for me, I want the progress bar to close automatically after 'f' is done executing. How can I achieve this? Could you please provide a minimal working example? I think that key lies on constructing a proper function to pass as 'command' option to 'create'.
This is what I have thus far. It is not even running properly, as the progress bar runs indefinitely and the task starts being executed only after the progress bar is closed (or after closing 'root'). However, it feels like this is really close, and there is some small issue that I should fix but that I cannot see:
from tkinter import *
from tkinter.ttk import *
import threading
import time
root = Tk() # Main window
def create_command():
stop_flag = threading.Event() # create a flag to stop the progress bar
def f():
# function to do some task
print("Starting task...")
time.sleep(5) # simulate some time-consuming task
print("Task complete.")
stop_flag.set() # set the stop flag to indicate that progress_check() should stop
progress_bar_window = Toplevel(root) # Progress bar window
progress_bar = Progressbar(progress_bar_window, orient= 'horizontal', length= 300, mode= 'indeterminate') # Create progress bar
progress_bar.pack()
progress_bar.start()
def progress_check():
# function to run an infinite loop
while not stop_flag.is_set():
print("Running infinite loop...")
time.sleep(1)
progress_bar.stop()
progress_bar_window.destroy()
progress_bar_window.mainloop() # Start mainloop for progress bar window
# create separate threads to run the functions
thread1 = threading.Thread(target=f, args=())
thread2 = threading.Thread(target=progress_check, args=())
thread1.start() # start executing f
thread2.start() # start the progress_check
# wait for f to finish before stopping the infinite loop
thread2.join()
stop_flag.set() # set the stop flag to indicate that progress_bar() should stop
create_button = Button(root, text= "Create", command= create_command)
create_button.pack()
root.mainloop()
Look at this:
from tkinter import ttk
import tkinter as tk
import threading
import time
root = tk.Tk() # Main window
def create_command():
# create a flag to stop the progress bar
stop_flag = threading.Event()
def f():
print("Starting task...\n", end="")
time.sleep(5)
print("Task complete.\n", end="")
# set the stop flag to indicate that progress_check() should stop
stop_flag.set()
progress_bar_window = tk.Toplevel(root)
progress_bar = ttk.Progressbar(progress_bar_window, orient="horizontal",
length=300, mode="indeterminate")
progress_bar.pack()
progress_bar.start()
def progress_check():
# If the flag is set (function f has completed):
if stop_flag.is_set():
# Stop the progressbar and destroy the toplevel
progress_bar.stop()
progress_bar_window.destroy()
else:
# If the function is still running:
print("Running infinite loop...\n", end="")
# Schedule another call to progress_check in 100 milliseconds
progress_bar.after(100, progress_check)
# start executing f in another thread
threading.Thread(target=f, daemon=True).start()
# Start the tkinter loop
progress_check()
create_button = tk.Button(root, text= "Create", command=create_command)
create_button.pack()
root.mainloop()
Explanation:
To run a loop alongside tkinter, you should use .after, like in this question. I changed progress_check so that tkinter calls it every 100 milliseconds until stop_flag is set. When stop_flag is set, the progressbar stops and the Toplevel is destroyed.
A few minor points:
from ... import * is discouraged
With tkinter, you don't need more than 1 .mainloop() unless you are using .quit(). .mainloop() doesn't stop until all tk.Tk windows have been destroyed.
There is no point in creating a new thread, if you are going to call .join() right after.
First of all, don't use wildcard imports!
Wildcard-imports can lead to name conflicts, for instance swap the wildcard imports from ttk and tkinter. You end up using tkinter buttons even if you want to use ttk buttons. Same issue might appear with PhotoImage and pillow. The magic word is "qualified-names".
Also I like to have some sort of structure in my code, I prefer classes. However, even in a procedural code there can be some sort of structure. For instance:
imports
1.0) built-in modules
1.1) import external modules
1.2) import own modules
Constants and global variables
free functions
main window definitions
...
every logical block can be separated with comments that indicates what the following code might do or represents. This could also be useful to "jump" with the search function of your IDE to the point you want to work next, in larger scripts and modules this becomes handy.
A slightly different version of your code can be found below and it is not intended to be used:
import tkinter as tk
from tkinter import ttk
import threading
import time
def start_worker_thread():
'This function starts a thread and pops up a progressbar'
def generate_waiting_window():
'nested function to generate progressbar'
#disable button to inform user of intended use
start_btn.configure(state=tk.DISABLED)
#toplevel definitions
toplevel = tk.Toplevel(root)
toplevel.focus()
#progressbar definitions
progress = ttk.Progressbar(
toplevel, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress.pack(fill=tk.BOTH, expand=True)
progress.start()
return toplevel
def long_blocking_function():
'This function simulates a long blocking call'
stopped = threading.Event()
n = 0
while not stopped.is_set():
n += 1
print('working in turn', n)
time.sleep(0.5)
if n == 10:
stopped.set()
nonlocal thread_info
thread_info = n
#important!! last logical line
toplevel.destroy()
return None
toplevel = generate_waiting_window()
thread_info = None
thread = threading.Thread(target=long_blocking_function)
thread.start()
toplevel.wait_window()
start_btn.configure(state='normal')
result_lbl.configure(text='Result is: '+str(thread_info))
print('thread exited on turn', thread_info)
#Main window definitions
root = tk.Tk()
start_btn = ttk.Button(root, text="Start", command=start_worker_thread)
start_btn.pack()
result_lbl = tk.Label(root, text='Result is: None')
result_lbl.pack()
#start the application
root.mainloop()
#after application is destroyed
While this code is efficient for this simple task, it requires understanding what it does to debug it. That is why you won't find code like this often. It is here for demonstrative purposes. So what is wrong with the code and how does it differ from the meanwhile canonical way of using threads in tkinter.
First of all, it uses nested function. While this might not an issue here, computing the same function over and over again, can slow down your code significantly.
Second it uses tkwait and therefore has some caveats over the linked answer.
Also threading.Event is a low-level primitive for communication, while there are cases you could use it, tkinter offers own tools for it and these should be preferred.
In addition it does not use a threadsafe storage for the data and this could also lead to confusion and non reliable data.
A better approach and a slight improvement to the canonical way can be found here:
import tkinter as tk
from tkinter import ttk
import threading
import sys
import queue
import time
inter_thread_storage = queue.Queue()
temporary_toplevel = None
EXIT = False
def on_thread_ended_event(event):
start_btn.configure(state=tk.NORMAL)
result = inter_thread_storage.get_nowait()
result_lbl.configure(text='Result is: '+str(result))
global temporary_toplevel
temporary_toplevel.destroy()
temporary_toplevel = None
def worker_thread_function():
'Simulates a long blocking function'
n = 0
while n < 10 and not EXIT:
n += 1
print('working in turn', n)
time.sleep(0.5)
if not EXIT:
inter_thread_storage.put(n)
root.event_generate('<<ThreadEnded>>')
def start_worker_thread():
'This function starts a thread and pops up a progressbar'
#toplevel definitions
toplevel = tk.Toplevel(root)
toplevel.focus()
#progressbar definitions
progress = ttk.Progressbar(
toplevel, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress.pack(fill=tk.BOTH, expand=True)
progress.start()
#thread definitions
thread = threading.Thread(target=worker_thread_function)
thread.start()
#disable button to inform user of intended use
start_btn.configure(state=tk.DISABLED)
#store toplevel temporary
global temporary_toplevel
temporary_toplevel = toplevel
#Main window definitions
root = tk.Tk()
root.bind('<Destroy>',lambda e:setattr(sys.modules[__name__], 'EXIT', True))
root.bind('<<ThreadEnded>>', on_thread_ended_event)
start_btn = ttk.Button(root, text="Start", command=start_worker_thread)
start_btn.pack()
result_lbl = tk.Label(root, text='Result is: None')
result_lbl.pack()
#start the application
root.mainloop()
#after application is destroyed
This is how it works:
generate a new event
Make sure your toplevel can be reached, with global or alternatives.
store data threadsafe like in a Queue
fire the event and let tkinter call your function safely in the mainloop.
it has a flag for the edge case, where the user closes the main window before the thread finished.
Let me know, if you have questions to my answer.
In the code below, pressing the space bar twice results in two successive beeps. I want to avoid this and instead disable the key while the first beep is happening. I thought unbinding the space key might work, but it doesn't. It's strange that only two beeps seem to stack up rather than more. I'm guessing maybe the cause of the issue is that winsound.Beep is non-blocking so the rebinding occurs almost instantly.
Any suggestion on how to get this to work please?
import winsound
from tkinter import *
def beep(e):
frame.unbind("<space>")
winsound.Beep(440, 1000)
frame.bind("<space>", beep)
root = Tk()
frame = Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
Here is a solution that takes the focus away from the widget, so the binding wont get triggered:
import winsound
from tkinter import *
def beep(event):
dummy.focus_set() #setting focus to dummy
winsound.Beep(440, 1000) #playing it
root.after(1000,frame.focus_set) #setting focus back after playing for 1000 ms
root = Tk()
dummy = Label() #making a dummy widget
dummy.pack()
frame = Frame(root, width=100, height=100)
frame.bind("<space>",beep)
frame.pack()
frame.focus_set()
root.mainloop()
I've commented it to understand better, but this is just a way around and its not that complicated to understand either.
Also keep in mind, in all cases of using winsound, as long as that beep has started and finished playing, the GUI will be unresponsive, that is, GUI will be unresponsive for 1 sec(in your case).
This should fix it however you have to download keyboard module with pip install keyboard :
import winsound
from tkinter import *
import keyboard
from _thread import start_new_thread
def beep():
while True:
if keyboard.is_pressed('space'):
winsound.Beep(440, 1000)
root = Tk()
frame = Frame(root, width=100, height=100)
start_new_thread(beep, ())
frame.pack()
frame.focus_set()
root.mainloop()
First start_new_thread()(syntax is important) makes beep() threaded (runs in background?) and its a while loop so it runs continuously and whenever you press space it will beep and even if you spam space it will still just run one beep. However there is a downside. It will run while script is not terminated so if you focus out it will still beep if you press spacebar
You can use the elapsed time since the last successful keypress to decide if the beep should be produced or not.
maybe like this: I do not have access to winsound, so I am using an os feature to mimic a beep. You can comment this out, and uncomment the calls to winsound
# import winsound
import os
import tkinter as tk
import time
def beep(e, time_limit=1, timer=[0]):
t0 = timer[0]
t1 = time.time()
delta_t = t1 - t0
if delta_t < time_limit:
return
# winsound.Beep(440, 1000)
os.system('say "Beep"')
timer[0] = t1
root = tk.Tk()
frame = tk.Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
You can bind back the event via after_idle():
def beep(e):
e.widget.unbind('<space>')
winsound.Beep(440, 1000)
e.widget.after_idle(e.widget.bind, '<space>', beep)
explanation:
The callback passed to after_idle() will be executed when tkinter mainloop is idle, i.e. no pending works/events to be handled. So if the spacebar is pressed many times, the first press triggers beep() in which tkinter unbinds the event, beep and then schedules the rebind. After beep() returns, tkinter keeps handling the pending tasks, i.e. handle the rest spacebar events (but at that moment, no bind is active) and then do the after_idle schedule task which is the rebind.
I'm trying to make a button recorder for a game I'm working on, but it requires timings (around 100 or so)
I need a program that can record when I press the spacebar in another program, for how long it's held down for (heldtime), and the time in between the next press in milliseconds (waittime). (formatted as jump(heldtime, waittime)) Pressing the Z key stops the recording.
I am using Python 2.7
If any more information is needed, I will edit to add it upon request.
You can use this to learn how to set a timer:
How to create a timer on python
And use this to learn how to create events to listen for the spacebar being pressed (provided you're using the Tkinter module to write your code):
http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
Here is what that might look like put together:
from timeit import default_timer
from Tkinter import *
key_pressed = False
last_start = 0
heldtime_array = []
def start_stop_timer():
if key_pressed == False:
last_start = default_timer()
key_pressed = True
else:
heldtime_array[len(heldtime_array)] = default_timer() - last_start
root = Tk()
frame = Frame(root, width=100, height=100)
# This will work with any key. See above link for specific keys.
frame.bind("<Key>", start_stop_timer)
frame.pack()
# Do other customisation/setup of your window here.
root.mainloop()
I want to make a GUI command line using the Text widget. For debugging purposes, I am trying to print whatever the user types into the separate GUI window to the system terminal. I know that it is frowned upon to mix GUI and Text Based commands into the same script, but I am just debugging, so forgive me 😉
Here is my code:
from Tkinter import *
main = Tk()
console = Text(main)
console.pack()
main.mainloop()
while True:
text = console.get("1.0", "end-1c")
print(text)
My current issue is that when the mainloop starts, (of course) the while loop doesn't. If I were to move the while loop in front of the mainloop call, it would never call mainloop. I really want it to continuously check for new text.
Is there a way to like "pause" the mainloop, or just carry out the command, maybe on a new thread or something?
I want to avoid using main.after(), but if that is the only way, then so be it. ¯\(°_o)/¯
I recommend using main.after(), as it's the canonical way to do things like this in Tkinter. The following will also ensure that it only tries to print every second, instead of as fast as the console can handle it (as the while loop in your code would do if it worked).
def print_console():
print(console.get("1.0", "end-1c"))
main.after(1000, print_console)
print_console()
main.mainloop()
You can also bind widgets to "Modified"
from Tkinter import *
class TextModified():
def __init__(self):
root = Tk()
self.txt = Text(root)
self.txt.pack()
self.txt.focus_set()
self.txt.bind('<<Modified>>', self.changed)
Button(text='Exit', command=root.quit).pack()
root.mainloop()
def changed(self, value=None):
flag = self.txt.edit_modified()
if flag: # prevent from getting called twice
print "changed called", self.txt.get("1.0", "end-1c")
## reset so this will be called on the next change
self.txt.edit_modified(False)
TM=TextModified()
I am just starting using python with a GUI interface. I've been experimenting with TKinter on a simple timer program. I am stuck, because I want to have a song play with the alert,but have not been able to find a solution. I am working on Linux mint. I have a message window that appears when the time is up, and i would like to start the audio along with the window, and when you exit the window, the audio stops. my code looks like this.
from Tkinter import *
import tkMessageBox
def messageWindow():
win = Toplevel()
b = Button(win, text='Times Up!',
bg="yellow", fg="green",
activebackground="purple", activeforeground="white",
command=quit)
b.pack(ipadx=root.winfo_screenwidth()/2,
ipady=root.winfo_screenheight()/2)
root.mainloop()
def alert():
#this is were i would a call the function to play mp3
messageWindow()
quit()
def start():
root.after(scale.get() * 1000, alert)
root = Tk()
minutes = Label(root, text ="Minutes: ")
minutes.grid(row=0, column=0)
scale = Scale(root, from_=1, to=60, orient=HORIZONTAL, length=450)
scale.grid(row=0, column=1)
button = Button(root,text= "Start Timing", command=start)
button.grid(row=1, column=1, pady=5, sticky=E)
root.mainloop()
pygame includes the functionality to do this. I don't know if it is the best way but it is certainly a way.
import pygame
pygame.init()
pygame.mixer.init()
sounda= pygame.mixer.Sound("desert_rustle.wav")
sounda.play()
sounda.stop()
example taken from here
Seems like you're already happy with an answer (which you accepted 3 years ago, admittedly!) but here's an alternative you could consider for future projects:
use import winsound. Pygame didn't work for me (possibly down to the fact that I'm using python 3), but more importantly, winsound probably makes audio simpler to play in the first place and it would be effective for your purposes as far as I know. You need to use a '.wav' file (as opposed to something like mp3), but it's easy to convert to that format if you look up 'online converter'.
import winsound
winsound.PlaySound('The Countdown.wav', winsound.SND_ASYNC)
Just in case you do need to stop the audio early, you can't use stop(). Instead, use
winsound.PlaySound(None, 0)
Perhaps pyglet does this anyway, but what's great about winsound.SYND_ASYNC is that it will run in conjunction with tkinter instead of halting the program/ waiting until the program finishes, to execute.