【问题标题】:How to use function in tkinter mainloop() [duplicate]如何在 tkinter mainloop() 中使用函数 [重复]
【发布时间】:2018-10-28 06:13:43
【问题描述】:

我决定尝试 Python,到目前为止它很有趣。然而,在玩弄 tkinter 时,我遇到了一个几个小时都无法解决的问题。我读过一些东西并尝试了不同的东西,但没有任何效果。 到目前为止,我已经得到了代码,我认为程序应该可以正常运行。除了我不能让它循环并因此自动更新的事实。

所以我的问题是:如何以无限循环方式调用具有 tkinters 循环选项的函数?

简单的生活游戏: 我基本上写了2个类。存储和处理单个单元格的矩阵 以及通过一些游戏逻辑和基本用户输入利用矩阵类的游戏本身。

首先是游戏类,因为有我的循环问题:

from tkinter import *
from ButtonMatrix import *

class Conway:

 def __init__(self, master, size = 20, cell_size = 2):

    self.is_running = True
    self.matrix = ButtonMatrix(master,size,cell_size)
    self.matrix.randomize()
    self.matrix.count_neighbours()
    self.master = master

    # playbutton sets boolean for running the program in  a loop
    self.playbutton = Button(master, text = str(self.is_running), command = self.stop)
    self.playbutton.grid(row = 0 , column = size +1 )

    #Test button to trigger the next generation manually. Works as itended. 
    self.next = Button(master, text="next", command = self.play)
    self.next.grid(row = 1, column = size +1)


 def play(self): # Calculates and sets the next generation. Intended to be used in a loop

   if self.is_running:
        self.apply_ruleset()
        self.matrix.count_neighbours()
        self.apply_colors()


 def apply_ruleset(self):

    #The ruleset of conways game of life. I wish i knew how to adress each element
    #without using these two ugly loops all the time

    size = len(self.matrix.cells)

    for x in range (size):
        for y in range (size):

            if self.cell(x,y).is_alive():
                if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
                    self.cell(x,y).toggle()
            if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
                self.cell(x,y).toggle()



 def apply_colors(self): #Some flashy colors just for fun  

    size = len(self.matrix.cells)

    for x in range (size):
        for y in range (size):

            if self.cell(x,y).is_alive():
                if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
                    self.cell(x,y).button.configure(bg = "chartreuse3")
            if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
                self.cell(x,y).button.configure(bg = "lightgreen")


 def cell(self,x,y):        
    return self.matrix.cell(x,y)

 def start (self): #start and stop set the boolean for the loop. They work and switch the state properly
    self.is_running = True
    self.playbutton.configure(text=str(self.is_running), command =self.stop)

 def stop (self): 
    self.is_running = False
    self.playbutton.configure(text=str(self.is_running), command =self.start)


#Test program. I can't make the loop work. Manual update via next button                                     works however
root = Tk()
conway = Conway(root)
root.after(1000, conway.play())
root.mainloop()

矩阵(仅供感兴趣的读者):

from tkinter import *
from random import randint

class Cell: 

 def __init__(self,master, cell_size = 1):

    self.alive = False
    self.neighbours = 0

    # initializes a squares shaped button that fills the grid cell
    self.frame = Frame(master, width= cell_size*16, height = cell_size*16) 
    self.button = Button(self.frame, text = self.neighbours, command = self.toggle, bg ="lightgray")
    self.frame.grid_propagate(False) 
    self.frame.columnconfigure(0, weight=1) 
    self.frame.rowconfigure(0,weight=1) 
    self.button.grid(sticky="wens") 


 def is_alive(self):

    return self.alive


 def add_neighbour(self):

    self.neighbours += 1


 def toggle (self):

    if self.is_alive()  :
        self.alive = False
        self.button.configure( bg = "lightgray")
    else:
        self.alive = True
        self.button.configure( bg = "green2")


