【问题标题】:While loop not working properly in tkinter window虽然循环在 tkinter 窗口中无法正常工作
【发布时间】:2021-05-12 00:29:30
【问题描述】:

我目前遇到一个问题,我试图在 tkinter 窗口内运行一个 while 循环。它一直等到 while 循环完成才能实际显示窗口。我想要发生的是窗口出现,然后 while 循环将开始。经过研究发现跟root.mainloop()函数有关,但不知道怎么改。

#creates new window
root = Tk()

#makes backdrop picture of map
C = Canvas(root, bg="black", height=780, width=1347)
C.grid(row=0,column=0)
filename = PhotoImage(file = "map4.png")
background_label = Label(root, image=filename)
background_label.place(x=0, y=0, relwidth=1, relheight=1)

totalDeaths = 0
totalCases = 0
totalPopulation = 15000
dayCount = 0

#loops until total deaths = population
while totalDeaths < totalPopulation:
    totalDeaths += 1
    time.sleep(1)

root.mainloop()

【问题讨论】:

  • while 循环需要 15000 秒(4 小时多一点)才能完成。
  • 我刚刚这样做是为了使代码更易于阅读。有一个复杂的函数,我已将其替换为 totalDeaths+= 1
  • 我们只能看到您在问题中输入的代码。我们无法知道您的计算机上有什么代码。您发布的代码休眠了四个多小时。

标签: python python-3.x loops tkinter while-loop


【解决方案1】:

不要将sleeptkinter 一起使用。相反,使用名为after的方法:

import tkinter as tk


class App:
    def __init__(self):
        self.root = tk.Tk()
        self.total_deaths = 0
        self.label = tk.Label(text='')
        self.label.pack()
        self.update_deaths()
        self.root.mainloop()

    def update_deaths(self):
        self.total_deaths += 1
        self.label.configure(text=f'Total deaths: {self.total_deaths}')
        self.root.after(1000, self.update_deaths)

App()

输出:

【讨论】:

  • 我已经大大简化了我的代码(类、按钮、标签),所以我不知道这是否会有所帮助。如果我要编辑我之前编写的代码以使其更具上下文,可以吗?
  • 没有。你需要在这里发帖Minimal, Reproducible Example
【解决方案2】:

while 循环完全按照设计工作。您实际上已经要求它休眠 15,000 秒,这就是它正在做的事情。在睡眠期间,tkinter 无法刷新显示或处理任何类型的事件。

在创建 GUI 时,一个经验法则是,您永远不应该在 GUI 所在的线程中使用大的 while 循环。 GUI 已经有一个一直在运行的无限循环。

因此,第一步是将循环内的代码移动到函数中。在 UI 中,将 UI 代码与非 UI 代码分开通常很好,因此在这种情况下,我推荐两个函数:一个用于计算,另一个用于更新显示。这将更容易替换 UI 或计算方法,而无需重写整个程序。它还可以更轻松地通过单元测试来测试计算功能。

所以,让我们从一个计算死亡人数的函数开始。根据 cmets 的其他答案,您似乎有一个复杂的公式可以做到这一点,但简单地增加总数就足以进行模拟。

def update_deaths():
    global totalDeaths
    totalDeaths += 1

接下来,您需要一种显示这些死亡人数的方法。您发布的代码没有任何方法可以做到这一点,因此此解决方案需要添加 Label 以显示当前的死亡人数。如何真正做到这一点取决于您,但以下代码说明了一般原则:

death_label = Label(...)
...
def update_display():
    global totalDeaths
    death_label.configure(text=f"Total Deaths: {totalDeaths}")

谜题的第三部分是模拟while 循环的代码。它的工作是更新死亡人数,然后每秒更新一次显示,直到整个人口都死亡。

我们使用after 方法来做到这一点,该方法可以安排函数在未来运行。通过使用after而不是睡眠,它允许mainloop能够继续处理诸如按钮点击、按键、更新显示的请求等事件。

def simulate_deaths():
    global totalDeaths
    global totalPopulation
    
    update_deaths()
    update_display()

    if totalDeaths < totalPopulation:
        root.after(1000, simulate_deaths)

如果您在程序开始时调用该函数一次,它将继续每秒调用一次,直到满足条件。

after 方法返回一个标识符,您可以使用该标识符在下一次迭代之前取消该函数。如果您将其保存在全局变量(或实例变量,如果您正在使用类)中,您可以通过使用该标识符调用 after_cancel 来停止模拟。

例如:

def simulate_deaths():
    global after_id
    ...
    after_id = root.after(1000, simulate_deahts)
    

def stop_simulation():
    root.after_cancel(after_id)

【讨论】:

  • 感谢布莱恩!如何处理这种情况的一个很好的解释,我不能感谢你。希望你有一个美好的一天!
【解决方案3】:

我相信你的 while 循环永远不会结束,因为 totalDeaths 总是小于 totalPopulation

你可以有这样的东西:

while totalDeaths < totalPopulation:
    if somebodyDies:
        totalDeaths+=1   

    else:
        continue

【讨论】:

  • 我试图让它更易于阅读,所以去掉了一个复杂的函数,所以我现在对其进行了编辑以使其符合逻辑
  • 是的,只是创建了一个模板……结果他只是把它弄糊涂了一点,这不是问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多