I was playing around with some Tkinter code that I found online:
from Tkinter import *
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.makeWidgets(options)
def handleList(self, event):
index = self.listbox.curselection()
label = self.listbox.get(index)
self.runCommand(label)
def makeWidgets(self, options):
sbar = Scrollbar(self)
list = Listbox(self, relief=SUNKEN)
sbar.config(command=list.yview)
list.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)
list.pack(side=LEFT, expand=YES, fill=BOTH)
pos = 0
for label in options:
list.insert(pos, label)
pos = pos + 1
list.bind('<Double-1>', self.handleList)
self.listbox = list
def runCommand(self, selection):
print 'You selected:', selection
if __name__ == '__main__':
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
ScrolledList(options).mainloop()
My question is: where is the frame created? I don't see anything like:
F1 = Tkinter.Frame()
Say if I wanted to add a label it would be:
label = Tkinter.Label(F1)
I'm looking into being able to add labels, and destroy the whole window when done (most likely add frame.destroy() line after print selection but I don't know what to address in that code).
Frames don't have titles; this works because Tkinter automagically creates a Tk instance the first time any widget is created and Tkinter detects that the root window hasn't been created yet (HT #BryanOakley). If you want to alter the window title, explicitly create a Tk instance and provide it as the parent to the ScrolledList:
if __name__ == '__main__':
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
app = Tk()
app.title('Demo')
ScrolledList(options, parent=app)
app.mainloop()
In many ways this is better, as it's easier to understand what's going on.
my question is were [sic] is the frame created?
A ScrolledList is a Frame, that's the whole point of inheritance (class ScrolledList(Frame): means "define a new class ScrolledList that inherits its behaviour from Frame"). So the frame is created by ScrolledList(...).
As #jonrsharpe points out, a ScrolledList is a Frame because the class is derived from it. The base Frame class is initialized in the first line of the ScrolledList.__init__() method:
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent) # <- calls base class constructor
...
Also, frames don't have a titles, so the closest way to have one is to add it to the window the frame is inside of. This can be done by explicitly creating the root window so you have a reference to it, use that to set its title, and then pass the window explicitly as the ScrolledList's parent:
if __name__ == '__main__':
root = Tk()
root.title('MyTitle')
root.minsize(200, 200) # also added so title is visible
options = map((lambda x: 'Lumberjack-' + str(x)), range(20))
ScrolledList(options, root) # <- Passes root window as the parent
root.mainloop()
Related
from tkinter import *
root = Tk()
def main():
Window1 = Window(root, "hello", "500x500",)
class Window:
def __init__(self, root, title, geometry,):
self.root = root
root.title(title)
root.geometry(geometry)
root.mainloop()
class Button(Window):
def __init__(self, message):
self.message = message
super().__init__(root,)
Button(root, text=message,).pack()
root.mainloop()
Button("HI")
main()
One of the two major issues I see is that your Button class is hiding the one that tkinter defines with the same name that would have been available via the from tkinter import *. The second one is that your Button shouldn't be derived from your Window class because subclassing implies there's is an "is a" relationship between the two classes — which is clearly not the case with them.
Below is an object-oriented way to do things that does work:
import tkinter as tk # PEP 8 recommends avoiding `import *`
def main():
root = tk.Tk()
window1 = Window(root, "hello", "500x500")
Button(root, "HI")
root.mainloop()
class Window:
def __init__(self, root, title, geometry):
self.root = root
root.title(title)
root.geometry(geometry)
class Button:
def __init__(self, parent, message):
self.message = message
tk.Button(parent, text=message).pack()
if __name__ == '__main__':
main()
There are a couple of issues.
Firstly, you're creating a subclass of Window called "Button". When you subclass something, it means that it will be of a similar type as it's parent (Window != Button). But more than that, when you define Button, you're actually hiding tkinter's button!
Second, you need to think about the event loop. When working with GUIs, you want to set everything up (where is the button, where are form elements, etc.) before running the mainloop (where possible). You're calling the root.mainloop function in each element, when you should only really call it once (and probably in your main() method).
So how to actually do it? You're code may look something like this
from tkinter import *
class Window:
def __init__(self, root, title, geometry, ):
self.root = root
root.title(title)
root.geometry(geometry)
def add_button(self, label):
btn = Button(self.root, text=label)
btn.pack(side='top')
def main():
root = Tk()
Window1 = Window(root, "hello", "500x500", )
Window1.add_button("Hi!")
root.mainloop()
if __name__ == "__main__":
main()
Here, the window has a method called "add_button", where you can add whatever button you want. Note that it is just creating a new Button object (the parent is "root") and then is "packing" it (feel free to read more about tk's layouts), which puts it in its place
I've also cleaned up the main function and called it under the classic 'if name == "main":' line.
import tkinter as tk
def main():
root = tk.Tk()
window1 = Window(root, "hello", "500x500")
Button(root, "Click me", 2, 2, 10, 5)
root.mainloop()
class Window:
def __init__(self, root, title, geometry):
self.root = root
root.title(title)
root.geometry(geometry)
class Button:
def __init__(self, parent, message, row, column, width, height,):
self.message = message
self.row = row
self.column = column
self.width = width
self.height = height
tk.Button(parent, text=message, height=height, width=width).grid(column=column, row=row)
if __name__ == '__main__':
main()
the grid function isnt working now lol
I am trying to delete a widget that has the name of a string but I cant find how to do it. This is what I have done so far but I cant get my head around that. I want to be able to select the name of the widget that I want to get deleted. any help would be useful
and this is the code that i have made so far
lasthover = "button1"
def dlt():
for widget in frm.winfo_children():
if widget == lasthover:
widget.destroy()
You can set the widget name using option name when creating the widget and then destroy it using the given name.
Below is an example:
import tkinter as tk
root = tk.Tk()
frm = tk.Frame(root)
frm.pack()
# create buttons with name button1, button2, etc
for i in range(1, 10):
name = f"button{i}"
tk.Button(frm, text=name, name=name).pack(side="left")
lasthover = "button1"
def dlt():
for widget in frm.winfo_children():
if widget._name == lasthover:
widget.destroy()
tk.Button(root, text="Destroy Button", command=dlt).pack()
root.mainloop()
ok, so here is my solution (using classes), this is how I did it in one of my projects (similarly), this also may raise a lot of questions so feel free to ask:
from tkinter import Tk, Button
root = Tk()
removable_widget_dict = {}
class RemovableWidget(Button):
def __init__(self, title, parent, key):
Button.__init__(self, parent)
self.parent = parent
self.title = title
self.key = key
self.button = Button(self.parent, text=self.title)
self.button.pack()
def delete(self):
global removable_widget_dict
self.button.destroy()
del removable_widget_dict[self.key]
print(removable_widget_dict)
for i in range(10):
key = f'item{i}'
removable_widget_dict[key] = RemovableWidget(f'Button {i}', root, key)
print(removable_widget_dict)
for key in removable_widget_dict.keys():
if key == 'item5':
removable_widget_dict[key].delete()
break
root.mainloop()
in simple terms: there is the class that will be the removable widgets, each time an instance of that class is created in the for loop, it is saved in dictionary. when deleting You match Your criteria with dictionary key and execute a function to delete the widget and remove it from the dictionary
I have a custom Frame with an update method that I want to call every time the user switches tab in a notebook using python2.7 ttk.
My first thought was to intercept some "tab switch event", get the frame from the event and call the update method. However, googling this yields no good results. I can't seem to catch the event, nor get the frame from a "tab click event" in a good way. My current solution below works okay but I'm looking for a better alternative. (Also with my real update method I get some nasty alloc errors).
import ttk
import Tkinter as tk
class MyFrame(ttk.Frame):
def __init__(self, master, name):
ttk.Frame.__init__(self, master)
self.name = name
self.pack()
def update(self):
# Update the contents of the frame...
print "Updating %s" % self.name
class App():
def __init__(self):
root = tk.Tk()
nb = ttk.Notebook(root)
f1_name = "Tab1"
f2_name = "Tab2"
f1 = MyFrame(nb, f1_name)
f2 = MyFrame(nb, f2_name)
nb.add(f1, text=f1_name)
nb.add(f2, text=f2_name)
def tab_switch(event):
if event.widget.identify(event.x, event.y) == "label":
index = event.widget.index("#%d,%d" % (event.x, event.y))
title = event.widget.tab(index, "text")
if title == f1_name:
f1.update()
elif title == f2_name:
f2.update()
# and so on...
nb.bind("<Button-1>", tab_switch)
nb.pack(fill="both", expand=True)
f1.update() # Initial update of first displayed tab
root.geometry("200x200")
root.mainloop()
App()
As you can imagine this becomes very non-DRY as the amount of tabs increases...
You can bind to the <Visibility> or <Map> event of the frame. For example:
class MyFrame(ttk.Frame):
def __init__(self, master, name):
ttk.Frame.__init__(self, master)
self.name = name
self.pack()
self.bind("<Visibility>", self.on_visibility)
def on_visibility(self, event):
self.update()
Instead of
def tab_switch():
# see question...
nb.bind("<Button-1>", tab_switch)
you can do the following trick
b.bind("<<NotebookTabChanged>>",
lambda event: event.widget.winfo_children()[event.widget.index("current")].update())
This also removes the need to call
f1.update() # Initial update of first displayed tab
However, the solution proposed by #BryanOakley might work better when you have different types of frames where you cannot be sure if an .update() method exists.
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()
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]