Displaying only one frame at a time in tkinter - python

I've been struggling with this for a while. I think I'm missing some simple piece of information and I hope you guys can help clear this up for me.
I'm trying to get tkinter to display different frames which I will eventually place widgets inside of. Here's what I did:
I've made a class that is supposed to initialize the window and make all the different frames the program will run.
I've made a separate class for each frame(I'm intending to have variables associated with the different classes when the program is done), and assigned a variable that will start that class up and make it run it's init function
I ended the StartUp class by telling it to tkraise() the frame I want displayed, and that's where things stop working correctly.
I set each frame to a different color, so when you run this program you will see that they split the screen space up instead of one being raised to the top. What am I missing?
One last point, I am purposely trying to spell everything out in my program, I learn better that way. I left it so I have to type tkinter.blah-blah-blah in front of each tkinter command so I can recognize them easily, and I decided not to have my classes inherit Frame or Tk or anything. I'm trying to understand what I'm doing.
import tkinter
class StartUp:
def __init__(self):
self.root = tkinter.Tk()
self.root.geometry('300x300')
self.container = tkinter.Frame(master=self.root, bg='blue')
self.container.pack(side='top', fill='both', expand=True)
self.page1 = Page1(self)
self.page2 = Page2(self)
self.page1.main_frame.tkraise()
class Page1():
def __init__(self, parent):
self.main_frame = tkinter.Frame(master=parent.container, bg='green')
self.main_frame.pack(side='top', fill='both', expand=True)
class Page2():
def __init__(self, parent):
self.main_frame = tkinter.Frame(master=parent.container, bg='yellow')
self.main_frame.pack(side='top', fill='both', expand=True)
boot_up = StartUp()
boot_up.root.mainloop()

When you do pack(side='top', ...), top doesn't refer to the top of the containing widget, it refers to the top of any empty space in the containing widget. Page initially takes up all of the space, and then when you pack Page2, it goes below Page1 rather than being layered on top of it.
If you are using the strategy of raising one window above another, you need to either use grid or place to layer the widgets on top of each other. The layering is something pack simply can't do.
Your other choice is to call pack_forget on the current window before calling pack on the new windowl

Related

How can I add custom Frame objects to custom ttk Notebook in Tkinter/python3?

I am creating a Tkinter/Python3 application where the main window inherits from Notebook (i need tabs), and each tab should be a custom class inheriting from Frame (I would then dynamically use matplotlib to create custom graphs).
Unfortunately I don't seem to be able to have Notebook accept my custom Frames.
Following very reduced snippet of code:
#!/usr/bin/env python3
from tkinter import *
from tkinter.ttk import Notebook
class MyFrame1(Frame):
def __init__(self, master=None, mytext=""):
super().__init__(master)
self.create_widgets(mytext)
def create_widgets(self, mytext):
self.label = Label(self.master, text=mytext, anchor=W)
# this is not placed relative to the Frame, but to the
# master
# 1. How I get the relative coordinates inside the frame
# to be 10, 10 of the frame area?
self.label.place(x=10, y=10, width=128, height=24)
class MyNotebook(Notebook):
def __init__(self, master=None):
super().__init__(master)
self.create_widgets()
def create_widgets(self):
self.f1 = MyFrame1(self, "abc")
# once the UI is drawn, the label "def" seems to overlay
# "abc" even when "f1" is selected
# 2. Why is self.f2 always shown even when self.f1 is
# selected?
self.f2 = MyFrame1(self, "def")
self.add(self.f1, text="f1")
self.add(self.f2, text="f2")
# Without this command nothing gets drawn
# 3. Why is this? Is this equivalent of 'pack' but for
# pixel driven layout?
self.place(width=640, height=480)
def main():
root = Tk()
root.minsize(640, 480)
root.geometry("640x480")
app = MyNotebook(master=root)
# this works as intended the label is indeed placed
# in the frame at 10, 10
#app = MyFrame1(master=root, mytext="123abc")
app.mainloop()
return None
if __name__ == "__main__":
main()
As per comments I have the following main question: why aren't my custom instances of MyFrame1 properly displayed inside MyNotebook?
Sub questions:
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Is self.place required in order to show all sub-elements when not using pack?
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
Not sure what I'm doing wrong?
Thanks!
Not sure what I'm doing wrong?
Your create_widgets method needs to add widgets to self, not self.master.
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
I don't understand what you mean by this. When you use place, coordinates will be interpreted relative to the frame. However, I strongly advise against using place. Both pack and grid will trigger the frame to resize to fit its children which almost always results in a more responsive UI
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Because you added internal widgets to self.master instead of self.
Is self.place required in order to show all sub-elements when not using pack?
No. It is required to use a geometry manager but it doesn't have to be place. Usually, place is the least desirable geometry manager to use. pack and grid are almost always better choices except for some very specific situations.
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
They will be in whatever tab you put them in.
Finally, I would suggest that you remove self.place in create_widgets. Instead, call pack, place, or grid in the same block of code that creates an instance of that class.
It's a bad practice for a widget to add itself to another widget's layout. The code that creates the widget should be the code that adds the widget to the layout.

