Python Forum
How to break out of a for loop on button press?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to break out of a for loop on button press?
#1
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...
Reply
#2
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.
Reply
#3
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() 
Reply
#4
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
Reply
#5
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
Reply
#6
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
Reply
#7
Thank you all for these examples! I managed to get it working using a thread :)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Button to stop while loop from another script Absolutewind 5 917 Sep-25-2023, 11:20 PM
Last Post: deanhystad
  Code won't break While loop or go back to the input? MrKnd94 2 965 Oct-26-2022, 10:10 AM
Last Post: Larz60+
  break out of for loop? User3000 3 1,465 May-17-2022, 10:18 AM
Last Post: User3000
  Asyncio: Queue consumer gets out of while loop without break. Where exactly and how? saavedra29 2 2,706 Feb-07-2022, 07:24 PM
Last Post: saavedra29
  tkinter auto press button kucingkembar 2 3,191 Dec-24-2021, 01:23 PM
Last Post: kucingkembar
  tkinter control break a while loop samtal 0 2,405 Apr-29-2021, 08:26 AM
Last Post: samtal
  Cannot 'break' from a "for" loop in a right place tester_V 9 4,000 Feb-17-2021, 01:03 AM
Last Post: tester_V
  How to break a loop in this case? Blainexi 10 7,309 Sep-24-2020, 04:06 PM
Last Post: Blainexi
  how to break the loop? bntayfur 8 3,081 Jun-07-2020, 11:07 PM
Last Post: bntayfur
  break for loop Agusben 1 1,923 Apr-01-2020, 05:07 PM
Last Post: Larz60+

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020