【问题标题】:Stop continuously running tkinter function(s) without closing GUI在不关闭 GUI 的情况下停止连续运行 tkinter 函数
【发布时间】:2018-05-17 13:28:56
【问题描述】:

我尝试了几种不同的方法来停止此代码(在 [不停止它] 之后,退出 [关闭 GUI])。我发现“有效”的是它抛出了一个错误......这让我很困扰。抛出错误,然后重新启动图表并不理想。我想清理这段代码(因此也欢迎任何其他建议!)

我错过了什么吗?停止功能不应该很简单吗?我将发布主要代码,因为有很多功能,所以仍然很多。我想要停止的两个(基于打印出来的)是 update_graph 和 get_data。如果我说我想要 5 个文件,它会得到 5 个文件 (i=5),然后从 0 (i=0) 重新开始。

代码应该做什么:

首先,它在 GUI 上显示一个空白图形。人们可以设置他们希望拥有的文件数量并设置传感器集成时间。然后,他们可以开始数据采集,图表每隔一秒左右弹出一次,直到达到文件数量。最后一部分是我无法得到的。达到文件数时停止文件创建和绘图。 (注意:文件创建在另一个代码中是完美的。我只显示此代码,这样我就不会创建大量文件来解决这个问题)。

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)

【问题讨论】:

  • “停止函数”是什么意思? (通常很难停止一个函数,因为函数会一直运行直到它们返回)你能提供一个minimal reproducible example 的问题吗?你能解释一下代码应该做什么吗?
  • 您可以尝试创建一个MCVE 吗?就像现在一样,没有人可以复制粘贴这段代码来查看它的作用并对其进行调试。如果您可以从update_graphget_data 函数中去除重现问题所不需要的代码,并让它们返回随机数之类的东西,那么任何人都可以运行它并尝试帮助您。还请包括使用的导入语句。
  • 提供了更好的描述。希望有帮助。我现在也将导入语句粘贴进去。这段代码下面是一个简单的 def main() 和 root = tk.Tk() 我也可以添加它。至于简化它,我使用的是串行端口。所以我不确定随机数据与真实数据涌入设备的关系如何(我使用的是树莓派)。但我可以试试! :)
  • 感谢编辑!我的意思是问题不在于从串行端口获取数据,因此所有能够获取数据的代码都与这个问题无关。特别是由于人们没有您的文件和带有您的配置的树莓派,因此包含此代码会使其他人更难调试。返回随机的“虚拟”数据可以解决这个问题。基本上,这就是 MCVE 中的 Minimal 的全部意义所在。当然,如果取出这部分代码确实可以解决问题,你应该保留它(并告诉我们,因为这是非常有价值的信息)。
  • 所以,在树莓派上尝试了这段代码(带有随机数据),问题仍然存在。目标是在设定的文件数量后停止绘图。它会自动设置为 50。但是,如果我想进行快速调试/故障排除,我通常会将其更改为 10 之类的其他值。如果这对任何人都不起作用,我可以尝试重做它,以便大家更轻松地进行调试。

标签: python-3.x function matplotlib tkinter


【解决方案1】:

我认为您在 update_graph 函数中遗漏了一个非常重要的语句,并且已经尝试了很多 after 并更改按钮命令以尝试修复它。

您在 update_graph 中缺少的只是 self.ani._stop() 在满足所需的迭代次数时停止动画。

所以把它放进去,并删除我认为没有任何好处的所有 after 调用,只留下按钮命令我来这里,我觉得这正是你想要的:

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()

【讨论】:

  • 这完美!非常感谢!不敢相信这是一条线,这似乎是一个大问题。这个问题我已经有一段时间了,所以我很高兴它终于解决了。 :)
  • 不客气!感谢您像您一样接受我们的回复并编辑代码和问题。这确实帮助我能够运行您的代码并找到解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-23
  • 2021-09-15
相关资源
最近更新 更多