Python class Inheriting from tk.Frame with a list attribute - python

I'm in the process of teaching myself tkinter (objected-oriented style).
I want my class to produce a simple window with a single label in it. And I want the text for the label to come from an attribute, the first element of a list. This is a mockup made in Paint of what I would expect the window to look like:
But when I run the code below I get an error message (TypeError: create() argument 1 must be str or None, not list). I can't figure out how to pass 'my_list' so to create an instance of my class.
Thanks for any help!
import tkinter as tk
my_list = [222, 333, 444, 555]
class MyWindow(tk.Frame):
def __init__(self, parent, lst):
self.lst = lst
tk.Frame.__init__(self, parent)
self.window_height = 200
self.window_width = self.window_height * 2
canvas = tk.Canvas(self, width=self.window_width, height=self.window_height)
canvas.pack()
label = tk.Label(self, text=str(self.lst[0]))
label.pack()
root= tk.Tk(my_list)
MyWindow(root).pack()
root.mainloop()

First, you cannot pass my_list to tk.Tk().
Second, your MyWindow requires two parameters: parent and list (though you should name that second parameter something different).
Third, calling a geometry manager (pack, grid, or place) inline with the creation of the widget is a bad practice that should be avoided.
Here is how the final block of code should look like:
root= tk.Tk()
mywindow = MyWindow(root, my_list)
mywindow.pack()
This will end up with a window that has the label at the bottom. If you want it at the top, reverse the order that you pack the items in the frame and/or explicitly state where they should go:
label.pack(side="top", fill="x")
canvas.pack(side="bottom", fill="both", expand=True)

Related

String Variable not setting initial value

class Lay():
def __init__(self):
root=Tk()
root.configure(background="black")
var=StringVar()
var.set("OVERVIEW")
Label(root,textvariable=var).grid(row=1,column=1,sticky=W+E+N+S)
Entry(root, textvariable = var).place(rely=1.0,relx=1.0,x=0,y=0,anchor=SE)
root.mainloop()
Hello, when i run this the initial value of the string variable does not appear, but when i type into the entry box, the text i type appears in the label. I'm not quite sure why this occurs, but i get an empty label to begin with, with the entry box. Thank you for any help.
Although, I couldn't reproduce the problem, I refactored your code to initialize tkinter widgets through a class(inspired by the snippet in the docs) and also increased the window size so that the widgets are clearly viewed. If there is anything else in your code that is calling multiple windows as #jasonharper suggested, you should share that.
import tkinter as tk
class Lay(tk.Tk):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.var=tk.StringVar()
self.var.set("OVERVIEW")
self.Widgets()
def Widgets(self):
self.displaylbl = tk.Label(self,textvariable=self.var)
self.displaylbl.grid(row=2,column=1,sticky=tk.W+tk.E+tk.N+tk.S)
self.entry = tk.Entry(self, textvariable = self.var)
self.entry.place(rely=1.0,relx=1.0,x=0,y=0,anchor=tk.SE)
app = Lay()
app.geometry("200x200")
app.mainloop()
Output:

How to select all instances of a widget in tkinter?

So does anyone know of a way to 'get' all Labels, for example from a program or window in Tk. i.e like root.winfo.children but only for a type of widget.
Also I know you can use lists, but i want to know if there is a better way?
You could use the universal winfo_toplevel() method to get the top-level window containing any widget and a list comprehension to filter the class of the items that winfo_children() returns so it only contains widgets of the desired type. Here's an example of doing that:
from pprint import pprint
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.quitButton = tk.Button(self, text='Test', command=self.find_buttons)
self.quitButton.grid()
nested_frame = tk.Frame(self) # Nest some widgets an extra level for testing.
self.quitButton = tk.Button(nested_frame, text='Quit', command=self.quit)
self.quitButton.grid()
nested_frame.grid()
def find_buttons(self):
WIDGET_CLASSNAME = 'Button'
toplevel = self.winfo_toplevel() # Get top-level window containing self.
# Use a list comprehension to filter result.
selection = [child for child in get_all_children(toplevel)
if child.winfo_class() == WIDGET_CLASSNAME]
pprint(selection)
def get_all_children(widget):
""" Return a list of all the children, if any, of a given widget. """
result = [] # Initialize.
return _all_children(widget.winfo_children(), result)
def _all_children(children, result):
""" Recursively append all children of a list of widgets to result. """
for child in children:
result.append(child)
subchildren = child.winfo_children()
if subchildren:
_all_children(subchildren, result)
return result
app = Application()
app.master.title('Sample application')
app.mainloop()

Inserting new rows in Tkinter grid

