Posts: 50
Threads: 21
Joined: Dec 2019
from tkinter import *
# root
root = Tk()
root.title('SeqTank')
scrwidth = root.winfo_screenwidth()
scrheight = root.winfo_screenheight()
root.geometry("%sx%s+%s+%s" % (int(scrwidth / 1.5),
int(scrheight / 1.25),
int(scrwidth / 6),
int(scrheight / 12)))
root.update()
# paned master with two panels
panedmaster = PanedWindow(root, orient='v', sashwidth=1, relief='flat', sashcursor='arrow', bg='#666666')
panedmaster.place(relwidth=1, relheight=1)
uppanel = PanedWindow(panedmaster, height=35, relief='flat')
panedmaster.add(uppanel)
downpanel = PanedWindow(panedmaster, relief='flat')
panedmaster.add(downpanel)
text_editor = Text(downpanel,
bg='black',
fg='#32a852',
insertbackground='white',
font=('Courier', 24))
text_editor.place(relwidth=1,
relheight=1)
text_editor.focus_set()
generate_button = Button(uppanel, text='Play!')
generate_button.place(relwidth=1)
def process():
for i in range(100):
...
# playing a midi-file here
generate_button.configure(command=process)
root.mainloop() This is my simple tkinter interface. I created a little tool that generates a midi-file based on the text input(it uses a special lilypond-like syntax) and plays the midi file using mido in a for loop. My question is how can I break the for loop if I press the play button again before it finished the for loop? I hope someone can tell me in (regarding the above example) how I should write it different...
Posts: 6,783
Threads: 20
Joined: Feb 2020
Oct-04-2022, 07:07 PM
(This post was last modified: Oct-04-2022, 07:07 PM by deanhystad.)
According to your application you are not pressing a button.
In tkinter, mainloop() is the function that sees that you pressed a button and calls your function. Your process() function contains a loop that prevents mainloop() from running. According to your program you are not pressing a button because mainloop() does not get a chance check if you pressed a button. After pressing the play button your application is frozen.
The first thing you need to do is find a way to play the midi file without blocking mainloop(). You should post more information about what is involved in playing the file. Is there a way to start the playback and return immediately, or do you have to wait for completion of the file? If you can start playout and return immediately, is there a continuous play option? If no to all of those, you might need to create a separate thread for playing the midi file.
Posts: 536
Threads: 0
Joined: Feb 2018
Oct-04-2022, 07:16 PM
(This post was last modified: Oct-04-2022, 07:19 PM by woooee.)
You would replace the for with a call to after(). This does not do exactly what you want but shows how to start and stop using a variable.
import tkinter as tk
class TimerTest():
def __init__(self, root):
self.root=root
self.is_running=False
self.count=tk.IntVar()
self.max_seconds=60
tk.Label(root, textvariable=self.count, font=('DejaVuSansMono', 12, "bold"),
bg="lightyellow").grid(row=1, column=0, columnspan=2, sticky="ew")
tk.Button(root, text="Start", fg="blue", width=15,
command=self.startit).grid(row=10, column=0, sticky="nsew")
tk.Button(root, text="Stop", fg="red", width=15,
command=self.stopit).grid(row=10, column=1, sticky="nsew")
tk.Button(self.root, text="Quit", bg="orange",
command=self.root.quit).grid(row=11, column=0,
columnspan=2, sticky="nsew")
def startit(self):
if not self.is_running: ## avoid 2 button pushes
self.is_running=True
self.increment_counter()
def increment_counter(self):
if self.is_running:
c=self.count.get() +1
self.count.set(c)
if c < self.max_seconds:
self.root.after(1000, self.increment_counter) ## every second
else:
self.is_running=False
tk.Label(root, text="Time Is Up", font=('DejaVuSansMono', 14, "bold"),
bg="red").grid(row=5, column=0, columnspan=2, sticky="ew")
def stopit(self):
self.is_running = False
root = tk.Tk()
TT=TimerTest(root)
root.mainloop()
Posts: 6,783
Threads: 20
Joined: Feb 2020
after() is only useful if you want to periodically do something that takes a small amount of time to complete. Like updating your label. It does not help when you need to perform a task that takes a long time.
Here I try to use after() to prevent my window from freezing when the start button is pressed. It is unsuccessful because the play() method takes 30 seconds to complete. Notice that pressing the start and stop buttons does nothing during this time, and you can't even close the window using the frame decorations.
import tkinter as tk
import time
class TimerTest(tk.Tk):
def __init__(self):
super().__init__()
self.countdown = tk.IntVar(self, 0)
tk.Label(self, textvariable=self.countdown).pack(padx=10, pady=5)
tk.Button(self, text="Start", command=self.start).pack(padx=10, pady=5)
tk.Button(self, text="Stop", command=self.stop).pack(padx=10, pady=5)
self.counting = False
def start(self):
if not self.counting:
print("Starting")
self.counting = True
self.after(1, self.play)
def stop(self):
print("Stopping")
self.counting = False
def play(self):
for i in range(30, 0, -1):
if not self.counting:
break
self.countdown.set(i)
time.sleep(1)
self.counting = False
TimerTest().mainloop() Here's the same program, but the lengthy play function is run in a different thread so it doesn't block mainloop(). This program responds to the run button
import tkinter as tk
import time
import threading
class TimerTest(tk.Tk):
def __init__(self):
super().__init__()
self.countdown = tk.IntVar(self, 0)
tk.Label(self, textvariable=self.countdown).pack(padx=10, pady=5)
tk.Button(self, text="Start", command=self.start).pack(padx=10, pady=5)
tk.Button(self, text="Stop", command=self.stop).pack(padx=10, pady=5)
self.counting = False
def start(self):
if not self.counting:
print("Starting")
self.counting = True
threading.Thread(target=self.play).start()
def stop(self):
print("Stopping")
self.counting = False
def play(self):
for i in range(30, 0, -1):
if not self.counting:
break
self.countdown.set(i)
time.sleep(1)
self.counting = False
TimerTest().mainloop() Knowing if after() or threading or something else is the best approach to this problem depends on how your midi player works.
philipbergwerf likes this post
Posts: 536
Threads: 0
Joined: Feb 2018
If you want to use after() in the way the OP was going, it has to call itself.
def process():
## replace for with after
##for i in range(100):
if some_condition: ...
root.after(milliseconds, process)
# playing a midi-file here Or look at one of the tkinter video players like https://pypi.org/project/tkvideoplayer/ I don't use them so don't know, but this one says it has pause and stop commands.
philipbergwerf likes this post
Posts: 6,783
Threads: 20
Joined: Feb 2020
This attempt assumes that playing a midi file consists of many fairly short events. I simulate these short events using sleep().
import tkinter as tk
import time
class MidiPlayer(tk.Tk):
"""Investigate using .after() to replace for loop in midi playout.
Periodically plays a bit of a midi file (ok, it sleeps). See how
responsive the GUI is when it spends most of its time waiting for
something to complete.
"""
def __init__(self):
super().__init__()
self.countdown = tk.IntVar(self, 0)
self.after_event = None
tk.Label(self, textvariable=self.countdown).pack(padx=10, pady=5)
tk.Button(self, text="Start", command=self.start).pack(padx=10, pady=5)
tk.Button(self, text="Stop", command=self.stop).pack(padx=10, pady=5)
def start(self):
"Start playing"
if self.after_event:
self.after_cancel(self.after_event)
self.countdown.set(30)
self.play()
def stop(self):
"Stop playing"
if self.after_event:
self.after_cancel(self.after_event)
self.after_event = None
self.countdown.set(0)
def play(self):
# Using sleep in place of play
time.sleep(1)
# Update display so we can see someing happen
countdown = self.countdown.get()
# Implement for loop using .after()
if countdown > 0:
self.countdown.set(countdown-1)
self.after_event = self.after(1, self.play)
else:
self.after_event = None
MidiPlayer().mainloop() The GUI's responsiveness depends on how long the event is. While sleeping, which is meant to simulate calling a function to play some part of the midi file, the GUI is sleeping. if you press a button, the button press is queued, but not acted upon. When the event is done, the play function returns and mainloop() processes queued events.
If playing part of your midi file takes a very short period of time (less than 0.1 seconds) the GUI will feel fairly responsive. If playing part of the midi file takes longer, the response will be sluggish.
philipbergwerf likes this post
Posts: 50
Threads: 21
Joined: Dec 2019
Thank you all for these examples! I managed to get it working using a thread :)
|