I'm starting to work with classes with a Tkinter app, but I don't seem to understand how classes work, especially the relationship parent-controller. As you can see in the code down below, I was planning to have an outer class for a whole section, then 4 inner classes for every section within that frame. However, I cannot call those classes from the initial frame. Is there any better way to do this? What is it that I'm doing wrong?
class MainScreenFrameCenter(tk.Frame):
def __init__(self, parent, controller,*args,**kwargs):
tk.Frame.__init__(self,parent, bg="white",height=680, width=640,highlightbackground="black", highlightthickness=1)
self.controller = controller
self.pack(side="top", fill="both", expand=True)
self.widgets_nw = MainScreenFrameCenterNW(parent=self,controller=self)
self.widgets_sw = MainScreenFrameCenterSW(parent=self,controller=self)
self.widgets_ne = MainScreenFrameCenterNE(parent=self,controller=self)
self.widgets_se = MainScreenFrameCenterSE(parent=self,controller=self)
class MainScreenFrameCenterNW(tk.Frame):
def __init__(self, parent, controller,*args,**kwargs):
tk.Frame.__init__(self,parent,height=350,width=640,bg="white",highlightbackground="black",highlightthickness=1)
self.controller = controller
self.grid(row=0,column=0,sticky="nsew")
class MainScreenFrameCenterSW(tk.Frame):
def __init__(self, parent, controller,*args,**kwargs):
tk.Frame.__init__(self,parent,height=350,width=640,bg="white",highlightbackground="black",highlightthickness=1)
self.controller = controller
self.grid(row=1,column=0,sticky="nsew")
class MainScreenFrameCenterNE(tk.Frame):
def __init__(self, parent, controller,*args,**kwargs):
tk.Frame.__init__(self,parent,height=350,width=640,bg="white",highlightbackground="black",highlightthickness=1)
self.controller = controller
self.grid(row=0,column=1,sticky="nsew")
class MainScreenFrameCenterSE(tk.Frame):
def __init__(self, parent, controller,*args,**kwargs):
tk.Frame.__init__(self,parent,height=350,width=640,bg="white",highlightbackground="black",highlightthickness=1)
self.controller = controller
self.grid(row=1,column=1,sticky="nsew")
You need to move all of the class definitions to global scope by putting them all at the same level of indentation.
class MainScreenFrameCenter(tk.Frame):
...
class MainScreenFrameCenterNW(tk.Frame):
...
class MainScreenFrameCenterSW(tk.Frame):
...
class MainScreenFrameCenterNE(tk.Frame):
...
class MainScreenFrameCenterSE(tk.Frame):
...
It seems you are attempting to make a small grid. Classes are generally not nested inside of another class. If you create a class that represents 1 grid cell, you can use a loop to create a grid from it.
import tkinter as tk
class Cell(tk.Frame):
def __init__(self, master, column:int=0, row:int=0, **kwargs):
kwargs = {**{'bg':'white', 'highlightbackground':'black','highlightthickness':1}, **kwargs}
tk.Frame.__init__(self, master, **kwargs)
self.grid(column=column, row=row, sticky='nswe')
class App(tk.Tk):
def __init__(self, **kwargs):
tk.Tk.__init__(self)
self.configure(**kwargs)
cols = 2
for i in range(cols):
self.grid_columnconfigure(i, weight=1)
rows = 2
for i in range(rows):
self.grid_rowconfigure(i, weight=1)
for i, c in enumerate(['nw', 'ne', 'sw', 'se']):
self.__dict__[f'widgets_{c}'] = Cell(self, i%cols, i//cols)
self.widgets_nw['background'] = 'red'
if __name__ == '__main__':
root = App(background="white", highlightbackground="black", highlightthickness=1)
root.geometry('640x680+300+300')
root.title('not Can is Should Example')
root.mainloop()
Related
I have Tkinter main class with a notebook:
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.color_widget = '#1B608E'
self.notebook = ttk.Notebook(self)
self.Page1 = Page1(self.notebook)
self.Page2 = Page2(self.notebook)
self.notebook.add(self.Page1, text='PCE Franchisés')
self.notebook.add(self.Page2, text='PVC Franchisés')
For each page of the notebook, I have a class defined as container:
class Page1(ttk.Frame):
def __init__(self, container):
super().__init__(container)
color = MainApplication.color_widget
self.label_INPUT = tk.Label(self, text='Settings', color=color,
)
self.label_INPUT.place(relx=0.03, rely=0.04)
class Page2(ttk.Frame):
def __init__(self, container):
super().__init__(container)
In each Page I want get the value of the variale color_widget defined in Main class. I tried MainApplication.color_widegt but it didn't work.
The simple way is to pass the instance of MainApplication to those pages, so that those pages can access the instance variable color_widget via the passed instance:
import tkinter as tk
from tkinter import ttk
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('800x600')
self.color_widget = '#1B608E'
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill='both', expand=1)
self.Page1 = Page1(self.notebook, self) # pass instance of MainApplication as well
self.Page2 = Page2(self.notebook, self)
self.notebook.add(self.Page1, text='PCE Franchisés')
self.notebook.add(self.Page2, text='PVC Franchisés')
class Page1(ttk.Frame):
# added controller argument
def __init__(self, container, controller):
super().__init__(container)
self.controller = controller
# access MainApplication.color_widget
color = controller.color_widget
self.label_INPUT = tk.Label(self, text='Settings', fg=color)
self.label_INPUT.place(relx=0.03, rely=0.04, anchor='nw')
class Page2(ttk.Frame):
def __init__(self, container, controller):
super().__init__(container)
self.controller = controller
MainApplication().mainloop()
I have this code below, and I want to pass variable n in class Data to be used in class tab1 and be used as the textvariable of entry1. however, I get this error:
AttributeError: 'dict' object has no attribute 'n'
or in general, I want to be able to pass variables between tabs.
import tkinter as tk
from tkinter import ttk
class Data:
def __init__(self):
self.n = tk.IntVar()
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.minsize(400, 400)
self.maxsize(400, 400)
self.my_notebook = ttk.Notebook(self)
self.my_notebook.pack(fill="both", expand=True)
self.app_data = {}
lst = [tab1, tab2, tab3]
for N, F in enumerate(lst):
tab = F(self.my_notebook, self.app_data)
self.my_notebook.add(tab, text="Tab"+str(N+1))
self.data = Data()
class tab1(tk.Frame):
def __init__(self, parent, data):
super().__init__(parent)
self.data = data
entry1 = tk.Entry(self, textvariable=data.n)
entry1.pack()
class tab2(tk.Frame):
def __init__(self, parent, data):
super().__init__(parent)
self.data = data
label2 = tk.Label(self, text="Tab2")
label2.pack()
class tab3(tk.Frame):
def __init__(self, parent, data):
super().__init__(parent)
self.data = data
label3 = tk.Label(self, text="Tab3")
label3.pack()
app = SampleApp()
app.mainloop()
You never create an instance of Data. Instead, you're initializing self.data to an empty dictionary. You need to create an instance of Data and pass that to the tabs.
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
self.app_data = Data()
...
I have a main class for my gui and I added a ttk.NoteBook after a label:
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('1000x500')
self.configure(background='#F0F8FF')
#TOP LABEL
load = Image.open("my_image")
load = load.resize((200, 67), Image.ANTIALIAS)
self.render = ImageTk.PhotoImage(load)
self.Label_top = tk.Label(self, image=self.render, compound=tk.LEFT, text="TOOL")
self.Label_top.pack()
#--Notebook---------
self.notebook = ttk.Notebook(self)
self.Page1 = Page1(self.notebook)
self.Page2 = Page2(self.notebook)
self.Page3 = Page3(self.notebook)
self.Page4 = Page4(self.notebook)
self.notebook.add(self.Page1, text='PAGE1')
self.notebook.add(self.Page2, text='PAGE2')
self.notebook.add(self.Page3, text='PAGE3')
self.notebook.add(self.Page4, text='PAGE4')
self.notebook.pack(fill='x', side=TOP)
#expand=True create empty space between my top label and my notebook, even with side=TOP
And I defined each frame in a class like this :
class Page1(ttk.Frame):
def __init__(self, container):
super().__init__()
self.(width=400, height=280) #Error message
self.pack(expand=True) #Doesn't work
Do you know how can I expand my frame for that it fills my page and pack the notebook just after my top label
I think this will do what you want. I've incorporated most of the things #Bryan Oakley mentioned in his answer except I also added a BasePage class and derived all the other Page classes from it. This was done to provide a place to put code that would otherwise need to be repeated each of the subclasses.
I also changed some of your variable names to conform to PEP 8 Naming Conventions.
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
class BasePage(ttk.Frame):
def __init__(self, container):
super().__init__(container, width=400, height=280)
classname = type(self).__name__
tk.Label(self, text=f'Welcome to {classname}').place(relx=0.5, rely=0.25,
anchor=CENTER)
class Page1(BasePage):
def __init__(self, container):
super().__init__(container)
class Page2(BasePage):
def __init__(self, container):
super().__init__(container)
class Page3(BasePage):
def __init__(self, container):
super().__init__(container)
class Page4(BasePage):
def __init__(self, container):
super().__init__(container)
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('1000x500')
self.configure(background='#F0F8FF')
#--Notebook---------
self.notebook = ttk.Notebook(self)
self.page1 = Page1(self.notebook)
self.page2 = Page2(self.notebook)
self.page3 = Page3(self.notebook)
self.page4 = Page4(self.notebook)
self.notebook.add(self.page1, text='Page1')
self.notebook.add(self.page2, text='Page2')
self.notebook.add(self.page3, text='Page3')
self.notebook.add(self.page4, text='Page4')
self.notebook.pack(expand=True, fill=BOTH)
app = MainApplication()
app.mainloop()
I see three problems.
First, each "page" needs to be a child of the notebook. You do that by making sure the notebook is passed to the __init__ of the frame:
class Page1(ttk.Frame):
def __init__(self, container):
super().__init__(container)
Second, you need to not call pack on the page. self.notebook.add is already adding the frame to the notebook. So, remove the line self.pack(expand=True) from each page.
Third, self.(width=400, height=280) needs to be self.configure(width=400, height=280)
I have two tkinter Frames through which I'm trying to pass values to determine which button was pressed. Here is what I have:
class GUIHandler(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frames = {}
for F in (MainFrame, GraphsTask):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MainFrame)
class MainFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.button_text = tk.StringVar()
tk.Button(self, text="Task 2a",
command=lambda: self.button_controller(GraphsTask, 'task_2a'))
def button_controller(self, class_name, btn_text):
self.controller.show_frame(class_name)
self.button_text.set(btn_text)
def get_button_text(self):
print(self.button_text.get())
return self.button_text.get()
class GraphsTask(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.bind("<<Show>>", self.get_graph)
def get_graph(self, event):
if self.controller.frames[MainFrame].get_button_text() == 'task_2a':
....
When I try to compare the result I get from get_button_text() function from MainFrame class, it returns empty even though the print statement inside that function prints the text I passed onto button_controller() function. I do not understand what is going on here.
You should update self.button_text first and then switch page:
def button_controller(self, class_name, btn_text):
self.button_text.set(btn_text)
self.controller.show_frame(class_name)
I'm creating an GUI interface for my own project in python with tkinter library.
On one frame i setted an tk.Entry for me to write some text.
I want to use this information in the next Frame (let's say in a tk.Label to be simple)
But i can't reach the information, seems to be because the two function belong to differents class.
Tried to make private_key global but seems to overwrite in the definition.
Tried to return private_key but i still can't access it because i can't call the parameter in the next class.
Tried to use the function again in the next class, same problem.
Tried to set the label in the PVK class, doesn't seems to work either
from tkinter import *
# type and size of font
LARGE_FONT = ('MS Serif', 15)
# white writing color
FRONT_COLOR = '#ffffff'
# dark_gray background color
BACKGROUND_COLOR = '#272727'
class Bobby(Tk):
# Used each time the function is called
def __init__(self):
# init tkinter
Tk.__init__(self)
Tk.iconbitmap(self, default='bobby.ico')
Tk.wm_title(self, "Bobby")
Tk.geometry(self, '500x200')
container = Frame(self)
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for f in (PVK, Display):
frame = f(container, self)
self.frames[f] = frame
frame.grid(row=0, column=0, sticky=NSEW)
self.show_frame(Welcome)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PVK(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.configure(background=BACKGROUND_COLOR)
here = Label(self, text="here", font=LARGE_FONT, background=BACKGROUND_COLOR, fg=FRONT_COLOR)
here.grid()
self.pvk = Entry(self, show=" ")
self.pvk.bind('<Return>', self.check)
self.pvk.grid()
def check(self, event):
private_key = int(self.pvk.get()), 11413
bobby.show_frame(Display)
return private_key
class Display(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.configure(background=BACKGROUND_COLOR)
#want to display it here
Label(self, text=str(PVK.private_key)).grid()
bobby = Bobby()
bobby.mainloop()
My expect is to display the label with the text in it which would mean i can use the variable.
I currently get the error that private_key isn't defined.
Since you pass controller into the __init__() of Display, then you could find the instance of the PVK class:
class PVK(Frame):
def __init__(self, parent, controller):
# stuff omitted
def check(self, event):
self.private_key = int(self.pvk.get()), 11413 # note self.private_key
bobby.show_frame(Display)
class Display(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.configure(background=BACKGROUND_COLOR)
#want to display it here
private_key = controller.frames[PVK].private_key
Label(self, text=str(private_key)).grid()
There will be other ways to access private_key, eg PVK could write it back to the controller: self.controller.private_key = private_key etc