keakins13
keakins13

Reputation: 61

Stop continuously running tkinter function(s) without closing GUI

I've tried a couple different things to get this code to stop (after [doesn't stop it], quit[closes GUI]). What I found that "worked" is it throwing an error..... This bothers me. Throwing an error, then just restarting the graph is not ideal. I want to clean this code up (so any other suggestions are appreciated as well!)

Am I missing something? Shouldn't stopping a function be simple? I will post the main code, which is a lot still since there are a lot of functions. The two that I want to stop (based off what prints out) are the update_graph and get_data. If I say I want 5 files, it gets to 5 files (i=5), then restarts at 0 (i=0).

What the code is supposed to do:

First, it shows a blank graph on a GUI. Person can set the number of files they wish to have and set up the sensor integration time. Then, they can start the data acquisition and the graph pops up every second or so until the number of files is reached. This last part is what I cannot get. Stopping the file creation and the graphing when the number of files is reached. (Note: the file creation is perfect in another code. I am only showing this code so that I don't create a ton of files troubleshooting this).

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
from PIL import ImageTk, Image
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
import serial
import random
import os
import datetime
import numpy as np
after_id = None

def get_data():
    #i=0

    rand_x = list(range(100))
    rand_y = [random.randrange(100) for _ in range(100)]
        #create a file

## In my code, this function send data through serial port from the Raspberry pi to the arduino and the arduino send the y axis data back.
##I then create the appropriate x axis
    return rand_x, rand_y


class App(tk.Frame):
    def __init__ (self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.running = False
        self.ani = None
        btns = tk.Frame(self)
        btns.pack()
        lbl = tk.Label(btns, text="Number of times to run")
        lbl.pack(side=tk.LEFT)
        self.points_ent = tk.Entry(btns, width=5)
        self.points_ent.insert(0,'50')
        self.points_ent.pack(side=tk.LEFT)
        lbl = tk.Label(btns, text="Intergration Time")
        lbl.pack(side=tk.LEFT)
        self.interval = tk.Entry(btns, width=5)
        self.interval.insert(0, '100')
        self.interval.pack(side=tk.LEFT)
        self.btn = tk.Button(btns, text='Start', command=self.on_click)
        self.btn.pack(side=tk.LEFT)
        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line, = self.ax1.plot([], [], lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas.show()
        self.canvas.get_tk_widget().pack()
        self.ax1.set_ylim(0, 500)
        self.ax1.set_xlim(0, 100)

    def on_click(self):

        print('onclick')
        if self.ani is None:
            return self.start()

        if self.running:
            self.ani.event_source.stop()
            self.btn.config(text='Un-Pause')
            print('pause')
        else:

            self.ani.event_source.start()
            self.btn.config(text='Pause', command=get_data())
            print('unpause')
        self.running = not self.running

    def start(self):
        global interval
        self.points = int(self.points_ent.get()) + 1
        print(self.points)
        self.ani = animation.FuncAnimation(
            self.fig, self.update_graph,
            frames=self.points,
            interval=int(self.interval.get()),
            repeat=True)
        self.running = True
        self.btn.config(text='Pause')
        self.ani._start()
        print('started animation')

    def update_graph(self, i):
        while os.path.exists(str(directory) + "/Data" + str(i) + "/"):
            i +=1
        subfolder = (str(directory) + "/Data" + str(i) + "/")
        global filename
        filename = "/home/pi/Documents/Serial" + str(i) + ".txt"
        self.line.set_data(*get_data())
        print('update_graph')
        print(i)
        global after_id
        after_id = self.after(1, get_data)
        if i >= self.points - 1:

            self.running = False
            self.ani = None
            self.btn.config(text='Start', command=self.stop())


    def stop(self):
        global after_id
        self.after_cancel(after_id)

Upvotes: 1

Views: 407

Answers (1)

fhdrsdg
fhdrsdg

Reputation: 10542

I think you're missing one very important statement in your update_graph function and have tried a lot with after and changing button commands to try and fix it.

What you're missing in update_graph is simply self.ani._stop() to stop the animation when the desired number of iterations is met.

So putting that in, and removing all of the after calls which I don't think did any good, leaving the button command alone I come to this, which I feel does exactly what you want:

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
import random

def get_data():
    #i=0

    rand_x = list(range(100))
    rand_y = [random.randrange(100) for _ in range(100)]
        #create a file

## In my code, this function send data through serial port from the Raspberry pi to the arduino and the arduino send the y axis data back.
##I then create the appropriate x axis
    return rand_x, rand_y


class App(tk.Frame):
    def __init__ (self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.running = False
        self.ani = None
        btns = tk.Frame(self)
        btns.pack()
        lbl = tk.Label(btns, text="Number of times to run")
        lbl.pack(side=tk.LEFT)
        self.points_ent = tk.Entry(btns, width=5)
        self.points_ent.insert(0,'50')
        self.points_ent.pack(side=tk.LEFT)
        lbl = tk.Label(btns, text="Intergration Time")
        lbl.pack(side=tk.LEFT)
        self.interval = tk.Entry(btns, width=5)
        self.interval.insert(0, '100')
        self.interval.pack(side=tk.LEFT)
        self.btn = tk.Button(btns, text='Start', command=self.on_click)
        self.btn.pack(side=tk.LEFT)
        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line, = self.ax1.plot([], [], lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas.show()
        self.canvas.get_tk_widget().pack()
        self.ax1.set_ylim(0, 500)
        self.ax1.set_xlim(0, 100)

    def on_click(self):

        print('onclick')
        if self.ani is None:
            return self.start()

        if self.running:
            self.ani.event_source.stop()
            self.btn.config(text='Un-Pause')
            print('pause')
        else:

            self.ani.event_source.start()
            self.btn.config(text='Pause')
            print('unpause')
        self.running = not self.running

    def start(self):
        self.points = int(self.points_ent.get()) + 1
        print(self.points)
        self.ani = animation.FuncAnimation(
            self.fig, self.update_graph,
            frames=self.points,
            interval=int(self.interval.get()),
            repeat=True)
        self.running = True
        self.btn.config(text='Pause')
        self.ani._start()
        print('started animation')

    def update_graph(self, i):
        self.line.set_data(*get_data())
        print('update_graph')
        print(i)
        if i >= self.points - 1:

            self.running = False
            self.ani._stop()
            self.ani = None
            self.btn.config(text='Start')


root = tk.Tk()
app = App(root)
app.pack(expand=1, fill=tk.BOTH)
root.mainloop()

Upvotes: 1

Related Questions