Where is my python tkinter grid from this inheritance? - python-3.x

this is a View and Controller part of a program that I am intending writing. My question is why I can't see my grid. My suspicion is that I am not inheriting correctly.
I think the problem is happening here:
"self.frame=Small_Frame(self)"
This is what I understand from my code. class Controller is inheriting from tk. class View is inheriting from tk.Frame. Up to here everything works.
class Small_Frame is my customer widget. The grid is just 12 instances of class Small_Frame using grid() method. I don't know why is it not showing up. Please help me understand. thank you.
import tkinter as tk
class View(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg= "yellow", bd =2, relief = tk.RIDGE)
self.parent = parent
self.controller = controller
self.pack(fill=tk.BOTH, expand=1)
for r in range(3):
self.rowconfigure(r, weight=1)
for c in range(4):
self.columnconfigure(c, weight=1)
self.frame=Small_Frame(self)
self.frame.grid(row = r, column = c, padx=1, pady = 1, sticky=
(tk.N, tk.S, tk.W, tk.E))
class Small_Frame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, borderwidth=1, relief="groove")
self.parent = parent
self.pack(fill=tk.BOTH, expand=1)
class Controller():
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root, self)
self.root.title("notbook my own try")
self.root.geometry("1200x650")
self.root.config(bg="LightBlue4")
self.root.mainloop()
if __name__ == "__main__":
c = Controller()

The problem is that you are mixing pack and grid with widgets that share a common parent.
First, you're creating a View object as a child of the root window, and you're calling pack to add it to the root window.
Next, you are creating a series of Small_Frame instances, but you are neglecting to pass the parent to the __init__ of the superclass so these instances become a child of the root window. The instance calls pack on itself, and then you call grid on the instance. Calling grid on the instance causes tkinter to get into an infinite loop as both grid and pack try to resize the parent in different ways. Each one triggers a redraw by the other one.
There are two things you need to do. First, remove self.pack(fill=tk.BOTH, expand=1) from the __init__ of Small_Frame. It's a bad practice to have a class call pack or grid on itself. The code that creates a widget should be responsible for adding it to the screen.
Second, you need to pass parent to __init__ method of the superclass in Small_Frame so that Small_Frame is a child of the correct parent. Your __init__ thus should look like this:
class Small_Frame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, borderwidth=1, relief="groove")
self.parent = parent

Related

How can I set the default container in a decorator class for tkinter.Frame?

