Python Forum
Creating a function interrupt button tkinter - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: Creating a function interrupt button tkinter (/thread-35176.html)



Creating a function interrupt button tkinter - AnotherSam - Oct-07-2021

from tkinter import *

def start(num):
    global sweepCancel
    print(num)
    if num < 10:
        num = num +1
        sweepCancel=win.after(1000,start,num)
 
def stop():
    win.after_cancel(sweepCancel)

win = Tk()
InitButton = Button(win,text='start',command=lambda:start(0)  , bg = "bisque2")
InitButton.grid(row=0, column=1, pady = 20, padx = 10)
ResetButton = Button(win,text='stop',command= stop , bg = "bisque2")
ResetButton.grid(row=0, column=2, pady = 20)
mainloop()
Hello all,
I am trying to create a button to interrupt a function running in the background of tkinter. I looked through the web and found a few examples. Above is a snippet of code I got to work. The start button starts a function to count up and the stop button can halt the count. My first question is, is there a more elegant way to do this without having that global variable. I'm far from an expert in coding, but always heard global variables weren't good to use in pratice

My second question, my real application is slightly different than above. Instead of the if statement allowing the function to continue run, I wanted to call the after() command in a for loop. The code below is what I tried. Unlike the code above, the start button stays depressed and the window locks up. Any thoughts as to why? I sort of realize the above code is functionally no different than a for loop, but I'd rather the after() call a separate function (sweepFunction in this case) and not the original function (start). Thanks in advance!
from tkinter import *

def sweepFunction(argument):
    print(argument)

def start():
    global sweepCancel
    for num in range(0,10):
        sweepCancel=win.after(1000,sweepFunction(num))
 
def stop():
    win.after_cancel(sweepCancel)

win = Tk()
InitButton = Button(win,text='start',command=start  , bg = "bisque2")
InitButton.grid(row=0, column=1, pady = 20, padx = 10)
ResetButton = Button(win,text='stop',command= stop , bg = "bisque2")
ResetButton.grid(row=0, column=2, pady = 20)
mainloop()



RE: Creating a function interrupt button tkinter - DeaD_EyE - Oct-07-2021

The method after blocks until the time is up.
The method is called in the for-loop of your start function.
Calling the start function blocks the GUI because the context of the function is not left, until the counter is done.

What I didn't know, that the method after blocks. I thought always, that it will put the task for the callback into the eventloop of tkinter, but this is wrong. The method after blocks until the time is up and then the callback is called, but the after method allows tkinter to do during this time other tasks like redrawing the GUI.

This is essential for GUIs not to block.


Here is the resulting example with changes (PEP8 names) and without global and the for-loop:
from tkinter import Button, Tk


class Sweep:
    def __init__(self, root, start_value, end_value=None, delay=1000):
        print("New sweep instance")
        self.root = root
        self.start_value = self.num = start_value
        self.end_value = end_value
        self.delay = delay
        self.running = False

    def start(self):
        print("Start method called")
        if not self.running:
            self.running = True
            self._loop()

    def stop(self):
        print("Stop method called")
        if self.running:
            self.running = False

    def reset(self):
        print("Reset method called")
        self.running = False
        self.num = 0

    def _loop(self):
        if self.running:

            if self.end_value is not None and self.end_value <= self.num:
                self.running = False
                print("The End")
                self.num = self.start_value # set value back to start value
                return

            print(self.num)
            self.num += 1

            # this method calls itself after self.delay ms
            self.root.after(self.delay, self._loop)


win = Tk()
sweep = Sweep(win, start_value=0, end_value=5, delay=1000)

init_button = Button(win, text="Start", command=sweep.start)
init_button.grid(row=0, column=1, pady=20, padx=10)

pause_button = Button(win, text="Stop", command=sweep.stop)
pause_button.grid(row=0, column=2, pady=20, padx=10)

reset_button = Button(win, text="Reset", command=sweep.reset)
reset_button.grid(row=0, column=3, pady=20, padx=10)

