How to get Tkinter Python3.x to nest grids? - python

I've been trying for days to figure out how to just put grids in grids of objects. I've got two frames (which I guess are widgets in Tk?) I add one to the other, but the position of its widgets don't appear to respect the parent widgets (and it just overwrites them).
here is my MCVE
import tkinter as tk
class TestDoubleNested(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid()
self.test_label = tk.Label(text="AAAAAAAAA")
self.test_label.grid(row=0, column=0)
class TestNested(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid()
self.test_label = tk.Label(text="top_level_test_label")
self.test_label.grid(row=0, column=0)
self.test_label2 = tk.Label(text="top_level_test_label2")
self.test_label2.grid(row=0, column=1)
# expected to be in 3rd column...
self.test = TestDoubleNested(master)
self.test.grid(row=0, column=4)
test = TestNested()
test.master.title("Test Example")
test.master.maxsize(1000, 400)
test.master.wm_geometry("400x300")
test.mainloop()
No matter how I move around self.grid() invokations or change the column stuff, the display is the same:
As you can see AAAAAAAA displays on top of another widget from the parent, where ideally it should display to the side of everything.

You aren't specifying the master for each widget, so they are all being added to the root window. If you want widgets to be inside frames, you must specify the frame as the first argument when creating the widgets:
class TestDoubleNested(tk.Frame):
def __init__(self, master=None):
...
self.test_label = tk.Label(self, text="AAAAAAAAA")
...
class TestNested(tk.Frame):
def __init__(self, master=None):
...
self.test_label = tk.Label(self, ...)
self.test_label2 = tk.Label(self, ...)
self.test = TestDoubleNested(self)
...

Related

How to use parent class, in Toplevel

I defined a GeneralFrame class that inherits from tk.LabelFrame, which contains other widgets like labels and entries:
class GeneralFrame(tk.LabelFrame):
def __init__(self, master, eCount, lCount):
super().__init__()
self.grid(padx=5, pady=5)
self.entry_widget(eCount)
self.label_widget(lCount)
def entry_widget(self, eCount):
self.e = {}
for i in range(0, eCount):
self.e[i] = tk.Entry(self, width=6)
self.e[i].grid(row=i, column=1, sticky='w')
self.e[i].delete(0, tk.END)
def label_widget(self, lCount):
self.l = {}
for i in range(0, lCount):
self.l[i] = tk.Label(self)
self.l[i].grid(row=i, column=0, sticky='w')
How can I use this class in a TopLevel window?
I've tried like this but it places the frame_save in parent window not TopLevel:
def openNewWindow():
newWindow = Toplevel(window)
newWindow.title('Saved Data')
newWindow.geometry('200x200')
frame_save = GeneralFrame(newWindow, eCount=5, lCount=5)
frame_save.configure(text='Saved data',font=("Helvetica",14,"bold"))
frame_save.grid(row=0, column=0)
labels_text = ['label1','label2','label3','label4','label5']
[frame_save.l[i].configure(text=labels_text[i]) for i in range(0,5)]
And general use in parent window:
window = tk.Tk()
window.geometry("980x500")
window.resizable(1,1)
window.title('Calculator')
class GeneralFrame(tk.LabelFrame):
[code]
frame_1 = GeneralFrame(window, eCount=5, lCount=5)
frame_2 = GeneralFrame(window, eCount=5, lCount=5)
def Frame_1():
[code]
def Frame_2():
[code]
Frame_1()
Frame_2()
window.mainloop()
You need to pass master when calling super().__init__. Otherwise, the actual frame has a default master, and the default master is the root window.
super().__init__(master)
Also, I encourage you to not call self.grid inside the __init__. The way tkinter widgets are designed, it's expected that the code that creates the widgets also calls pack, place, or grid on the widget. Otherwise your class can only ever be used in a parent that uses grid.
It works if I do this:
class GeneralFrame(tk.LabelFrame):
def __init__(self, master, eCount, lCount):
#super().__init__()
tk.LabelFrame.__init__(self,master)

Python - accessing a local variable from another class

I have tried to condense the code down as much as possible to make it clear what I am asking...
I have a variable called chosen_name, determined in a class called booking_frame, that I would like to access in the calendar_frame class.
Therefore, it would be obvious for calendar_frame to inherit the attributes of booking_frame - however, I believe (I'm probably completely wrong lol) that calendar_frame has to inherit the characteristics of Frame so that the whole program functions correctly.
The reason that calendar_frame is a completely separate class is so that it can appear as a different frame.
Extremely grateful for any help given :)
# import tkinter modules
from tkinter import *
from tkinter import ttk
import tkinter.font as tkFont
from PIL import ImageTk, Image
from tkcalendar import *
# define self
class tkinterApp(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
# creating a container
container = Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
# initialising frames to an empty array
self.frames = {}
menu_bar = Menu(container)
main_menu = Menu(menu_bar)
menu_bar.add_cascade(label="Main Menu", menu=main_menu)
main_menu.add_command(label="Welcome page", command=lambda: self.show_frame(welcome_frame))
main_menu.add_command(label="Book a vehicle", command=lambda: self.show_frame(booking_frame))
main_menu.add_command(label="Register as new user", command=lambda: self.show_frame(register_frame))
Tk.config(self, menu=menu_bar)
for F in (welcome_frame, booking_frame, register_frame, calendar_frame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(welcome_frame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class welcome_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
welcome = Label(self, text="Hello, please use the menu above to navigate the interface")
welcome.grid(row=0, column=4, padx=10, pady=10)
class register_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
register_label = Label(self, text="New user - enter your details below to use the Collyer's car park.")
register_label.grid()
class booking_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
chosen_name = "Steve"
class calendar_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
print(booking_frame.chosen_name)
app = tkinterApp()
app.geometry("1000x800")
app.title("Collyer's Car Park")
app.mainloop()
First you need to change local variable chosen_name to instance variable self.chosen_name inside booking_frame class, otherwise it cannot be accessed outside the class:
class booking_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.chosen_name = "Steve" # changed to instance variable
Then you can access it via controller.frames[booking_frame].chosen_name inside calendar_frame class:
class calendar_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
print(controller.frames[booking_frame].chosen_name)
Inheritance is to model relationships of objects which behave the same ("IS A" relationship). It is not meant to share data between objects.
A possible solution to your problem is to use a third object, that would be shared
between booking_frame and calendar_frame.
This object can be a Python dictionary for example ; you can pass it to all your
"frame" objects, or you can maybe decide to have it global (not very academic, for sure, but quick and dirty):
GLOBAL_STATE = {}
class booking_frame(Frame):
...
GLOBAL_STATE["chosen_name"] = "Steve"
class calendar_frame(Frame):
...
print(GLOBAL_STATE.get("chosen_name"))
I hope you can see now how you can refactor your code to share data between those objects.

I've created a "clear" button in my GUI I'm working on using tkinter. How do I get this button to clear text?

The problem I'm running into is trying to clear the text entered, when the clear button is pressed. I understand there's something called command binding, and passing in a function that could clear the text as a reference to the command. I'm just a bit confused as to go about doing so.
Here's the relevant code:
class Window(Frame):
def __init__(self, master):
super().__init__(master)
master.title('Learning Python')
master.configure(background='black')
master.geometry('900x200')
master.resizable(0, 0)
class Submit(Button):
def __init__(self, master):
super().__init__(master)
self.configure(text='Submit', background='black', foreground='light green', highlightthickness=0,
border=0)
class Clear(Button):
def __init__(self, master):
super().__init__(master)
self.configure(text='Clear', background='black', foreground='light green', highlightthickness=0,
border=0)
class ProgressBar(ttk.Progressbar):
def __init__(self, master):
super().__init__(master)
self.config(orient='horizontal', maximum=1462, mode='determinate')
class PagesRead(Label):
def __init__(self, master):
super().__init__(master)
self.config(text='How many page(s) did you read?', background='black', foreground='light green')
class EntryBox(Entry):
def __init__(self, master):
super().__init__(master)
if __name__ == '__main__':
root = tk.Tk()
app = Window(root)
bar = ProgressBar(root)
bar.pack(fill=tk.BOTH)
pages = PagesRead(root)
pages.pack()
entry = EntryBox(root)
entry.pack()
submit = Submit(root)
submit.pack()
clear = Clear(root)
clear.pack()
app.mainloop()
class Clear(Button):
def __init__(self, master):
super().__init__(master)
self.configure(
command=clear_callback
text='Clear',
background='black',
foreground='light green',
highlightthickness=0,
border=0
)
Then you could do something like this:
def clear_callback(self, event=None):
self.entry.delete(0, "end")
You can also bind events, there's some great information on this page
Explicit bindings pass an additional argument in the form of a tkinter event object, so sometimes you might need to use a kwarg, or a lambda expression to pass the extra argument
app.bind(
"<Return>",
lambda: clear_callback(event)
)
Hope this was helpful. Cheers.
Even if you have created a new class for the Entry, it still retains the Entry methods. So you can simply call the delete method:
clear = Clear(root)
clear.config(command=lambda:entry.delete(0, 'end'))
clear.pack()
Depending on your taste you could instead create a clear() method for the EntryBox class and call it without parameters:
class EntryBox(Entry):
def __init__(self, master):
super().__init__(master)
def clear(self):
self.delete(0, 'end')
and then command the Button to this method:
clear = Clear(root)
clear.config(command=entry.clear)
clear.pack()

Tkinter Class structure (class per frame) issue with duplicating widgets

Ive been trying out OOP for use with Tkinter - Im getting there (I think) slowly...
I wanted to build a structure where each frame is handled by its own class, including all of its widgets and functions. Perhaps I am coming from the wrong angle but that is what makes most logical sense to me. - Feel free to tell me if you agree / disagree!
I know why the problem is happening - when im calling each class my __init__ runs everytime and builds the relevant widgets regardless of whether they are already present in the frame. However, the only way I can think of getting round this would be to build each frame in the __init__ of my primary class GUI_Start. - Although this seems like a messy and un-organised soloution to the problem.
Is there a way I can achieve a structure where each class takes care of its own functions and widgets but doesn't build the frame each time?
See below for minimal example of the issue:
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.frames = {}
for window in ['win1', 'win2']:
frame = Frame(self.master, bg=self.win_colour, bd=10, relief=GROOVE)
frame.grid(row=0, column=0, sticky='news')
setattr(self, window, frame)
self.frames[window] = frame
Page_1(self.frames)
def Next_Page(self, frames, controller):
controller(frames)
class Page_1(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win1'].tkraise()
page1_label = Label(self.master['win1'], text='PAGE 1')
page1_label.pack(fill=X)
page1_button = Button(self.master['win1'], text='Visit Page 2...', command=lambda: self.Next_Page(self.master, Page_2))
page1_button.pack(fill=X, side=BOTTOM)
class Page_2(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win2'].tkraise()
page2_label = Label(self.master['win2'], text='PAGE 2')
page2_label.pack(fill=X)
page2_button = Button(self.master['win2'], text='Back to Page 1...', command=lambda: self.Next_Page(self.master, Page_1))
page2_button.pack(fill=X, side=BOTTOM)
root = Tk()
gui = GUI_Start(root)
root.mainloop()
Feel free to critique the structure as I may be trying to approach this from the wrong angle!
Any feedback would be much appreciated!
Luke
The point of using classes is to encapsulate a bunch of behavior as a single unit. An object shouldn't modify anything outside of itself. At least, not by simply creating the object -- you can have methods that can have side effects.
In my opinion, the proper way to create "pages" is to inherit from Frame. All of the widgets that belong to the "page" must have the object itself as its parent. For example:
class PageOne(tk.Frame):
def __init__(self, parent):
# use the __init__ of the superclass to create the actual frame
tk.Frame.__init__(self, parent)
# all other widgets use self (or some descendant of self)
# as their parent
self.label = tk.Label(self, ...)
self.button = tk.Button(self, ...)
...
Once done, you can treat instances of this class as if they were a single widget:
root = tk.Tk()
page1 = PageOne(root)
page1.pack(fill="both", expand=True)
You can also create a base Page class, and have your actual pages inherit from it, if all of your pages have something in common (for example, a header or footer)
class Page(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
<code common to all pages goes here>
class PageOne(Page):
def __init__(self, parent):
# initialize the parent class
Page.__init__(self, parent)
<code unique to page one goes here>
Your use of OOP is not very logical here. Your main program is in the class GUI_start. If your pages inherit from GUI_start, basically you create a whole new program with every page instance you create. You should instead inherit from Frame as Bryan Oakley has pointed our in the comments. Here is a somewhat repaired version of what you have posted. The original one by Bryan is still much better.
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.current_page=0
self.pages = []
for i in range(5):
page = Page(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
for i in range(2):
page = Page_diff(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
self.pages[0].tkraise()
def Next_Page():
next_page_index = self.current_page+1
if next_page_index >= len(self.pages):
next_page_index = 0
print(next_page_index)
self.pages[next_page_index].tkraise()
self.current_page = next_page_index
page1_button = Button(self.master, text='Visit next Page',command = Next_Page)
page1_button.grid(row=1,column=0)
class Page(Frame):
def __init__(self,master,number):
super().__init__(master,bg='#D2B48C')
self.master = master
self.master.tkraise()
page1_label = Label(self, text='PAGE '+str(number))
page1_label.pack(fill=X,expand=True)
class Page_diff(Frame):
def __init__(self,master,number):
super().__init__(master)
self.master = master
self.master.tkraise()
page1_label = Label(self, text='I am different PAGE '+str(number))
page1_label.pack(fill=X)
root = Tk()
gui = GUI_Start(root)
root.mainloop()

Passing class values to another class using Tkinter in Python 3.5

I have the following code (example of my real program):
from tkinter import *
def class1(Frame)
def nv(self,x):
self.vent=Toplevel(self.master)
self.app=class2(self.vent)
self.value=x
def __init__(self,master):
super().__init__(master)
self.master=master
self.frame=Frame(self.master)
self.btn=Button(self, text="example", command=lambda: self.nw(1))
self.btn.pack()
self.pack()
def class2(Frame):
def __init__(self, master):
super().__init__(master)
self.master=master
self.frame=Frame(self.master)
self.value=class1.nw.value.get()
root= Tk()
marco=Frame(root)
marco.pack
lf=class1(marco)
root.mainloop()
this last part is the problem, i cant use .get() properly for this problem, I want to get the value of x when I create the new window.
I use lambda so i can execute the command with parameters.
So the question is, is there a way for me to access the value of x in class 2?
You appear to be quite confused about the usage of classes when using tkinter. super() should not be used with tkinter as explained here and when declaring a class you should use the class keyword, not def. .get() is a method of a tkinter variable class such as tkinter.IntVar, tkinter.StringVar, etc. so is not needed in the example you have given.
What you need is the function in the Frame you are trying to get the value of x from (nv) and then parse that value to the __init__ method in the child Frame.
Here is my solution:
from tkinter import *
class Class1(Frame):
def nv(self,x):
self.vent = Toplevel(self.master)
self.app = Class2(self.vent,x)
def __init__(self,master):
Frame.__init__(self,master)
self.master = master
self.btn = Button(self, text="example", command=lambda: self.nv(1))
self.btn.pack()
self.pack()
class Class2(Frame):
def __init__(self, master, x):
Frame.__init__(self,master)
self.master=master
self.frame=Frame(self.master)
self.x_text = Label(self, text=str(x))
self.x_text.pack()
self.pack()
root = Tk()
marco = Frame(root)
marco.pack()
lf = Class1(marco)
root.mainloop()

Categories