Let's say I have a Tkinter app with 2 rows displaying 2 widgets:
from tkinter import *
from tkinter.ttk import *
root = Tk()
Label(root, text="Some Data").grid(row=0)
Label(root, text="Some Data").grid(row=1)
root.mainloop()
Now this will display two widgets on row0 and row1.
Now if I want to insert another (one or more) widget between these two rows at a later stage (say as a response to a button click event), what would be the best way to do that.
Current output:
Some Data
Some Data
Expected output:
Some Data
<<New data>>
Some Data
<<New Data>> will be inserted at a later stage as a response to a button click.
<<New Data>> may be one or more rows.
I do have a simple solution for you.
If you are expecting to insert a widget later and you know you will be then you can simply place your 2nd label on grid row 2 and then place your new widget on grid row 1 later. If you need to have more than one row you could place your 2nd label even further down the line.
from tkinter import *
from tkinter.ttk import *
root = Tk()
def add_new_data():
Label(root, text="<<New Data>>").grid(row=1)
Label(root, text="Some Data").grid(row=0)
Label(root, text="Some Data").grid(row=2)
Button(root, text="Add New Data", command=add_new_data).grid(row=3)
root.mainloop()
Results:
The reason this works is because Tkinter's geometry manager will collapse rows and columns to nothing if there is nothing in them so you can use this behavior to your advantage when working with something like this.
Now if you wanted something that could work with any number of label then we can use a list to help us accomplish that.
My next example with be written in class and will show the use of a list to do what we want.
We can store widgets in a list and because we can do this we are also able to decide where in that list to put stuff and use the lists index to our advantage when setting up the grid.
First thing is to create our Some Data labels and append them to a list. The next is the add the button to that list at the end of the list. This button we will used to call a class method that will insert() a new label into our list.
Next that same method will forget the grid for all widgets inside of our label frame and then it will perform a for loop over the list and re add all the old widgets and the new one in the correct order.
Take a look at the below example.
import tkinter as tk
class App(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.label_frame = tk.Frame(self.master)
self.label_frame.grid()
self.label_list = []
for i in range(2):
self.label_list.append(tk.Label(self.label_frame, text="Some Data"))
self.label_list[i].grid(row=i)
self.label_list.append(tk.Button(self.label_frame, text="Add new data", command=self.add_new_data))
self.label_list[2].grid(row=2)
def add_new_data(self):
self.label_list.insert(1, tk.Label(self.label_frame, text="<<New Data>>"))
for widget in self.label_frame.children.values():
widget.grid_forget()
for ndex, i in enumerate(self.label_list):
i.grid(row=ndex)
if __name__ == "__main__":
root = tk.Tk()
my_app = App(root)
root.mainloop()
Results:
We can add as many new labels as we like.

Is there a way to clear all widgets from a tkinter window in one go without referencing them all directly?

I'm trying to clear a tkinter window completely. However, I need a way to clear every widget on the window all at once without using pack.forget().
You could use a simple recursive loop to list all the children wigets of your main window :
def all_children (window) :
_list = window.winfo_children()
for item in _list :
if item.winfo_children() :
_list.extend(item.winfo_children())
return _list
Then just use this list :
widget_list = all_children(window)
for item in widget_list:
item.pack_forget()
What you need to do is set a frame to your main window and place everything that is to be cleared out at some point inside that frame. Then you simply do frame_name.destroy()
The following example has a button that creates a frame with several label widgets and a button.
The button calls a method that will destroy the frame and everything in it.
Then you can create the frame again with the first button.
Let me know if you have any question:
import tkinter as tk
class ExampleApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.some_frame = None
tk.Button(self.master, text="Create new frame with widgets!", command = self.create_stuff).pack()
def create_stuff(self):
if self.some_frame == None:
self.some_frame = tk.Frame(self.master)
self.some_frame.pack()
for i in range(5):
tk.Label(self.some_frame, text = "This is label {}!".format(i+1)).pack()
tk.Button(self.some_frame, text="Destroy all widgets in this frame!",
command= self.destroy_some_frame).pack()
def destroy_some_frame(self):
self.some_frame.destroy()
self.some_frame = None
root = tk.Tk()
my_example = ExampleApp(root)
root.mainloop()
You can use destroy method for each widget for example if it's a button you write btn1.destroy() and do it for all widgets. The forget method isn't recommended for it only remove the widgets from appearance.

How to automatically insert several tkinter items in Tk() window using classes

I apologize in advance if this is a stupid simple question, but i am really bad att python classes and can't seem to get it to work!
Here is my code:
from tkinter import *
a = Tk()
class toolsGUI():
def __init__(self, rootWin):
pass
def frame(self):
frame = Frame(rootWin)
frame.configure(bg = 'red')
frame.grid()
def button(self, binding, text):
btn = Button(rootWin, text=text)
btn.configure(bg = 'orange', fg = 'black')
btn.bind('<'+binding+'>')
btn.grid(row=1, sticky = N+S+E)
I simply want the button() or frame() to understand that rootWin is the same as in __init__, in this case rootWin should be variable a, thus placing the button in the Tk() window. After looking around, I understand that this is not the way to do it. Do anyone have another suggestion that might work?
You're pretty close. You are passing a to the toolsGUI initializer which is the right first step. You simply need to save this as an instance variable, then use the variable whenever you need to reference the root window:
def __init__(self, rootWin):
...
self.rootWin = rootWin
...
def frame(self):
frame = Frame(self.rootWin)
...
An alternative is to have toolsGUI inherit from Frame, in which case you can put all of the widgets in the frame instead of the root window. You then need the extra step of putting this frame inside the root window.
class toolsGUI(Frame):
def __init__(self, rootWin):
Frame.__init__(self, rootWin)
def frame(self):
frame = Frame(self)
...
a = Tk()
t = toolsGUI(a)
t.pack(fill="both", expand=True)
a.mainloop()
As a final bit of advice: don't user variables that are the same name as methods if you can avoid it. "frame" is a poor choice of function names. Instead, call it "create_frame" or something, otherwise it could be confused with class Frame and the local variable frame

Categories

Resources