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
Related
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 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()
I've been doing a Gui multiple windows quest but Tkinter doesn't seem to have Tk. My full error is
Traceback (most recent call last):
File "/Users/connorsmacbook/PycharmProjects/2.8/2.8 Internal/TextTypers 2.2.py", line 6, in <module>
class TextTypers(tk.TK):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 2101, in __getattr__
return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'TK'
My code is
from tkinter import *
tk=Tk()
# Classes
class TextTypers(tk.TK):
def __init__(self, *args, **kwargs): # Runs when our class is called and allows almost anything to be passed
tk.Tk.__init__(self, *args, **kwargs) # Initialise Tk
window = tk.Frame(self) # Creates the container the windows/frames will populate
window.pack()
self.frames = {} # Creates a dictionary for the frames
frame = MenuScreen(window, self)
self.frames[MenuScreen] = frame
frame.grid(row=0, column=0, sticky="nswe")
self.show_frame(MenuScreen) # Shows the menu screen as this is initialising
def show_frame(self, cont):
frame = self.frames[cont] # Grabs value of self.frames and puts in in frame
frame.tkraise() # Raises frame to the front
class MenuScreen(tk.frame): # Inherits everything from the frame
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent) # Inherits from main class
label = tk.Label(self, text="Menu")
label.pack()
run = TextTypers()
run.mainloop()
If any wizards could help I would be grateful :).
The line
tk=Tk()
creates an instance of Tk() with the name tk.
When you create the class
class TextTypers(tk.TK):
you are trying to inherit an attribute called TK from the instance tk.
In general, I would not use the name tk for the root window as tk is usually used as an alias for the tkinter module.
I think what you are after is something like this:
import tkinter as tk
# Classes
class TextTypers(tk.Tk):
def __init__(self, *args, **kwargs): # Runs when our class is called and allows almost anything to be passed
tk.Tk.__init__(self, *args, **kwargs) # Initialise Tk
window = tk.Frame(self) # Creates the container the windows/frames will populate
window.pack()
self.frames = {} # Creates a dictionary for the frames
frame = MenuScreen(window, self)
self.frames[MenuScreen] = frame
frame.grid(row=0, column=0, sticky="nswe")
self.show_frame(MenuScreen) # Shows the menu screen as this is initialising
def show_frame(self, cont):
frame = self.frames[cont] # Grabs value of self.frames and puts in in frame
frame.tkraise() # Raises frame to the front
class MenuScreen(tk.Frame): # Inherits everything from the frame
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent) # Inherits from main class
label = tk.Label(self, text="Menu")
label.pack()
run = TextTypers()
run.mainloop()
Have a look at Best way to structure a tkinter application were you can find some suggestions and discussion.
I posted a question about this topic a few days ago but since my sample code in that question was wrong I deleted that topic to come up with a cleaner sample code.
what's the best practice to navigate through different pages/windows in a GUI built by Tkinter? simply, I want to be able to go through different pages in my App, via commands from my menubar. I want to avoid stacking pages on top of each other and a method in which you use grid_remove() or pack_forget() is preferable to me.
The only other tutorial which I found here uses the stacking method and lift(). is there any other better way?
import tkinter as tk
from tkinter import *
class MainWin(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.page_1 = Page1(self.parent)
self.page_2 = Page2(self.parent)
self.init_UI()
def init_UI(self):
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
self.parent.title('Frame Switching test app')
file_menu = Menu(menubar)
pages_menu = Menu(menubar)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label='Exit', command=self.on_exit)
menubar.add_cascade(label='Pages', menu=pages_menu)
pages_menu.add_command(label='Pages 1', command=self.page_1.show)
pages_menu.add_command(label='Page 2', command=self.page_2.show)
def on_exit(self):
self.quit()
class Page1(LabelFrame):
def __init__(self, parent):
LabelFrame.__init__(self, parent)
self.parent = parent
self.config(text='This is page 1 label Frame')
self.sample_text = Label(self, text='You are viewing Page 1')
def show(self):
self.pack(fill=BOTH, expand=1)
self.sample_text.grid(in_=self)
self.lift()
def close(self):
self.pack_forget()
class Page2(LabelFrame):
def __init__(self, parent):
LabelFrame.__init__(self, parent)
self.parent = parent
self.config(text='This is page 2 label Frame')
self.sample_text = Label(self, text='You are viewing Page 2')
def show(self):
self.pack(fill=BOTH, expand=1)
self.sample_text.grid(in_=self)
self.lift()
def close(self):
self.pack_forget()
def main():
root = tk.Tk()
app = MainWin(root)
root.mainloop()
if __name__ == '__main__':
main()
There's already a question and answer that shows how to stack frames. To switch to a mode where you use grid_forget or pack_forget you only have to change the code that calls lift to instead call the appropriate "forget" method on the current page (which you'll need to keep track of), and then add the new window.
If you want to create the pages on demand, and destroy them when they aren't in use, that's easy too. The only real difference is that you don't create the page until it is asked for, and delete it when you are done. Otherwise the implementation is identical.
Following is an example of creating the pages on demand. Starting with the code in this answer, modify the SampleApp class to look like this:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll pack the current page
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.current_frame = None
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
# destroy the old page, if there is one
if self.current_frame is not None:
self.current_frame.destroy()
# create the new page and pack it in the container
cls = globals()[page_name]
self.current_frame = cls(self.container, self)
self.current_frame.pack(fill="both", expand=True)