user5112571
user5112571

Reputation: 23

How to stop a running function without exiting the Tkinter window entirely?

I'm using Python 2.7, and I'm trying to write a GUI, but am having some issues with my buttons. I currently have everything running properly, but assuming I've made a mistake with my inputs or something, I'd like a way to stop a running function after hitting the "GO" button. My code is way too long to post here, but a simple example is below. How do I make the "Stop" button break the start function, but not quit the window entirely? Maybe something to do with threading?

I'm sort of new with writing GUIs, and I'm not really a programmer, so this isn't really my area of expertise.

The GUI is totally unresponsive while the main function is running. There must be a way to simultaneously run my function while also allowing me to change things in the GUI and hit buttons, but I'm not sure how that works. The updates don't have to be implemented until the next time the "GO" button is hit though.

import time
from Tkinter import *


class Example:
    def __init__(self,master):
        self.startButton = Button(master,text='Start',command=self.start)
        self.startButton.grid(row=0,column=0)

        self.stopButton = Button(master,text='Stop',command=self.stop)
        self.stopButton.grid(row=0,column=1)

        self.textBox = Text(master,bd=2)
        self.textBox.grid(row=1,columnspan=2)

    def start(self):
        self.textBox.delete(0.0,END)
        for i in xrange(1000):
            text = i+1
            self.textBox.insert(END,str(text)+'\n\n')
            time.sleep(1)
        return

    def stop(self):
        """ Do something here to stop the running "start" function """
        pass


root=Tk()
Example(root)
root.mainloop()

Upvotes: 1

Views: 9279

Answers (3)

Petar Ivanov
Petar Ivanov

Reputation: 1

I added some improvement to the answer written on top in way that multiple clicks on start button doesn't corrupt the program:

from tkinter import *

class Example:
    def __init__(self, master):
        self.cancel_id = None

        self.startButton = Button(master,text='Start', command=self.start)
        self.startButton.grid(row=0, column=0)

        self.stopButton = Button(master, text='Stop', command=self.stop)
        self.stopButton.grid(row=0, column=1)

        self.textBox = Text(master, bd=2)
        self.textBox.grid(row=1, columnspan=2)

    def start(self):
        if(self.cancel_id==None):
            self.count = 0
            #self.cancel_id = None
            self.counter()

    def counter(self):
        self.textBox.delete("1.0", END)
        if (self.count < 10):
            self.count += 1
            self.textBox.insert(END, str(self.count)+'\n\n')
            self.cancel_id = self.textBox.after(1000, self.counter)

    def stop(self):
        if self.cancel_id is not None:
            self.textBox.after_cancel(self.cancel_id)
            self.cancel_id = None

root=Tk()
Example(root)
root.mainloop()

Upvotes: 0

martineau
martineau

Reputation: 123531

With Tkinter, such things are commonly done using the universal widget after() method. You should generally not use time.sleep() in a Tkinter program because it prevents the mainloop() from running (which is what is making the GUI unresponsive in your code).

A successful after() call will return an integer "cancel id" which can used to stop the callback just scheduled. This is what's needed by the Stop() method of your Example class to stop the method doing the counting.

from Tkinter import *

class Example:
    def __init__(self, master):
        self.startButton = Button(master,text='Start', command=self.start)
        self.startButton.grid(row=0, column=0)

        self.stopButton = Button(master, text='Stop', command=self.stop)
        self.stopButton.grid(row=0, column=1)

        self.textBox = Text(master, bd=2)
        self.textBox.grid(row=1, columnspan=2)

    def start(self):
        self.count = 0
        self.cancel_id = None
        self.counter()

    def counter(self):
        self.textBox.delete("1.0", END)
        if self.count < 10:
            self.count += 1
            self.textBox.insert(END, str(self.count)+'\n\n')
            self.cancel_id = self.textBox.after(1000, self.counter)

    def stop(self):
        if self.cancel_id is not None:
            self.textBox.after_cancel(self.cancel_id)
            self.cancel_id = None

root=Tk()
Example(root)
root.mainloop()

Upvotes: 4

A O
A O

Reputation: 5698

You could try using a class property of type bool

Above your init method, you could define a property like this: bool stopFlag = False

Then inside your for loop, add an if(!self.stopFlag): break. This will check the stopFlag every iteration, and negate it. So it will default to passing the if check at first.

Then inside your stop method, just set your self.stopFlag = True

So when stop is called, it will flip the flag, and if the start method is running in it's for loop, it will catch this change in the flag and break out.

Upvotes: -1

Related Questions