class ButtonMatrix: 

 def __init__(self, master, size = 3, cell_size = 3):

    self.master = master
    self.size = size
    self.cell_size = cell_size
    self.cells = []
    for x in range (self.size):
        row = []
        self.cells.append(row)
    self.set_cells()


 def cell(self, x, y):

    return self.cells[x][y]


 def set_cells(self):

    for x in range (self.size):
        for y in range (self.size):
            self.cells[x] += [Cell(self.master, self.cell_size)]
            self.cell(x,y).frame.grid(row=x,column=y)


 def count_neighbours(self): # Checks 8 sourounding neighbours for their stats and sets a neighbour counter

    for x in range(self.size):
        for y in range(self.size):

            self.cell(x,y).neighbours = 0

            if y  < self.size-1:
                if self.cell(x,y+1).is_alive(): self.cell(x,y).add_neighbour() # Right
                if x > 0 and self.cell(x-1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Top Right
                if x < self.size-1 and self.cell(x+1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Right

            if x > 0 and self.cell(x-1,y).is_alive(): self.cell(x,y).add_neighbour()# Top
            if x < self.size-1 and self.cell(x+1,y).is_alive():self.cell(x,y).add_neighbour() #Bottom

            if y >  0:
                if self.cell(x,y-1).is_alive(): self.cell(x,y).add_neighbour() # Left
                if x > 0 and self.cell(x-1,y-1).is_alive():  self.cell(x,y).add_neighbour() #Top Left
                if x < self.size-1 and self.cell(x+1,y-1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Left

            self.cell(x,y).button.configure(text = self.cell(x,y).neighbours)


 def randomize (self):
    for x in range(self.size):
       for y in range(self.size):
            if self.cell(x,y).is_alive(): self.cell(x,y).toggle()
            rando = randint(0,2)
            if rando == 1: self.cell(x,y).toggle()

【问题讨论】:

  • 我不确定我是否理解您的问题。是否只是您希望 conway.play 每秒运行一次,直到程序退出,而不是在一秒钟后运行一次,然后再也不运行?
  • @abarnert 是的,这就是我想要的。我现在尝试了很多东西,但没有任何效果:(

标签: python python-3.x tkinter


【解决方案1】:

你的代码有两个问题:

root.after(1000, conway.play())

首先,你不是在告诉 Tkinter 在 1 秒后调用 conway.play,你现在是在调用 conway.play(),它返回 None,然后告诉 Tkinter 在 1 秒后调用 None。你想传递函数,而不是调用它:

root.after(1000, conway.play)

同时,after 并不是说​​“ 1000ms调用一次这个函数”,它的意思是“在1000ms之后调用这个函数一次,然后再也不会”。解决这个问题的简单方法是让函数要求在另外 1000 毫秒内再次调用:

def play(self): # Calculates and sets the next generation. Itended to use in a loop

    if self.is_running:
        self.apply_ruleset()
        self.matrix.count_neighbours()
        self.apply_colors()
    self.master.after(1000, self.play)

这在the docs for after中有解释:

此方法注册一个回调函数,该函数将在给定的毫秒数后调用。 Tkinter 只保证不会早于调用回调;如果系统繁忙,实际延迟可能会更长。

每次调用此方法时,只会调用一次回调。要继续调用回调,您需要在其内部重新注册回调:

class App:
    def __init__(self, master):
        self.master = master
        self.poll() # start polling

    def poll(self):
        ... do something ...
        self.master.after(100, self.poll)

(我假设没有人会关心你的时间是否有一点偏差,所以一个小时后你可能有 3549 步或 3627 而不是 3600+/-1。如果这是一个问题。你必须得到一点更复杂。)

【讨论】:

  • 这个解决方案看起来很有希望,并且非常接近我试图达到的目标,但是我得到一个 snytax 错误 unindet 与任何外部意图级别不匹配。通常这样的东西是通过使用标签和格式化代码来修复的很快。但在这种情况下它不起作用它的这条线 self.master.after(1000, self.play)
  • @TheFool 通常这样的事情是通过not 使用制表符格式化代码来解决的,但恰恰相反。答案中的缩进是正确的。但是,如果您的源文件使用制表符——或者更糟糕的是,混合制表符和空格——并且您将从 SO 复制的代码粘贴到其中,它通常不起作用。在这种情况下,如果您不知道如何让您的编辑器修复问题,只需手动重新输入即可。始终使用 4 个空格进行缩进,从不使用制表符,并使用自动为您完成大部分工作的编辑器,这些问题都会消失。
  • 好吧,我使用退格和空格,现在它可以按我的意愿运行 :) 非常感谢。我会永远记住让函数自己调用,不要把 () 放在所需的函数后面。
猜你喜欢
  • 1970-01-01
  • 2015-10-27
  • 2019-12-25
  • 2015-05-23
  • 2020-08-30
  • 2020-05-16
  • 2019-05-08
  • 2021-06-30
  • 2020-02-16
相关资源
最近更新 更多