Tkinter: window navigation inside a GUI - python

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)

Related

Resizing ttk.frame of a ttk notebook, frame defined in a other class than the notebook

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)

Tkinter won't accurately reflect kwargs when making a new frame. How do I change inheriting frame background color?

In my code, I'm creating an object called a HistoryFrame that is supposed to inherit from tkinter.Frame. I want to change the background color of my HistoryFrame as a parameter that's passed in on its creation, and when I print kwargs, it accurately reflects a that I've passed in {'bg':'blue', etc...}, but it doesn't change the actual color of the background of the frame.
How do I fix this? and why is this happening?
Picture of Application resulting from code below.
Code below. V---V
import tkinter as tk
class HistoryFrame(tk.Frame):
def __init__(self, parent, *args, **kwargs):
print(kwargs)
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
button = tk.Button(self, text="hello", fg="blue")
button.pack(side=tk.TOP, fill=tk.X)
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.width, self.height = max(parent.winfo_screenwidth(), 200), max(
parent.winfo_screenheight(), 200
)
self.history_frame = HistoryFrame.HistoryFrame(
self, width=max(20, self.width // 4), height=self.height, bg="blue"
)
self.history_frame.pack(side=tk.LEFT, fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("650x250")
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Your color is working fine. By default, when you add one or more widgets to a frame with pack or grid, the frame will shrink to fit its children. The color is there, it's just that the frame is hidden by the button.
You can observe this by changing how you pack the frame:
self.history_frame.pack(side=tk.LEFT, fill="both", expand=True)

How to get a variable from a parameter function outside his class?

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

subclass Label in tkinter

I need to subclass a Label widget, so that the browser can open a link while the mouse clicks on it.
Here is the code snippet of what I've done so far.
from tkinter import *
import webbrowser
class HyperLinkLabel(Label):
def __init__(self, link, *args, **kwargs):
Label.__init__(self, *args, **kwargs)
self.link = link
self.bind("<Button-1>", self.click_callback)
def click_callback(self):
webbrowser.open_new(self.link)
if __name__ == '__main__':
master = Tk()
root = Frame(master)
label1 = HyperLinkLabel(root, link='https://www.google.com')
label1.config(text='hello')
label1.pack()
root.master.minsize(100, 50)
root.mainloop()
You should declare master (or parent) parameter and pass it to Label constructor. (root from the perspect of caller)
event handle should have event parameter even though you don't use it. Otherwise TypeError exception is thrown.
Pack frame so that widgets inside it is visible.
from tkinter import *
import webbrowser
class HyperLinkLabel(Label):
def __init__(self, master, link, *args, **kwargs): # <-- pass master parameter
Label.__init__(self, master, *args, **kwargs) # <-- pass master parameter
self.link = link
self.bind("<Button-1>", self.click_callback)
def click_callback(self, event): # <--- missed event parameter
webbrowser.open_new(self.link)
if __name__ == '__main__':
master = Tk()
root = Frame(master)
label1 = HyperLinkLabel(root, link='https://www.google.com')
label1.config(text='hello')
label1.pack()
root.pack() # <-- should pack frame; otherwise link widget is not visible
root.master.minsize(100, 50)
root.mainloop()

Unable to change frame color once created

In the code below, app is an instance of mainWindow, which inherits from Tkinter.Frame. I am trying to use the Frame.Configure method to change the background color of the Frame.However, calling self.configure(background="yellow") doesn't work. Can someone help me understand what mistake I am making?
import Tkinter
class mainWindow(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, master=parent)
self.parent=parent
self.button1=Tkinter.Button(master=self.parent, text='ONE', command=self.change)
self.button1.pack()
self.pack()
def change(self):
self.parent.wm_title("Changed")
self.configure(background="yellow")
root = Tkinter.Tk()
root.geometry("600x600+50+50")
app=mainWindow(root)
root.mainloop()
It does not work, because your Frame is "tiny". It does not contain any widgets (button's parent is the top window, not the frame). So to make frame big, thus visible, you need to expand it:
import Tkinter
class mainWindow(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, master=parent)
self.parent=parent
self.button1=Tkinter.Button(master=self.parent,
text='ONE',
command=self.change)
self.button1.pack()
self.pack(fill=Tkinter.BOTH, expand=1) #<--- expand frame
def change(self):
self.parent.wm_title("Changed")
self.configure(background="yellow")
root = Tkinter.Tk()
root.geometry("600x600+50+50")
app=mainWindow(root)
root.mainloop()
Try self.parent.configure(background="yellow")
I'm new to Tkinter (few-minute new), so my guess based on your code is that the frame is not showing at all. The frame's parent is root, which is also the button's parent.
So here, I'm changing the root's (top level widget) background
Edited:
Base on my reasoning above, and Marcin's answer, I deduce that the frame simply just does not have a size. So here's an edited version of your code with the frame expanded, and the frame will contain the button.
import Tkinter
class mainWindow(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, master=parent)
self.parent=parent
self.button1=Tkinter.Button(master=self, text='ONE', command=self.change)
self.button1.pack()
self.pack(fill=Tkinter.BOTH, expand=True)
def change(self):
self.parent.wm_title("Changed")
self.configure(background="yellow")
root = Tkinter.Tk()
root.geometry("600x600+50+50")
app=mainWindow(root)
root.mainloop()

Categories

Resources