简体   繁体   中英

I need to slow down a loop in a python tkinter app

I am having a problem with a fairly simple app. It performs properly, but I would like it to perform a little slower.

The idea is to randomly generate a name from a list, display it, then remove it fromthe list every time a button is clicked.

To make it a little more interesting, I want the program to display several names before picking the last one. I use a simple for loop for this. However, the code executes so quickly, the only name that winds up displaying is the last one.

using time.sleep() merely delays the display of the last name. no other names are shown.

here is my code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from tkinter import *
import random
import time

class Application(Frame):
    def __init__(self, master):
        """ Initialize the frame. """
        super(Application, self).__init__(master)  
        self.grid()
        self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
        self.create_widget()

    def create_widget(self):
        self.lbl = Label(self)
        self.lbl["text"] = "Click to spin"
        self.lbl["font"] = ("Arial", 24) 
        self.lbl.grid()

        self.bttn = Button(self)
        self.bttn["text"]= "Spin"
        self.bttn["command"] = self.spin
        self.bttn.grid()

    def spin(self):
        if self.name_list:
            for i in range(5):
                index = random.randrange(len(self.name_list))
                self.lbl["text"] = self.name_list[index]
                self.lbl.grid()
            self.name_list.pop(index)
        else:
            self.lbl["text"] = "No more names"
            self.lbl.grid()

def main():
    root = Tk()
    root.title("Click Counter")
    root.geometry("600x600")

    app = Application(root)

    root.mainloop()

if __name__ == '__main__':
    main()

This is a pretty common class of problems related to GUI programming. The heart of the issue is the window drawing manager. As long as your function is executing, the drawing manager is frozen; updating the label's text will have no apparent effect until your function ends. So if you have a for loop with a sleep(1) command inside, all it will do is freeze everything for five seconds before updating with your final value when the function finally ends.

The solution is to use the after method, which tells Tkinter to call the specified function at some point in the future. Unlike sleep , this gives the drawing manager the breathing room it requires to update your window.

One possible way to do this is to register six events with after : five for the intermediate name label updates, and one for the final name change and pop.

def spin(self):
    def change_name():
        index = random.randrange(len(self.name_list))
        self.lbl["text"] = self.name_list[index]
        self.lbl.grid()
    def finish_spinning():
        index = random.randrange(len(self.name_list))
        self.lbl["text"] = self.name_list[index]
        self.lbl.grid()
        self.name_list.pop(index)
    if self.name_list:
        name_changes = 5
        for i in range(name_changes):
            self.after(100*i, change_name)
        self.after(100*name_changes, finish_spinning)
    else:
        self.lbl["text"] = "No more names"
        self.lbl.grid()

(disclaimer: this is only a simple example of how you might use after , and may not be suitable for actual use. In particular, it may behave badly if you press the "spin" button repeatedly while the names are already spinning. Also, the code duplication between change_name and finish_spinning is rather ugly)

The code as it is can show the same item twice since it chooses a new random number each time and so will choose the same number part of the time. Note that you do not pop until after the loop which means that each time you run the program you will have one less name which may or may not be what you want. You can use a copy of the list if you want to keep it the same size, and/or random.shuffle on the list and display the shuffled list in order. Also you only have to grid() the label once,

class Application():
    def __init__(self, master):
        """ Initialize the frame. """
        self.master=master
        self.fr=Frame(master)
        self.fr.grid()
        self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
        self.ctr=0
        self.create_widget()

    def create_widget(self):
        self.lbl = Label(self.master width=30)
        self.lbl["text"] = "Click to spin"
        self.lbl["font"] = ("Arial", 24) 
        self.lbl.grid()

        self.bttn = Button(self.master)
        self.bttn["text"]= "Spin"
        self.bttn["command"] = self.spin
        self.bttn.grid()

    def change_label(self):
        self.lbl["text"] = self.name_list[self.ctr]
        self.ctr += 1
        if self.ctr < 5:
            self.master.after(1000, self.change_label)
        else:
            self.ctr=0

    def spin(self):
        if self.name_list and 0==self.ctr:  # not already running
            random.shuffle(self.name_list)
            self.change_label()
        else:
            self.lbl["text"] = "No more names"

if __name__ == '__main__':
    root = Tk()
    root.title("Click Counter")
    root.geometry("600x600")

    app = Application(root)

    root.mainloop()

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