How To Make A Buttons Appear Progressively In tkinter - python

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.

Related

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.

Event when entry's xview changes

I am creating a simple program using Tkinter. I want a function to be called every time xview property of entry changes. But there doesn't seem to be an event like this, at least not one that I can find.
The <Configure> event fires only on resize, which I already handled, but it doesn't fire when actual value I'm tracking changes in a different way, such as the user dragging his mouse to see the end of the entry.
Here is the code:
import Tkinter as Tk
import tkFileDialog
root = Tk.Tk()
class RepositoryFolderFrame(Tk.Frame):
def __init__(self, root):
Tk.Frame.__init__(self, root)
self.build_gui()
self.set_entry_text("Searching...")
#root.after(0, self.find_repo)
self.prev_entry_index = len(self.entry.get())
root.bind("<Configure>", self.on_entry_resize)
#self.entry.bind(???, self.on_entry_change)
#self.entry.bind("<Configure>", self.on_entry_change)
def on_entry_resize(self, event):
cur_entry_index = self.entry.xview()[1]
if cur_entry_index != self.prev_entry_index:
self.entry.xview(self.prev_entry_index)
def on_entry_change(self, event):
# This should be called when xview changes...
cur_entry_index = self.entry.xview()[1]
self.prev_entry_index = cur_entry_index
def set_entry_text(self, text):
self.entry_text.set(text)
self.entry.xview("end")
def build_gui(self):
label = Tk.Label(self, text = "Repository folder:")
label.pack(side = Tk.LEFT)
self.label = label
entry_text = Tk.StringVar()
self.entry_text = entry_text
entry = Tk.Entry(self, width = 50, textvariable = entry_text)
entry.configure(state = 'readonly')
entry.pack(side = Tk.LEFT, fill = Tk.X, expand = 1)
self.entry = entry
button = Tk.Button(self, text = "Browse...")
button.pack(side = Tk.LEFT)
self.button = button
repo_frame = RepositoryFolderFrame(root)
repo_frame.pack(fill = Tk.X, expand = 1)
root.mainloop()
There is no mechanism for getting notified when the xview changes. There are ways to do it by modifying the underlying tcl code, but it's much more difficult than it's worth.
A simple solution is to write a function that polls the xview every few hundred milliseconds. It can keep track of the most recent xview, compare it to the current, and if it has changed it can fire off a custom event (eg: <<XviewChanged>>) which you can bind to.
It would look something like this:
class RepositoryFolderFrame(Tk.Frame):
def __init__(self, root):
...
self.entry.bind("<<XviewChanged>>", self.on_entry_change)
# keep a cache of previous xviews. A dictionary is
# used in case you want to do this for more than
self._xview = {}
self.watch_xview(self.entry)
def watch_xview(self, widget):
xview = widget.xview()
prev_xview = self._xview.get(widget, "")
self._xview[widget] = xview
if xview != prev_xview:
widget.event_generate("<<XviewChanged>>")
widget.after(100, self.watch_xview, widget)
You'll need to modify that for the edge case that the entry widget is destroyed, though you can handle that with a simple try around the code. This should be suitably performant, though you might need to verify that if you have literally hundreds of entry widgets.

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

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

Is it possible in tkinter to pull up different screens in the same location

i am going to create an tkinter gui app, and i know how i want it to look. but after playing around with tkinter, i found no way to toggle between screens when you press buttons down at the bottom. i know it does nothing but below is the simple layout i want to have, and switch between "myframe1" and "myframe2" kind of like the Apple App Store layout. is this possible?
from tkinter import *
tk = Tk()
tk.geometry("300x300")
myframe1 = Frame(tk,background="green",width=300,height=275)
myframe1.pack()
myframe2 = Frame(tk,background="cyan",width=300,height=275)
myframe2.pack()
btnframe = Frame(tk)
btn1 = Button(btnframe,text="screen1",width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",width=9)
btn2.pack(side=LEFT)
btn3 = Button(btnframe,text="screen3",width=9)
btn3.pack(side=LEFT)
btn4 = Button(btnframe,text="screen4",width=9)
btn4.pack(side=LEFT)
myframe1.pack()
btnframe.pack()
tk.mainloop()
something for you to get started with:
def toggle(fshow,fhide):
fhide.pack_forget()
fshow.pack()
btn1 = Button(btnframe,text="screen1", command=lambda:toggle(myframe1,myframe2),width=9)
btn1.pack(side=LEFT)
btn2 = Button(btnframe,text="screen2",command=lambda:toggle(myframe2,myframe1),width=9)
btn2.pack(side=LEFT)
Are you looking for something like a tabbed widget? You could use forget and pack as suggested here
Here is a class that I use in my code that works:
class MultiPanel():
"""We want to setup a pseudo tabbed widget with three treeviews. One showing the disk, one the pile and
the third the search results. All three treeviews should be hooked up to exactly the same event handlers
but only one of them should be visible at any time.
Based off http://code.activestate.com/recipes/188537/
"""
def __init__(self, parent):
#This is the frame that we display
self.fr = tki.Frame(parent, bg='black')
self.fr.pack(side='top', expand=True, fill='both')
self.widget_list = []
self.active_widget = None #Is an integer
def __call__(self):
"""This returns a reference to the frame, which can be used as a parent for the widgets you push in."""
return self.fr
def add_widget(self, wd):
if wd not in self.widget_list:
self.widget_list.append(wd)
if self.active_widget is None:
self.set_active_widget(0)
return len(self.widget_list) - 1 #Return the index of this widget
def set_active_widget(self, wdn):
if wdn >= len(self.widget_list) or wdn < 0:
logger.error('Widget index out of range')
return
if self.widget_list[wdn] == self.active_widget: return
if self.active_widget is not None: self.active_widget.forget()
self.widget_list[wdn].pack(fill='both', expand=True)
self.active_widget = self.widget_list[wdn]

Categories

Resources