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

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

Related

Class atributes get linked when using tkinter

I'm using a tkinter interface to read in some information like file path, measurement variables etc. for my code. And I read them out with the use_entry function which just remains here as a kill command for the app. Then I run the code a few times to analyze different data sets.
It seems to work fine but i noticed that if two attributes get the same value (here exp and pwr) they get scrambled. Meaning that both become the same value permanently. So if i set exp to A and pwr to B i can freely change them , but as soon as I set both to the same string or int or whatever they start being changed simultaneously (here both are "A"). I can't resolve this unless i restart the consol or change one of the values outside of tkinter.
import tkinter as tk
class App:
def __init__(self, parent):
self.exp="A"
self.pwr="A"
self.parent=parent
self.exposure = tk.Entry(parent, textvariable=self.exp)
self.exposure.pack()
self.power = tk.Entry(parent, textvariable=self.pwr)
self.power.pack()
self.button4 = tk.Button(parent,
text="Done",
command=self.use_entry)
self.button4.pack()
def use_entry(self):
self.contents = (float(self.power.get()),float(self.exposure.get()))
self.parent.destroy()
root = tk.Tk()
app = App(root)
root.mainloop()
I'm rather new to using tkinter and I would be glad if someone could point out my mistake.
Use tk.StringVar instead of normal strings for textvariable in your entries.
self.exp = tk.StringVar(value='A')
self.pwr = tk.StringVar(value='A')
self.parent=parent
self.exposure = tk.Entry(parent, textvariable=self.exp)
self.exposure.pack(side=tk.LEFT,anchor=tk.W)
self.power = tk.Entry(parent, textvariable=self.pwr)
self.power.pack()

How To Make A Buttons Appear Progressively In tkinter

