Smooth frame-switch and window resize in Tkinter - python

Using the methodology described in this post, I've created a multi-frame application. Without resizing the main window, the transitions from one frame to the next are very smooth, as all of the widgets are pre-loaded upon initializing the application.
However, many of my frames have different sizes, so ideally I would like to be able to set the geometry of the main window according to the requested size of the frame. I've used two methods thus far to achieve this, both working successfully:
Using frame.grid_remove() on the top frame when switching to another, and then frame.grid() on the new top frame, as described in this post.
Using the incoming frame's size attributes, frame.winfo_reqwidth() and frame.winfo_reqheight(), and then calling the master window's geometry(f'{width}x{height}') method to set the size.
However, my issue is that this transition, in both cases, is not smooth, and causes visual disruptions when switching frames, including leaving widgets blacked out until the mouse is moved again. I would like to make the resizing process less visually disruptive for my end-users.
import tkinter as tk, tkinter.ttk as ttk
class Application(tk.Tk):
def __init__(self):
super().__init__()
container = tk.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 (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame(PageOne))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame(PageTwo))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
button.pack()
The different versions of the Application method show_frame I have tried to use are:
1.
class Application(tk.Tk):
def show_frame(self, page_name, frame_removed):
frame = self.frames[page_name]
frame_removed.grid_remove()
frame.grid(sticky="nswe")
class StartPage(ttk.Frame):
def __init__(self, parent, controller):
...
button1 = ttk.Button(self, text="Go to page one",
command=lambda: controller.show_frame(PageOne, self))
button2 = ttk.Button(self, text="Go to page two",
command=lambda: controller.show_frame(PageTwo, self))
# all pages have been modified in the same manner as this
class Application(tk.Tk):
def show_frame(self, page_name):
frame = self.frames[page_name]
width = str(frame.winfo_reqwidth())
height = str(frame.winfo_reqheight())
if width != "1":
self.geometry(f'{width}x{height}')
frame.tkraise()

Related

Is it possible to center a frame in the middle of the screen in Python Tkinter?

I am a high school student coding in Python and I have a very simple GUI application, and my program is different from others in that it doesn't use normal Tkinter windows. I used frames so that my code would flow more, but now I want to know is it possible to make each frame pop up in the center of my screen and not off to the top left. I am still new to this so let me know if you need anything more. Thank you <3
Here is my code:
import tkinter as tk
from tkinter import font as tkfont
LARGE_FONT = ("Verdana", 12)
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.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 (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Raising a Tkinter frame from a separate function

Apologies if the title is a bit broad.
I am creating a Tkinter app with multiple pages and I'm using this piece of code in order to do so.
Each page is a frame and frames are raised by calling the 'show_frame' function. I have no problem with switching between pages using buttons, however I want to run a certain function and change pages if a condition is met.
Below is an example:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.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 (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
def doSomething():
...
if x == y:
"RAISE PAGE ONE"
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
As you can see, the doSomething function is outside of any class. How would I go about raising PageOne from this function.
The easiest answer would be to make doSomething() a method of SampleApp. You would then have access to the method show_frame. However, your question seems to imply this isn't an option.
Failing this, I would advise passing app to the function, and then calling app.show_frame("PageName").
If you don't want to pass a pointer to app as an argument, then you could pass doSomething the function app.show_frame as foo, and then call foo("PageName") when you want the page to be shown.
E.g.
def doSomething(foo):
'''some code'''
foo("page name")
doSomething(app.show_frame)

Add picture with ImageTk for Multiple Windows/Frames in Tkinter GUI with Python

I am trying to create a mutiple-frames GUI. The code I am using is working fine for this purpose. However, when I add some pictures in some frames, the GUI appears in two windows: one with normal functionalities and arrangement; one with nothing inside it. If I close either of them, both of them are closed.
I use Python 2.7.
Would anybody please explain to me what happened?
Edit: I know the reason now. It is because I use tk.Toplevel instead of tk.Tk. With tk.Tk, I have no problem with double windows but I cannot get my pictures shown. Any help?
Here is my code:
import tkinter as tk # python3
#import Tkinter as tk # python
from PIL import ImageTk, Image
TITLE_FONT = ("Helvetica", 18, "bold")
img = Image.open('arrow.png')
class SampleApp(tk.Toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.arrow = ImageTk.PhotoImage(img)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
# label.pack(side="top", fill="x", pady=10)
label.grid(row=0)
arrow1 = tk.Label(self, image = self.controller.arrow)
arrow2 = tk.Label(self, image = self.controller.arrow)
arrow1.grid(row=1,column=0)
arrow2.grid(row=2,column=0)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.grid(row=1,column=1)
button2.grid(row=2,column=1)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
label.grid(row=0)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.grid(row=1,column=1)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You forgot to include the image=... option into your label in PageOne and PageTwo. I have added it. ;)
Also, removed the Toplevel. Alternatively, you can still use Toplevel as you did and hide the root=tk.Tk() window after it has been created.
#import tkinter as tk # python3
import Tkinter as tk # python
from PIL import ImageTk, Image
TITLE_FONT = ("Helvetica", 18, "bold")
img = Image.open('arrow.png')
class SampleApp(tk.Frame): #change to tk.Frame
def __init__(self, parent, *args, **kwargs): #added parent
tk.Frame.__init__(self, parent, *args, **kwargs) #added parent
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(parent) #changed self to parent
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.arrow = ImageTk.PhotoImage(img)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
# label.pack(side="top", fill="x", pady=10)
label.grid(row=0)
arrow1 = tk.Label(self, image = self.controller.arrow)
arrow2 = tk.Label(self, image = self.controller.arrow)
arrow1.grid(row=1,column=0)
arrow2.grid(row=2,column=0)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.grid(row=1,column=1)
button2.grid(row=2,column=1)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT,
image = self.controller.arrow) # Added
label.pack(side="top", fill="x", pady=10)
label.grid(row=0)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.grid(row=1,column=1)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT,
image = self.controller.arrow) # Added
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
root = tk.Tk() #Added
app = SampleApp(root) #added root
root.mainloop()

TKinter focus command not working on the widget of the second called form from stack of forms [duplicate]

This question already has answers here:
Tkinter focus_set and focus_force not working as expected
(2 answers)
Closed 6 years ago.
I used the codes (below) from Switch between two frames in tkinter
(thanks to Brian Oakley) on my first pilot project using Python3.5 + Tkinter and was having issue doing a "focus" on an Entry widget from the second form (PageOne), the focus is not working at all.
However, if I do the "focus" on any widget from the first or starting form (StartPage) it works! And if I switch calling first PageOne before StartPage the "focus" on the second form would now work. Can you please provide some tips on how I would handle doing a "focus" on any of the widget on the second form (PageOne) based on the code below?
import tkinter as tk
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.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 (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I was able to find an answer to this issue, I found it from this post. And the explanation was given by anderswb
What you are doing in the init method of DIS is to draw all of the pages, basically on top of each other, and then you use tkraise to pull the one you need to the front using tkraise(). For some reason this seems to mess up the focus, so you need to set the focus after you've pulled it to the front. That is why I've added an extra method which will be called on all of you pages after it has been raised. – anderswb Oct 20 '15 at 19:27
tkinter-focus-set-and-focus-force-not-working-as-expected

tkinter access widget on a different window

So I'm fairly new to the tkiner programming and trying my hands at a GUI with more than one window wich should scan Wifi-Hotspots and show me a list of them. I've copied an example from Switch between two frames in tkinter to get the different windows
I have a Main Menu to enable Monitor Mode for the Wifi-Card and to start the scan. I'm calling another frame with a Listbox to show the results.
My problem now is, that the function startScan(self) is called within StartPage, while the Listbox is in PageOne. How can I adress it there and add entrys to it?
class PyWiFi(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.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 (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, c):
'''Show a frame for the given class'''
frame = self.frames[c]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
self.var = tk.IntVar()
monCheck = tk.Checkbutton(self, text="Monitor Mode", variable=self.var, command=self.monSwitch)
scanButton = tk.Button(self, text="Start Scan", command=self.startScan)
quitButton = tk.Button(self, text="Quit", command=self.master.quit)
monCheck.pack()
scanButton.pack()
quitButton.pack()
def monSwitch(self):
if(self.var.get()):
print "Monitor Modus an"
check_call(["airmon-ng", "start", "wlan0"])
else:
print "Monitor Modus aus"
check_call(["airmon-ng", "stop", "mon0"])
def startScan(self):
print "Scan gestartet"
app.show_frame(PageOne)
output=check_output('iwlist wlan0 scan | grep -E "Channel:|ESSID:"', shell=True)
netze = output.split()
print netze
for i in range(0,(len(netze)/2)-1):
string = netze[2*i]+" "+netze[2*i+1]
app.frames[PageOne].netzList.insert(END, string)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Scanergebnisse", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
menuButton = tk.Button(self, text="Menu", command=lambda: controller.show_frame(StartPage))
quitButton = tk.Button(self, text="Quit", command=self.master.quit)
button = tk.Button(self, text="P2", command=lambda: controller.show_frame(PageTwo))
netzList = tk.Listbox(self, width=30)
netzList.pack()
quitButton.pack(side=LEFT)
menuButton.pack(side=LEFT)
button.pack(side=LEFT)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
button.pack()
if __name__ == "__main__":
app = PyWiFi()
app.resizable(0, 0)
app.geometry("320x240")
app.mainloop()
netzlist and all other widgets defined in PageOne are local to __init__. You should name them self.netzlist etc. to be able to reference them as class attributes of PageOne later on.

Categories