Posts: 6
Threads: 4
Joined: Sep 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()
Posts: 2,085
Threads: 10
Joined: May 2017
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()
Posts: 6
Threads: 4
Joined: Sep 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()
|