Как работает метод after в tkinter? - PullRequest
1 голос
/ 16 июня 2019

Я новичок в программировании и пытаюсь сделать простую анимацию, чтобы лучше учиться.Я только что выучил python (все еще nooby) и начал изучать tkinter.Я пытаюсь сделать анимацию «Игры жизни Конвея», потому что она имеет очень простые принципы и выглядит круто.Мне удалось заставить мой код работать, но я действительно не понимаю, как.Дело в том, что метод после того, как я не могу понять, как он работает.

Часть кода, которую я не понимаю, - это метод, называемый 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.Затем я нахожу мертвые ячейки, которые могли бы стать живыми и перебирать мертвые и живые клетки в

Если состояние ячейки совпадает с предыдущим или нет живых клеток: тогда функция getnewstate возвращает None.

В методе start я затем вызываю функцию getnewstate и отрисовываю ее содержимое до тех пор, пока celstate не вернет None (с функцией startloop, которая вызывает себя с помощью метода after).Я не понимаю, почему «цикл закончен» может быть напечатан, если startloop еще не остановился.Несмотря на то, что я не понимаю эту часть, код все еще работает как задумано, что только делает его более раздражающим для меня.Может кто-нибудь помочь прояснить, что происходит ??

  • Переменная dist представляет размер ячейки в пикселях
  • Вы можете нарисовать новые ячейки, используя левую кнопку мыши, или стереть существующие, используяправая кнопка.(Самое интересное, что вы можете сделать это, пока анимация еще продолжается)

Я уверен, что проблема возникает, потому что я не совсем понимаю, как работает основной цикл

1 Ответ

2 голосов
/ 16 июня 2019

Метод tkinter after эффективно отправляет сообщение в mainloop () для запуска функции обратного вызова в течение n миллисекунд.Ваша start функция отправляет это сообщение, затем печатает «цикл завершен».Он не ждет возврата после вызова, прежде чем продолжить выполнение.Через 100 мс он вызывает startloop (), пересчитывает и отображает новую сетку.Если он действительно дождется обратного вызова, он заморозит пользовательский интерфейс во время ожидания.Функция after позволяет вам запускать код после задержки, но при этом все еще имеет активный пользовательский интерфейс.

Я изменил вашу функцию запуска, чтобы она печатала «цикл завершен» вместо того, чтобы возвращать 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

...