I would like to create a contractible panel in a GUI, using the Python package tkinter.
My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).
Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).
Minimal example: (omitting the toggling stuff and any "formatting"):
(Here's a published (runnable) Repl project)
import tkinter
class ContractiblePanel(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self._panel = tkinter.Frame(self)
self._toggle = tkinter.Button(self, text='<', command=self._toggle_panel)
self.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
def _toggle_panel(self):
# ...
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('128x128')
contractible_panel = ContractiblePanel(root)
Forwarding configuration calls is just overriding the config method I guess?
class ContractiblePanel(tkinter.Frame):
# ...
def config(self, **kwargs):
self._panel.config(**kwargs)
# ...
contractible_panel.config(background='blue')
But I would like to be able to add a child component into the nested panel frame by
label_in_panel = tkinter.Label(contractible_panel, text='yadayada')
How do I get the ContractiblePanel object to act like a proxy to its member _panel, when adding child components?
What other methods/use cases should I consider? I am quite new to tkinter and thus expect the current implementation to break some common practices when developing tkinter GUIs.
This is an interesting question. Unfortunately, tkinter really isn't designed to support what you want. I think it would be less complicated to simply expose the inner frame and add widgets to it.
That being said, I'll present one possible solution. It's not implemented as a python decorator, but rather a custom class.
The difficulty is that you want the instance of the custom class to represent the outer frame in one context (for example, when packing it in your UI) and the inner frame in another context (when adding child widgets to it)
The following solution solves this by making the instance be the inner frame, and then overriding pack,place, and grid so that they operates on the outer frame. This works fine, with an important exception: you cannot use this class directly inside a notebook or embedded in a text widget or canvas.
I've used colors and borders so it's easy to see the individual components, but you can remove the colors in production code, obviously. Also, I used a label instead of a button since I created the screenshot on OSX where the background color of a button can't be changed.
import tkinter as tk
class ContractiblePanel(tk.Frame):
def __init__(self, parent, **kwargs):
self._frame = tk.Frame(parent, **kwargs)
super().__init__(self._frame, bd=2, relief="solid", bg="#EFE4B0")
self._button = tk.Label(
self._frame, text="<", bg="#00A2E8", bd=2,
relief="solid", font=("Helvetica", 20), width=4
)
self._frame.grid_rowconfigure(0, weight=1)
self._frame.grid_columnconfigure(0, weight=1)
self._button.grid(row=0, column=1, sticky="ns", padx=4, pady=4)
super().grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
self._button.bind("<1>", lambda event: self.toggle())
def collapse(self):
super().grid_remove()
self._button.configure(text=">")
def expand(self):
super().grid()
self._button.configure(text="<")
def toggle(self):
self.collapse() if self.winfo_viewable() else self.expand()
def pack(self, **kwargs):
# override to call pack in the private frame
self._frame.pack(**kwargs)
def grid(self, **kwargs):
# override to call grid in the private frame
self._frame.grid(**kwargs)
def place(self, **kwargs):
# override to call place in the private frame
self._frame.place(**kwargs)
root = tk.Tk()
root.geometry("400x300")
cp = ContractiblePanel(root, bg="yellow", bd=2, relief="raised")
cp.pack(side="left", fill="y", padx=10, pady=10)
label = tk.Label(cp, text="Child component", background="#22B14C", height=3, bd=2, relief="solid")
label.pack(side="top", expand=True, padx=20, pady=20)
root.mainloop()
First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.
The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.
I don't recommend using this code by the way.
import tkinter as tk
NONE = object()
#use an object here that there will no mistake
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
def __getattr__(self, name):
#when wrapper class has no attr name
#delegate the lookup chain to self.frame
inreturn = getattr(self._wrapped_frame, name, NONE)
if inreturn is NONE:
super().__getattribute__(name)
return inreturn
root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()

How does tkinter grid placement work when using child classes in Python?

To preface this, I have this working, I was just hoping somebody could explain why the code is behaving the way it is. I'm not understanding the grid system inside of classes very well apparently.
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self.rowconfigure(0, uniform=True, weight=1)
self.rowconfigure(1, uniform=True, weight=1)
self.rowconfigure(2, uniform=True, weight=1)
self.header_frame = HeaderFrame(self)
self.header_frame.grid(column=0,row=0)
self.login_frame = LoginFrame(self)
self.login_frame.grid(column=0,row=1)
self.button_frame = ButtonFrame(self)
self.button_frame.grid(column=0,row=2)
class HeaderFrame(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.columnconfigure(0, uniform=True, weight=1)
self.canvas = tk.Canvas(self, bg='black')
self.canvas.grid(column=0,row=0)
class loginFrame(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent=parent
self.columnconfigure(0, uniform=True, weight=1)
self.entryBox = ttk.Entry(self, width=33)
self.entryBox.grid(column0,row1)
class ButtonFrame(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent=parent
self.columnconfigure(0, uniform=True, weight=1)
self.btn = ttk.Button(self, text='test')
self.btn.grid(column=0,row=2)
if __name__ == '__main__':
App().mainloop()
Now my question comes down to the grid placement. When I call each class to place the items, I specify different grid locations. header_frame.grid(column=0, row=0), login_frame(column=0,row=1), etc. However, the initial App class having a grid placement does not affect the layout of the gui whatsoever. I can place them all on row 0 and they will all still show up on separate rows until I change the grid placement inside of the individual class. I thought my grid inside of the class was being placed inside of cells of the App class, what am I missing here?
Thanks in advance for your help.
Consider this code:
class HeaderFrame(ttk.Frame):
def __init__(self, parent):
super().__init__()
super().__init__() is what actually creates the widget. You aren't passing parent to super().__init__()) so the widget will be created as a child of the root window. You need to pass parent like in the following example.
super().__init__(parent)
You need to make a similar change for all of your classes.

Tkinter Nested Frame using Classes

I am trying to build a GUI with the main Frame having nested frame containing header label (created with different class).
In below snippet, I am expecting frame created by FrameHeader class to be inside MainWindow.container Frame (while initialization of MainWindow.container attribute, container attribute is passed as parent).
Still, when the below code is run, FrameHeader frame is at the bottom after container frame, instead of being inside the container frame.
I am new to tkinter, can someone help me out here, what am I missing while going between different classes?
from tkinter import *
class FrameHeader(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, bg='red',relief=RAISED, borderwidth=2)
self.pack(fill=BOTH, side=TOP)
lblTitle = Label(self, text='Welcome to the Program!')
lblTitle.pack(fill=BOTH)
class MainWindow(Tk):
def __init__(self,*args):
Tk.__init__(self,*args)
self.geometry('400x300')
# Main Container
container=Frame(self, bg='black')
container.pack(side=TOP, expand=TRUE, fill=BOTH)
frameHeader=FrameHeader(container, self)
if __name__=='__main__':
mainWindow=MainWindow()
mainWindow.mainloop()
You are neglecting to pass parent to Frame.__init, so the parent of FrameHeader defaults to the root window rather than the container.
The code needs to be this:
Frame.__init__(self, parent, bg='red',relief=RAISED, borderwidth=2)

inherit parent window and add an additional button in tkinter?

i'm trying to create two separate windows, one of which should inherit the others interface, and grid some additional buttons. How can I achieve this?
Below is an example piece of code:
f = ("Helvetica", 18)
bg = 'white'
g = '1400x800'
class MainUser(Frame):
def __init__(self, master):
Frame.__init__(self, master)
Frame.configure(self, background='white')
self.logo = PhotoImage(file="logo.gif")
Label(self, image=self.logo).pack()
Button(self, text='test', bg=bg, font=f).pack()
class MainAdmin(MainUser):
pass # What now?
You simply need to create a proper __init__ that calls the same function in the superclass. Then, add widgets like you would have done in the superclass.
Example:
class MainAdmin(MainUser):
def __init__(self, master):
super().__init__(master)
another_label = Label(self, text="Hello from MainAdmin")
another_label.pack(side="top", fill="x")

How can I configure a widget that is in a different class?

I need the buttons within LeftFrame to change its appearance when clicked. In the class AccOne, I tried to do left_frame.acc1.config(releif='SUNKEN'), but I get NameError: name 'left_frame' not defined. I tried making left_frame global, but no luck.
Here's the script.
class MainApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack()
container.rowconfigure(4, weight=1)
container.columnconfigure(2, weight=1)
right_frame = RightFrame(container, self)
left_frame = LeftFrame(container, right_frame)
left_frame.pack(side=LEFT)
right_frame.pack()
class RightFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent, *args, **kwargs)
self.frames = {}
for F in (Welcome, AccOne, AccTwo, AccThree, AccFour, AccFive):
frame = F(self, self)
self.frames[F] = frame
self.show_frame(Welcome)
def show_frame(self, cont):
frame = self.frames[cont]
frame.grid(row=0, column=0)
frame.tkraise()
class LeftFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent)
acc1 = Button(self, text="Account 1", width=12, height=3, command=lambda: controller.show_frame(AccOne))
acc1.pack()
I figured it would make sense to configure the button under def show_frame(self,cont): but I have no idea where to start since that method isn't under LeftFrame.
When creating tkinter windows with classes, try and think about creating a 'widget tree', this being a path through which you can access all of your widgets. In this simple example, MainWindow and SubWindow can access all of eachother's widgets:
class MainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# widget
self.lbl = tk.Label(self, text='Title')
self.lbl.pack()
# create child window, as class attribute so it can access all
# of the child's widgets
self.child = SubWindow(self)
self.child.pack()
# access child's widgets
self.child.btn.config(bg='red')
class SubWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# can use this attribute to move 'up the tree' and access
# all of mainwindow's widgets
self.parent = parent
# widget
self.btn = tk.Button(self, text='Press')
self.btn.pack()
# access parent's widgets
self.parent.lbl.config(text='Changed')
Things to change in your code
Firstly, every time you create a widget that you might want to access later, assign it to a class variable. For example (this is part of the cause of your problem):
self.left_frame
self.acc1
not
left_frame
acc1
Secondly, make proper use of your parent and controller arguments. You're doing these, but you never use them or assign them to an atribute, so they may as well not be there. Assign them to a self.parent or self.controller attribute, so if you need to access them in a method later, you can.
I don't know exactly what you're trying to do and I can't see your AccOne class, but you should be able to find a way to access that button by making these changes.
Good luck!

Resources