Python Tkinter动画将变得无响应并崩溃,除非在每个帧上调用root.update()

Python Tkinter动画将变得无响应并崩溃,除非在每个帧上调用root.update(),python,tkinter,Python,Tkinter,我正在尝试在窗口上播放一些简单的动画。这是我第一次使用Tkinter,所以我在每一帧设置了对root.update的调用,以确保它显示在屏幕上,否则帧速率有点不稳定。我现在了解到这是一种非常糟糕的做法,并尝试将其完全删除,或者用对root.update_idletasks的调用替换它。奇怪的是,当我这样做的时候,窗口变得没有反应,最终崩溃 我试着将代码剥离到如下所示的最小值,但问题仍然存在 从tkinter进口* 从tkinter导入ttk 从PIL导入ImageTk,图像 班级申请: 定义初始

我正在尝试在窗口上播放一些简单的动画。这是我第一次使用Tkinter,所以我在每一帧设置了对root.update的调用,以确保它显示在屏幕上,否则帧速率有点不稳定。我现在了解到这是一种非常糟糕的做法,并尝试将其完全删除,或者用对root.update_idletasks的调用替换它。奇怪的是,当我这样做的时候,窗口变得没有反应,最终崩溃

我试着将代码剥离到如下所示的最小值,但问题仍然存在

从tkinter进口* 从tkinter导入ttk 从PIL导入ImageTk,图像 班级申请: 定义初始自我: 窗口设置 self.root=Tk self.root.geometry'512x512' 协议'WM_DELETE_WINDOW',self.root self.screen=ttk.Labelself.root self.screen.placerelx=.5,rely=.5,anchor=c 调用动画函数 self.state='Idle' self.Animationself.state[self.Idle1,self.Idle2],500 self.root.mainloop 动画功能 def Animationself、状态、帧列表、帧持续时间: 对于rangelenframelist中的i: 如果self.state==状态: 框架=框架列表[i] self.screen.configureimage=帧 self.root.update这是我要删除的行 self.root.afterframeduration 其他: 回来 self.AnimationState、帧列表、帧持续时间 图像列表 def Idle1self: 返回ImageTk.PhotoImageImage。打开“Image1.tif”。调整大小512 def Idle2self: 返回ImageTk.PhotoImageImage。打开“Image2.tif”。调整大小512 def湮灭自我: self.root.eval'::ttk::CancelRepeat' self.state='退出' 自我毁灭 应用 这闻起来你的代码中有一个更大的错误,你的第一个错误是不小心被阻止了,但是我没有主意,我还没能用谷歌搜索这个错误。如果有任何帮助,我们将不胜感激。

您不应该使用for循环,因为它可能需要一些时间,如果帧之间没有时间,它将毫无意义。不要在动画中运行动画,因为它会创建您不需要的递归。使用后期动画

我使用self.current\u frame保存要显示的帧的信息。动画仅显示一帧,更改self.current_frame中的值并使用after再次运行它-它将替换for循环。它还取代了递归

您还可以只加载一次图像,并将图像保留在列表中,而不是函数名 从tkinter进口* 从tkinter导入ttk 从PIL导入ImageTk,图像

class Application():

    def __init__(self):

        # WINDOW SETUP
        self.root = Tk()
        self.root.geometry('512x512')
        self.root.protocol('WM_DELETE_WINDOW', self.annihilation)

        self.screen = ttk.Label(self.root)
        self.screen.place(relx=.5, rely=.5, anchor="c")

        # CALL THE ANIMATION FUNCTION
        self.state = 'Idle'

        self.current_frame = 0
        self.animation(self.state, [self.idle1, self.idle2], 500)

        self.root.mainloop()


    # ANIMATION FUNCTION
    def animation(self, State, framelist, frameduration):
        if state == self.state:
            # change image
            self.frame = framelist[self.current_frame]()
            self.screen.configure(image=self.frame)

            # get next frame (or first frame)
            self.current_frame = (self.current_frame+1) % len(framelist)

        # run again after 'frameduration' milliseconds
        self.root.after(frameduration, self.animation, State, framelist, frameduration)


    # LIST OF IMAGES
    def idle1(self):
        return ImageTk.PhotoImage(Image.open('Image1.tif').resize((512, 512)))

    def idle2(self):
        return ImageTk.PhotoImage(Image.open('Image2.tif').resize((512, 512)))


    def annihilation(self):
        self.root.eval('::ttk::CancelRepeat')
        self.state = 'Quitting'
        self.root.destroy()

Application()

我将名称改为小写,因为

我认为不应该在动画中使用for循环,也不应该在创建递归的动画中运行动画。它会花费很长时间,然后它会阻塞mainloop,mainloop无法从系统中获取事件,将事件发送到小部件,重新绘制小部件,运行分配给after的函数,等等。您必须使用update给mainloop一些时间来完成这一切。您应该使用after再次运行动画。谢谢您的回答!为了避免多次调用动画,我尝试了类似的方法,但在添加不同的动画时会出现一些问题:我如何告诉程序立即停止当前动画以立即启动另一个动画?如果我不这样做,画面就会混在一起。另外,虽然我同意这种解决方案最适合我的情况,但您是否介意解释一下为什么我当前的代码似乎需要调用.update?它可能会帮助一些人,我很好奇。每个GUI和游戏中的主要元素是事件循环mainloop。它运行循环,从系统中获取键/鼠标事件,将事件发送到小部件,更新小部件中的值,在按下按钮时启动函数,运行after添加的函数,并在窗口中绘制和重画小部件。如果您运行长时间运行的函数,即while.True或time.sleep或recursion,则它不会返回mainloop,也不会重新绘制小部件,您会看到冻结的GUI。您的动画会在最后再次运行Animation,它会在最后运行Animation,以此类推。因此,它永远不会停止运行,也永远不会返回mainloop,也不会重新绘制小部件。若您运行root.update,那个么它将运行一个事件循环,可以从系统中获取事件,更新小部件并重新绘制它们!好吧,现在说得通了。我用过你的方法,现在一切都好得多了,反应也快多了。谢谢你的时间!要控制后,可以使用两种方法或两种方法。1使用变量running停止动画-如果正在运行:在…之后。。。。在开始时,设置running=True,并使用running=False停止。它将在显示下一帧后停止,以便您可以在运行时将动画中的所有代码放入其中。2在将函数放入列表后,mainloop将运行它 稍后,它也会给你这个作业的ID-job_ID=after,你可以在取消joib_ID后删除它,但是如果函数现在正在运行,那么你仍然需要第一个版本,这样你就可以同时使用这两种方法了。