Posts: 50
Threads: 21
Joined: Dec 2019
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
from tkinter import *
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()
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 ):
...
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,798
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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:
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)
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,798
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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.
1 2 3 4 5 6 |
def process():
if some_condition: ...
root.after(milliseconds, process)
|
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,798
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().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import tkinter as tk
import time
class MidiPlayer(tk.Tk):
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 ):
time.sleep( 1 )
countdown = self .countdown.get()
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 :)
|