after 方法在 tkinter 中如何工作?

How does the after method works in tkinter?

我是编程新手,我正在尝试制作一个简单的动画以更好地学习。刚刚学了python(还是nooby)开始学tkinter。 我正在尝试制作康威生命游戏的动画,因为它的原理非常简单而且看起来很酷。 我已经设法让我的代码正常工作,但我真的不明白怎么做。 问题是我无法理解它是如何工作的。

我不明白的代码部分是名为 start 的方法。 我真的不明白"loop finished"怎么可以在startloop函数returnsNone之前打印(这应该和说动画还没有停止一样)

import tkinter as tk


width = 1400
height = 600
dist = 5
drawlines = False

celstate = set()
numcol = width//dist
numrow = height//dist

def getdeadcells(setcells):
    global celstate
    deadcells = set()
    for cell in setcells:
        i, j = cell
        list = [(i-1, j-1), (i, j-1), (i+1, j-1),
                (i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
        for cel in list:
            if cel not in celstate:
                deadcells.add(cel)
    return deadcells

def getnewstate():
    def neight(cell):
        i, j = cell
        count = 0
        list = [(i-1, j-1), (i, j-1), (i+1, j-1),
                (i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
        for cel in list:
            if cel in celstate:
                count +=1
        return count

    global celstate, numcol, numrow
    alivecells = celstate.copy()
    deadcells = getdeadcells(alivecells)
    newstate = set()
    for cell in alivecells:
        neigh = neight(cell)
        if neigh == 2 or neigh == 3:
            newstate.add(cell)
    for cell in deadcells:
        neigh = neight(cell)
        if neigh == 3:
            newstate.add(cell)
    if newstate == celstate:
        return None
    else:
        celstate = newstate
        if len(newstate) == 0:
            return ""
        else:
            return newstate

def getcords(x, y):
    col = x//dist
    row = y//dist
    return (col, row)


class GUI():
    def __init__(self, master, width, height, dist):
        master.geometry("{}x{}".format(width, height))
        master.bind("<Key>", self.start)
        self.master = master
        self.width = width
        self.height = height
        self.dist = dist
        self.canvas = tk.Canvas(master, width=width, height=height)
        self.canvas.pack(expand=True)
        self.drawlimits(dist)

    def start(self, event):
        if event.keycode == 32 or event.keycode == 13:
            def startloop():
                newstate = getnewstate()
                if newstate == None:
                    return None
                elif newstate == "":
                    self.canvas.delete("rect")
                    return None
                else:
                    self.canvas.delete("rect")
                    self.fillrects(list(newstate))
                    self.master.after(100, startloop)
            startloop()
            print("loop finished")

    def drawlimits(self, dist):
        if self.width % dist == 0 and self.height % dist == 0:
            self.canvas.bind("<B1-Motion>", self.drawcells)
            self.canvas.bind("<ButtonRelease-1>", self.drawcells)
            self.canvas.bind("<B3-Motion>", self.killcell)
            self.canvas.bind("<ButtonRelease-3>", self.killcell)
            if drawlines:
                xsteps = self.width/dist
                ysteps = self.height/dist
                for num in range(int(xsteps-1)):
                    self.canvas.create_line((num+1)*dist, 0, (num+1)*dist, self.height)
                for num in range(int(ysteps-1)):
                    self.canvas.create_line(0, (num+1)*dist, self.width, (num+1)*dist)

    def drawcells(self, event):
        cell = getcords(event.x, event.y)
        if cell not in celstate:
            self.fillrects([cell])
            celstate.add(cell)

    def killcell(self, event):
        cell = getcords(event.x, event.y)
        if cell in celstate:
            celstate.remove(cell)
            col, row = cell
            tag = "{},{}".format(col, row)
            obj.canvas.delete(tag)

    def fillrects(self, cords):
        for gcords in cords:
            col, row = gcords
            tag = "{},{}".format(col,row)
            dist = self.dist
            self.canvas.create_rectangle(col*dist, row*dist, (col+1)*dist, (row+1)*dist,
            fill="black", tags=(tag, "rect"))


root = tk.Tk()
obj = GUI(root, width, height, dist)
root.mainloop()


代码的工作原理如下: 我只保存在 celstate 集中存活的细胞。 然后,我找到可以复活的死细胞,并遍历

中的死细胞和活细胞

如果celstate与之前的celstate相同或者没有活细胞:那么函数getnewstate returns None.

然后在 start 方法中,我调用函数 getnewstate 并绘制其内容,直到 celstate returns None(使用 startloop 函数通过 after 方法调用自身)。 我不明白如果 startloop 还没有停止,为什么可以打印 "loop finished"。 即使我不理解这部分,代码仍然按预期工作,这对我来说更加烦人。 谁能帮忙弄清楚这是怎么回事??

我确定问题来了,因为我不太了解主循环的工作原理

tkinter after 方法有效地在 n 毫秒内将消息发送到 mainloop() 到 运行 回调函数。您的 start 函数发送此消息然后打印 "loop finished"。在继续执行之前,它不会等待 return 的 after 回调。 100 毫秒后,它调用 startloop() 并重新计算并显示新网格。如果它确实等待 return 的回调,它会在等待时冻结 UI。 after 函数允许您在延迟后 运行 编码,但仍然有一个活动的 ui.

我已经修改了您的启动函数以在代码的退出部分打印 "loop finished" 而不是 returning None。

def start(self, event):
    if event.keycode == 32 or event.keycode == 13:
        def startloop():
            newstate = getnewstate()
            if newstate == None:                
                print("loop finished")
            elif newstate == "":
            self.canvas.delete("rect")
                print("loop finished")
            else:
                self.canvas.delete("rect")
                self.fillrects(list(newstate))
                self.master.after(100, startloop)
        startloop()

您可能遇到的一个问题是,生命游戏可以达到每两个周期 return 相同状态的稳定条件。有些形状的循环周期甚至更长。

HTH