quit_button = Button(win, text="Quit", command=win.destroy)
quit_button.grid(row=1, column=0, columnspan=3, pady=20, padx=10)

win.mainloop()



RE: Creating a function interrupt button tkinter - AnotherSam - Oct-07-2021

Hello,
Thanks so much for your time and help. I had a follow up if you don't mind.
from tkinter import Button, Tk
  
class Sweep:
    def __init__(self, root, value_List, delay=1000):
        self.root = root
        self.value_List= value_List
        self.delay = delay
        self.running = False
        self.end_value = len(self.value_List)
        self.num = 0
 
    def start(self):
        print("Start method called")
        if not self.running:
            self.running = True
            self._loop()
 
    def stop(self):
        print("Stop method called")
        if self.running:
            self.num = 0
            self.running = False
 
    def _loop(self):
        if self.running:
            if self.end_value is not None and self.end_value <= self.num:
                self.running = False
                print("The End")
                self.num=0
                return
            print(self.value_List[self.num])
            self.num += 1
            self.root.after(self.delay, self._loop)
 
def CreateList(start, stop, numPts):
    SweepList=[]
    SweepList.append(start)
    stepInterval = (stop-start)/(numPts-1)
    for x in range(1,numPts):
        SweepList.append(start + x*stepInterval)
    return SweepList

    
win = Tk()

numList = CreateList(1000,10000,10)
sweep = Sweep(win, numList, 1000)
 
init_button = Button(win, text="Start", command=sweep.start)
init_button.grid(row=0, column=1, pady=20, padx=10)
 
pause_button = Button(win, text="Stop", command=sweep.stop)
pause_button.grid(row=0, column=2, pady=20, padx=10)
 
quit_button = Button(win, text="Quit", command=win.destroy)
quit_button.grid(row=1, column=0, columnspan=3, pady=20, padx=10)
 
win.mainloop()
In my real application I am running this sweep function within a separate function which is called after a button push. I'll better illustrate in a moment. However, working in baby steps. The code above modifies your original class to contain the list to be swept through, and the _loop function to step through self.value_List. In this case I understand the button's commands are well defined because the class is initialized in the main loop.
The code below puts the class definition and list creaction in a separate function outside the main loop, RunSweep. I have the pause button commented out because it returns an error, and rightfully so, that sweep isn't defined. Is there a way to modify that function call to include the class as one of the arguments instead of using sweep.Function? Perhaps have a new button pup up when that function is running and sweep is defined?
from tkinter import Button, Tk
  
class Sweep:
    def __init__(self, root, value_List, delay=1000):
        self.root = root
        self.value_List= value_List
        self.delay = delay
        self.running = False
        self.end_value = len(self.value_List)
        self.num = 0
 
    def start(self):
        print("Start method called")
        if not self.running:
            self.running = True
            self._loop()
 
    def stop(self):
        print("Stop method called")
        if self.running:
            self.num = 0
            self.running = False
 
    def _loop(self):
        if self.running:
            if self.end_value is not None and self.end_value <= self.num:
                self.running = False
                print("The End")
                self.num=0
                return
            print(self.value_List[self.num])
            self.num += 1
            self.root.after(self.delay, self._loop)
 
def CreateList(start, stop, numPts):
    SweepList=[]
    SweepList.append(start)
    stepInterval = (stop-start)/(numPts-1)
    for x in range(1,numPts):
        SweepList.append(start + x*stepInterval)
    return SweepList

def RunSweep(start,stop,numPts,delay):
    numList = CreateList(start,stop,numPts)
    sweep = Sweep(win, numList, delay)
    sweep.start()

    
win = Tk()

init_button = Button(win, text="Start", command=lambda:RunSweep(1000,10000,10,1000))
init_button.grid(row=0, column=1, pady=20, padx=10)
 
#pause_button = Button(win, text="Stop", command=sweep.stop)
#pause_button.grid(row=0, column=2, pady=20, padx=10)
 
quit_button = Button(win, text="Quit", command=win.destroy)
quit_button.grid(row=1, column=0, columnspan=3, pady=20, padx=10)
 
win.mainloop()