Python Tkinter OOP Layout Configuration

I am trying to build an application with tkinter.
The layout works without OO principles, but I am struggling to understand how I should move it to OO.
The layout is as shown in the pic below. (1280x720px)
I have the following:
banner on top with username/welcome message, and logo rh corner, columnspan=8
menu bar with buttons on the left, split into 2 rows (row1: rowspan 6, row2: rowspan=4)
working area (white block) that has a Frame, which I'll add a notebook to, each menu button opening a different notebook page.
What is the best way to make this OO? (I am still learning, so very new to OO)
There is no straight translation possible, since everything depends on your needs.
If you create a simple programm you can just create the class and create every Button,Label,Frame... in the constructor. When created you have to choose one of the layout managers grid,pack or place. After that you create your functions and you are done. If you deal with bigger projects and have a big amount of Labels, Buttons etc.. you maybe want to create containers for each.
In your case you wont need a lot of functions and buttons, so you should maybe go with the basic approach:
from tkinter import *
class name_gui:
def __init__(self, top):
#specify main window
self.top = top
self.title = top.title("name_gui")
self.minsize = top.geometry("1280x720")
self.resizable = top.resizable(height=False,width=False)
#create buttons,Labels,Frames..
self.Button1 = Button(top,text="Button1",command=self.exa_fun)
self.Button2 = Button(top,text="Button2",command=self.exa_fun2)
#place them, choose grid/place/pack
self.Button1.place(relx=0.5,rely=0.5)
self.Button2.place(relx=0.5,rely=0.2)
#create your functions
def exa_fun(self):
pass
def exa_fun2(self):
pass
top = Tk()
exa_gui = name_gui(top)
top.mainloop()

How do I use destroy within functions and classes - tkinter?

I've seen some usage of self.destroy() within classes but I couldn't get it working with what I wanted it to do.
I have the class resultsPage that shows results obtained on another page. I have made the displayResults(pageNo) function to show these when resultsPage is visible. The problem arises with the back and next buttons which are made to go between pages of results. All widgets are created on top of each other but I want to remove them all then create the new ones. I added self.destroy() to try and fix this but it didn't work.
I'm not sure if it's to do with the placement of where I'm defining my functions but I have had a play around with where they're defined and it hasn't changed the error message.
This is a simplified example of my code:
class resultsPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def onShowFrame(self, event):
def displayResults(pageNo):
self.destroy()
#Create widgets related to pageNo
#Create back and next buttons e.g.
back = tk.Button(self, text="<=",
command=lambda: displayResults(pageNo - 1))
displayResults(1)
The error I get is: _tkinter.TclError: bad window path name ".!frame.!previewresultspage"
If it helps, I can post my full code but I thought I'd generalise it so it's more helpful to others.
You are deleting the widget in onShowFrame, and then immediately try to create a new widget with it as the parent. You can't use a deleted widget as the parent of another widget.
As pointed out, using self.destroy() in this case, will not work. To achieve the goal of deleting all widgets on the frame, you can use a loop (credit to #stovfl):
for widget in self.grid_slaves():
widget.destroy()

Inheritance from Tkinter Frame in varying implementations

I'm fairly new to python and try to build a simple GUI following an object oriented approach. Therefor I let my widget classes inherit from tk.Frame and create an application controller to build the GUI.
My application contains the following two files:
mainModule.py
# coding: utf8
from testPackage import myGUI
import Tkinter as tk
root = tk.Tk() # Main window
my_gui = myGUI.MainApplication(root)
root.mainloop() # Hold window open until we close it
myGUI.py
# coding: utf8
import Tkinter as tk
# Application initializer
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.configure_gui()
self.pack() # <--- CODE IN QUESTION
self.create_widgets()
def configure_gui(self):
self.master.title("Any title")
self.master.geometry('800x600')
self.master.minsize(600, 100)
def create_widgets(self):
self.main_window = MainWindow(self)
self.main_window.pack()
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Initially running my code without self.pack() (marked as #Code in question) in the MainApplication class definition gave me only the root basic window without the Label created from MainWindow class.
As simple as the answer may be but why?
I used a few sources to get into the topic inbefore and some of them didn't use any geometry manager on the root window (or i'm to inexpierienced to see it. Source below for examples).
Source:
https://www.begueradj.com/tkinter-best-practices.html
http://python-textbok.readthedocs.io/en/latest/Introduction_to_GUI_Programming.html#putting-it-all-together
Tkinter example code for multiple windows, why won't buttons load correctly?
Only after reading following answers regarding widget creation I came aware of the missing part:
Best way to structure a tkinter application
creating a custom widget in tkinter
Figuring that pack() on initialization would be the same as
MainApplication(root).pack(side="top", fill="both", expand=True)
or
Example(root).place(x=0, y=0, relwidth=1, relheight=1)
for a grid based layout.
I guess it's a very simple or obvious element i don't see relating to inheritance but i'm not entirely sure why have to pack() the MainApplication Frame and furthermore why it seems to work for example without this step.
Because MainWindow inherits from Frame, it is itself a frame. If you never call pack, place, or grid on it, it will be invisible. This is no different than if you created a button or scrollbar or any other widget and then don't call one of those methods on it.
Since all of the other widgets are a children of this frame, they will be invisible since their parent is invisible.
Somewhat unrelated to the question being asked, self.pack() is a code smell. Generally speaking, a class should never call pack, place or grid on itself. This tightly couples itself to the caller (meaning, this widget has to know that the caller is using one of those methods).
In other words, if you decide that MainApplication wants to switch from pack to grid for all of its children, you can't just update MainApplication, you also have to update MainWindow. In this case, the root window has only one child so the problem is fairly small, but by doing it this way you are starting a bad practice that will eventually cause you problems.
The rule of thumb is that the function that creates a widget should be responsible for adding it to the screen. That means that it would be better to do it like this:
root = tk.Tk()
my_gui = myGUI.MainApplication(root)
my_gui.pack(fill="both", expand=True)
You would then need to remove self.pack() from MainApplication.__init__.
You also have some very misleading code that might be contributing to the confusion. Take a look at this code:
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
...
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Notice how you have a class named MainWindow. Within that you have a label that is named self.main_window, but self.main_window is not a MainWindow. This is especially confusing since the function that creates MainWindow also creates an instance variable named self.main_window.
You might want to consider renaming this label to be something else (eg: self.label, self.greeting, etc).

Horizontal Scrollbar not working in Python TKinter Frame

I have a canvas (for ttk.Frame not having xview,yview methods) holding frames inside of it, which I want to browse horizontally, as they are in the same row. Found some tutorials, and while they were dealing with different combination of widgets, I followed them, and tried to apply them to my problem, but with no success. I do see a scrollbar, but it doesn't do or respond to anything.
class App:
def __init__(self,db):
self.db = db
self.root = tkinter.Tk()
self.masterframe = ttk.Frame(self.root)
self.masterframe.grid()
self.mastercanvas = tkinter.Canvas(self.masterframe)
self.mastercanvas.grid()
self.scrollbar = ttk.Scrollbar(self.masterframe,orient="horizontal",
command = self.mastercanvas.xview)
self.scrollbar.grid()
self.mastercanvas.configure(xscrollcommand=self.scrollbar.set)
for i,e in enumerate(self.db.elements):
xf = XFrame(self,e)
xf.grid(row=0,column=i,sticky="n")
Edit:
class XFrame:
def __init__(self,app,x):
self.app = app
self.x = x
self.frame = ttk.Frame(self.app.mastercanvas)
self.set_up() # this sets frame's padding and populates it with widgets
Now, where ever I paste two lines of code here suggested* - at the end of the first init definition, or at the end of the second init definition - nothing new happens. I see my frames appearing as I intended them to appear - 3 of them. Part of the 4th. And an unfunctional scrollbar.
*
self.update_idletasks() # using self.root in first, self.app.root in second variant
self.mastercanvas.configure(scrollregion=self.mastercanvas.bbox("all"))
# in second variant reffered to as self.app.mastercanvas ...
You've got to configure the scrollregion attribute of the canvas so that it knows how much of the virtual canvas you want to scroll. This should do it:
# create everything that will be inside the canvas
...
# next, cause the window to be drawn, so the frame will auto-size
self.update_idletasks()
# now, tell the canvas to scroll everything that is inside it
self.mastercanvas.configure(scrollregion=self.mastercanvas.bbox("all"))
That being said, there's nothing in your canvas to be scrolled, and I'm not entirely sure what you were expecting to be in the canvas. If you are wanting to scroll through the instances of XFrame, they need to be children of some other frame, and that other frame needs to be an object on the canvas.

Categories