after方法在tkinter中如何工作?

时间:2019-06-16 11:22:15

标签: python animation tkinter conways-game-of-life

我是编程新手,我正在尝试制作一个简单的动画以更好地学习。我刚刚学习了python(仍然是nooby)并开始学习tkinter。 我正在尝试制作Conway的《人生游戏》的动画,因为它具有非常简单的原理并且看上去很酷。 我设法设法使我的代码正常工作,但我真的不明白如何做。 问题是我无法理解其工作原理之后。

我不理解的代码部分是称为start的方法。 我真的不明白在startloop函数返回None之前如何打印“循环完成”(这应该与说动画还没有停止一样)

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与先前的相同,或者没有活动的单元格,则函数getnewstate返回None。

然后在start方法中,我调用函数getnewstate并绘制其内容,直到celstate返回None(使用startloop函数使用after方法调用其自身)。 我不明白,如果startloop还没有停止,为什么可以打印“循环完成”。 即使我不理解这部分内容,代码仍然可以按预期工作,这对我来说更加令人生厌。 任何人都可以帮助澄清发生了什么事吗?

  • dist变量表示像元大小(以像素为单位)
  • 您可以使用鼠标左键绘制新的单元格,或使用鼠标右键删除现有的单元格。 (最酷的部分是您可以在动画仍在播放时执行此操作)

我确定问题是因为我不太了解mainloop的工作原理

1 个答案:

答案 0 :(得分:2)

tkinter after方法有效地将消息发送到mainloop()以在n毫秒内运行回调函数。您的start函数发送此消息,然后打印“循环完成”。在执行之前,它不等待after回调返回。 100毫秒后,它将调用startloop()并重新计算并显示新的网格。如果确实等待回调返回,它将在等待时冻结UI。 after函数使您可以在延迟后运行代码,但仍具有活动的ui。

我修改了您的启动函数,以打印“循环完成”,而不是在代码的退出部分不返回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()

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

HTH