I have made a small application with tkinter and Python 3 which has four buttons on the top of the window to form a menu. It works fine but I want to know how to make the buttons appear along the window over a period of time starting from a single button in the center when first started rather than being statically placed in the center.
Here is my script so far:
import tkinter as tk
class utilities(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.window()
def window(self):
self.pluginrun = tk.Button(self)
self.pluginrun["text"] = "Run Existing Plugin"
self.pluginrun["command"] = self.run_plugin
self.pluginrun.pack(side="left")
self.owning = tk.Button(self)
self.owning["text"] = "Add A New Plugin"
self.owning["command"] = self.plugin
self.owning.pack(side="left")
self.webpage = tk.Button(self)
self.webpage["text"] = "Webpage"
self.webpage["command"] = self.web
self.webpage.pack(side="left")
self.more_info = tk.Button(self)
self.more_info["text"] = "More"
self.more_info["command"] = self.more
self.more_info.pack(side="left")
def run_plugin(self):
print('Running Plugin')
def plugin(self):
print('Available Extensions')
def web(self):
print("Opening Webpage To Python.org")
def more(self):
print('Made Entirely In Python')
root = tk.Tk()
root.geometry('500x500')
show = utilities(master=root)
show.mainloop()
Which gives this result:
When first opened I would like it to look like this:
and over a period of time for more buttons to appear alongside one at a time until it looks like the first image.
How can this be done?
You can add all your buttons to a list and then use a repeating timed method to pack each button in the list one at a time at a set interval.
I created a counter that we can use to keep track of what button is going to be packed next from the list.
I also created a new list to store all the buttons in.
Then I modified your window() method to add each button to the list instead.
The last thing was to create a timed method that would use the self.counter attribute I created to keep track of what button is to be packed next.
In tkinter the best method to use to keep a timed loop or set a timer for anything is to use after(). Using sleep() or wait() in tkinter will only cause the entire tkinter app to freeze.
Take a look at the below code.
import tkinter as tk
class utilities(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.list_of_buttons = []
self.counter = 0
self.window()
def window(self):
for count in range(4):
self.list_of_buttons.append(tk.Button(self))
pluginrun = self.list_of_buttons[0]
pluginrun["text"] = "Run Existing Plugin"
pluginrun["command"] = self.run_plugin
owning = self.list_of_buttons[1]
owning["text"] = "Add A New Plugin"
owning["command"] = self.plugin
webpage = self.list_of_buttons[2]
webpage["text"] = "Webpage"
webpage["command"] = self.web
more_info = self.list_of_buttons[3]
more_info["text"] = "More"
more_info["command"] = self.more
self.timed_buttons()
def timed_buttons(self):
if self.counter != len(self.list_of_buttons):
self.list_of_buttons[self.counter].pack(side ="left")
self.counter +=1
root.after(1500, self.timed_buttons)
def run_plugin(self):
print('Running Plugin')
def plugin(self):
print('Available Extensions')
def web(self):
print("Opening Webpage To Python.org")
def more(self):
print('Made Entirely In Python')
root = tk.Tk()
root.geometry('500x500')
show = utilities(master=root)
show.mainloop()
Add the Buttons inside a Frame, which you centre, and then as you add more Buttons, the Frame should centre them. If not, you may need to call root.update(), to re-centre the Frame.

How to Clear the window in tkinter (Python)?

I want to hide/remove all the buttons from my window (temporarily) with the "hide_widgets" function so I can put them back after but its just not working for me, I have tried using grid_hide() and destroy() and anything I have tried so for from searching stackoverflow as not worked either.
Here is my program so far:
from tkinter import *
class Application(Frame):
#GUI Application
def __init__(self, master):
#Initialize the Frame
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
#Create new game etc...
#Title
self.title = Label(self,text = "Gnome")
self.title.grid()
#New Game
self.new_game = Button(self,text = "New Game")
self.new_game ["command"] = self.create_new_game
self.new_game.grid()
#Load Game
self.load_game = Button(self,text = "Load Game")
self.load_game ["command"] = self.display_saves
self.load_game.grid()
#Settings
self.settings = Button(self,text = "Settings")
self.settings ["command"] = self.display_settings
self.settings.grid()
#Story
self.story = Button(self,text = "Story")
self.story ["command"] = self.display_story
self.story.grid()
#Credits
self.credits = Button(self,text = "Credits")
self.credits ["command"] = self.display_credits
self.credits.grid()
def hide_widgets(self):
#clear window
new_game.grid_forget()
def create_new_game(self):
#Create new game file
self.hide_widgets
self.instruction = Label(self, text = "Name World:")
self.instruction.grid()
self.world_name = Entry(self)
self.world_name.grid()
def display_saves(self):
#display saved games and allow to run
print("saves")
def display_settings(self):
#display settings and allow to alter
print("settings")
def display_story(self):
#display story
print("story")
def display_credits(self):
#display credits
print("credits")
root = Tk()
root.title("Welcome")
width, height = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry('%dx%d+0+0' % (width,height))
app = Application(root)
root.mainloop()
Thank you in advance.
You can hide the Buttons by calling each one's grid_forget() method.
To make that easier you might want to create a self.buttons list or dictionary that contains them all.
Alternatively there's also a grid_slaves() method you might be able to use on the Application instance that will give you a list of all the widgest it manages (or just the ones in a specified row or column). The Buttons should be in one of these lists. I've never used it, so I don't know how easy it would be to identify them in the list returned however.
Ok I got it working now, silly me forgot "()" in self.hide_widgets(), i just never thought about it because there was no error as it was creating a variable instead.
Have you tried replacing new_game.grid_forget() with self.new_game.grid_forget()?
Check this answer out for an explanation as to why self needs to be referenced explicitly. I ran a very simple script to test this behavior and it worked fine.

Tkinter pack_forget() and pack() issue on key-release event

My Requirement
I am developing a program in which I handle space and KeyPress-space Events to handle time based event.
What I am doing is that on the start of program a window gets open to display a time interval, say "5" which depicts 5 Second. Once the time lapses(5Sec) the text "5" gets disappear.
After that the user is required to press space key, when user press space key the same number will display until user don't release space key.
I have given 2 second gap to show next interval(once user releases the space) and in that interval the number should not appear which was appearing in previous set time interval space Event.
Problem Statement:
The issue that I am facing is that I have managed to display number(text) on key press but when I key-release, time interval is not disappearing for 2 second.
Here is my Code:
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
Here you go..Please let me know if you have any further query -
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
self.parent.update()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
You appear to have found a "crevice" in the way Python interacts with the Tk main loop. At least on my system (Python 2.7, Fedora 22, x86_64) none of the operations in showLabel() have any effect on the screen until the function (including time.sleep(2)) completes.
If you modify the code as follows for troubleshooting, I think you can see what I mean.
def hideLabel(self, event):
global loop, times
# self.lbl.config(text=times[loop])
self.lbl.config(bg = "red")
self.lbl.pack()
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
# self.lbl.config(text= '')
self.lbl.config(bg = "green")
The second label now shows up below the first. The first label gets a blue foreground, but not on the keypress, as we would expect. It apparently only gets it when the function returns, after time.sleep().
I don't pretend to completely understand this - someone who has a deep understanding of Python and Tk might explain. One workaround for this particular program is to add update_idletasks(). This (warning: sloppy terminology ahead) forces the Tk main loop to immediately process pending events.
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
On my system, that made the first label turn blue before time.sleep(). I didn't experiment beyond that.
p.s showLabel() hides the label and hideLabel() shows it?
EDIT: The following slight mod of the original posted code works on my system almost as I would expect. I'm not 100% sure what it is intended to do. But the current label does disappear when I tap the space bar, and the next one shows up 2 seconds later, only to be blanked a variable time after that.
The only puzzle I ran into was that my system basically went crazy when I tried to hold down the space bar for more than 2 seconds. It turned out that the key auto-repeat was kicking in on my system, giving me dozens of keystrokes and causing dozens of subscript out-of-range errors. I gave up at that point so I still don't know how the program behaves if given a single space-down followed 3 seconds later by a single space-up.
from Tkinter import Label, Tk
import time
times = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
master.bind("<h>", self.showLabel)
print('called', loop)
self.lbl.pack()
for child in master.children: print child
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
self.lbl.config(bg = "green")
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
If the pack_forget has no effect on your system even with the update_idletasks(), I'd have to guess that the Tk main loop is more platform dependent than is widely known.

Python Tkinter frame inside frame limitation or user error?

I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Categories

Resources