简体   繁体   中英

While loop not working properly in tkinter window

I am currently having an issue where I am trying to run a while loop inside a tkinter window. It waits until the while loop finishes for it to actual show the window. What I want to happen is for the window to come up, and then the while loop will begin. After research I found that it is something to do with the root.mainloop() function, but I am not sure how to change it.

#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()

Don't use sleep with tkinter . Instead, use method called 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()

Output:

在此处输入图像描述

The while loop is working exactly as designed. You have in effect asked it to sleep for 15,000 seconds so that's what it is doing. While it is sleeping, tkinter is unable to refresh the display or process events of any type.

When creating GUIs, a rule of thumb is that you should never have a large while loop in the same thread as the GUI. The GUI already has an infinite loop running all the time.

So, the first step is to move the code which is inside your loop to a function. In a UI it's often good to separate the UI code from non-UI code, so in this case I recommend two functions: one to do the calculation and one to update the display. This will make it easier to replace either the UI or the method of calculation without having to rewrite the whole program. It will also make it easier to test the calculation function with a unit test.

So, let's start with a function that calculates the deaths. Based on comments to other answers, it appears that you have a complicated formula for doing that but simply incrementing the total is good enough for a simulation.

def update_deaths():
    global totalDeaths
    totalDeaths += 1

Next, you need a way to display those deaths. The code you posted doesn't have any way to do that, so this solution requires the addition of a Label to show current deaths. How you do it for real is up to you, but the following code illustrates the general principle:

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

The third piece of the puzzle is the code to simulate your while loop. Its job is to update the deaths and then update the display every second until the entire population has died.

We do that with the after method which can schedule a function to be run in the future. By using after rather than sleep, it allows mainloop to be able to continue to process events such as button clicks, key presses, requests to update the display, etc.

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

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

If you call this function once at the start of your program, it will continue to be called once a second until the condition is met.

The after method returns an identifier, which you can use to cancel the function before its next iteration. If you save that in a global variable (or instance variable if you're using classes), you can stop the simulation by calling after_cancel with that identifier.

For example:

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

def stop_simulation():
    root.after_cancel(after_id)

I believe your while loop is never ending since totalDeaths will always be smaller than totalPopulation

You could have something like this:

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

    else:
        continue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM