I need to slow down the loop in a python tkinter application

I have a problem with a fairly simple application. It works correctly, but I would like it to perform a little slower.

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

To make this a little more interesting, I want the program to display several names before selecting the last one. I use a simple loop for this. However, the code runs so fast, the only name that completes the display is the last.

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

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() 
+5
source share
2 answers

This is a fairly common class of problems associated with GUI programming. The heart of the problem is the window drawing manager. While your function is running, the drawing manager is frozen; updating the label text will not have a visible effect until your function ends. Therefore, if you have a for loop with the sleep(1) command inside, all it does is freeze everything for five seconds before updating its final value when the function finally finishes.

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 room manager a breathing room that requires updating your window.

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

 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 just a simple example of how you can use after , and may not be suitable for actual use. In particular, it can behave badly if you press the spin button several times while the names are already spinning Also, code duplication between change_name and finish_spinning pretty ugly)

+3
source

The code, as it is, can show the same element twice, as it selects a new random number each time and therefore selects the same part of the time. Note that you do not exit before 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 in the list and display the shuffled list in order. In addition, you should have only one () shortcut (),

 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() 
+1
source

Source: https://habr.com/ru/post/1209143/


All Articles