How would I make a method which is run every time a frame is shown in tkinter
Tkinter has low level events such as <Visibility>
and <Map>
which should fire when the pages change. Unfortunately, these don't work reliably on all platforms.
The simplest and most robust solution is to generate your own event. You can create and bind to a custom event by specifying the event inside <<
and >>
(eg: <<ShowFrame>>
).
First, change show_frame
to send an event to a window when it is shown:
def show_frame(self, page_name):
...
frame.event_generate("<<ShowFrame>>")
Next, each page can bind to this event if it needs to be notified when it is made visible:
class UploadPage(tk.Frame):
def __init__(self, parent, controller):
...
self.bind("<<ShowFrame>>", self.on_show_frame)
def on_show_frame(self, event):
print("I am being shown...")
tkinter - Run Function after Frame is Displayed
The problem is related to the fact that you're calling sleep
which causes the application to sleep. In order for the display to update, the event loop has to be able to process events, which it cannot do while sleeping. To sleep literally means to stop all processing. The first six words of the documentation for time.sleep
is Suspend execution of the current thread.
This is one place where calling update
on a widget is a reasonable thing to do. It will give tkinter a chance to process all pending events -- such as the finishing of handling a button click, redrawing the window, etc -- before generating the new event.
It's as easy as changing your function to look like this:
def showFrame(self,cont):
frame = self.frames[cont]
frame.tkraise()
frame.update()
frame.event_generate("<<ShowFrame>>")
run daemons only when frames are opened in tkinter
The simple solution is to not create the frames until you need them. You can do that my modifying show_frame
to create the frames on demand.
First, you need to modify the code that initializes the UI to not create the frames. Instead, it can just create a mapping from a page name to the page class. You need to remove the loop that begins with for F in (StartPage, PageOne, PageTwo):
and replace it with something like this:
self.frames = {
"StartPage": StartPage,
"PageOne": PageOne,
"PageTwo": PageTwo,
}
Next, modify show_frame
to destroy any existing frame and then create the new frame. It should look something like this:
def show_frame(self, page_name):
# destroy the old frame
for child in container.winfo_children():
child.destroy()
# create the new frame
frame_class = self.frames[page_name]
frame = frame_class(parent=self.container, controller=self)
frame.pack(fill="both", expand=True)
Another solution would be to keep the original code, but only start the threads when the frame is shown. An example of how to run a function when the frame is shown can be seen in the accepted answer to this question: How would I make a method which is run every time a frame is shown in tkinter
Python/Tkinter - Button to run function with multiple arguments, change Frame and send a letter
You forgot ()
to execute second function
tk.Button(..., command=lambda:[controller.show_frame("PageOne"), self.Forward()] )
But preferred and more readable would be to create separated function for this
tk.Button(..., command=self.next_page )
def next_page(self):
self.controller.show_frame("PageOne")
self.Forward()
To make it more useful you could set this as variable
self.next_page_name = "PageOne"
tk.Button(..., command=self.next_page )
def next_page(self):
self.controller.show_frame(self.next_page_name)
self.Forward()
And now you can use OOP
to reduce code - as you said: make "def" global
.
First create class with functions forward
, backward
, reset
(PEP8: lower_case_names
for methods. See more PEP 8 -- Style Guide for Python Code )
class BasePage(tk.Frame):
def reset(self):
global status1
ser.write(b'R')
status1 = ser.readline()
def backward(self):
global status1
ser.write(b'A')
status1 = ser.readline()
def forward(self):
global status1
ser.write(b'D')
status1 = ser.readline()
def next_page(self):
self.controller.show_frame(self.next_page_name)
self.forward()
And next use BasePage
to create other pages
class StartPage(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
label = tk.Label(self, text="ESTADO A", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.next_page_name = 'PageOne'
button1 = tk.Button(self, text="Ir a Estado B", command=next_page)
button1.pack()
class PageOne(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
label = tk.Label(self, text="ESTADO B", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.next_page_name = 'PageTwo'
button1 = tk.Button(self, text="Ir a A", command=self.next_page)
button2 = tk.Button(self, text="Ir a C", command=self.backward)
button3 = tk.Button(self, text="RESET", command=self.reset)
button1.pack()
button2.pack()
button3.pack()
class PageTwo(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
label = tk.Label(self, text="ESTADO C", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.next_page_name = 'PageThree'
button1 = tk.Button(self, text="Ir a B", command=self.next_page)
button2 = tk.Button(self, text="Ir a D", command=self.backward)
button3 = tk.Button(self, text="RESET", command=self.reset)
button1.pack()
button2.pack()
button3.pack()
class PageThree(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
label = tk.Label(self, text="ESTADO D", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.next_page_name = '???'
button1 = tk.Button(self, text="Ir a C",command=self.next_page)
button2 = tk.Button(self, text="RESET", command=self.reset)
button1.pack()
button2.pack()
Similar way you could add to BasePage
function which create label and buttons
class BasePage(tk.Frame):
# ... code ...
def create_gui(self, label_text, buttons):
label = tk.Label(self, text=label_text, font=self.controller.title_font)
label.pack(side="top", fill="x", pady=10)
for text,func in buttons:
tk.Button(self, text=text, command=func).pack()
And later use it with different paramaters in different pages
class StartPage(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.next_page_name = 'PageOne'
self.create_gui("ESTADO A", [("Ir a Estado B", next_page)])
class PageOne(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.next_page_name = 'PageTwo'
self.create_gui("ESTADO B",
[("Ir a A", next_page), ("Ir a C", self.backward), ("RESET", self.reset)])
class PageTwo(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.next_page_name = 'PageThree'
self.create_gui("ESTADO C",
[("Ir a B", next_page), ("Ir a D", self.backward), ("RESET", self.reset)])
class PageThree(BasePage):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.next_page_name = '???'
self.create_gui("ESTADO D",
[("Ir a C", next_page), ("RESET", self.reset)])
I am trying to create a new window and execute a function after that new window creation in tkinter
Tkinter has two universal widget methods that will help do what you want. One is called wait_visibility()
which will pause the program until the new Toplevel
is visible. The second is called after()
which allows the scheduling of call to a function after a delay specified in milliseconds. If the called function does the same thing, effectively it creates a loop.
Here's how to use them:
import tkinter as tk
import time
def loop(limit, i):
if i < limit:
print(i)
window.after(1000, loop, limit, i+1)
def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
window.wait_visibility(new)
loop(5, 0)
window = tk.Tk()
window.geometry("450x250")
window.title('main window')
button = tk.Button(window, text='button', width=20, command=new)
button.place(x=180,y=200)
window.mainloop()
Switch between two frames in tkinter?
One way is to stack the frames on top of each other, then you can simply raise one above the other in the stacking order. The one on top will be the one that is visible. This works best if all the frames are the same size, but with a little work you can get it to work with any sized frames.
Note: for this to work, all of the widgets for a page must have that page (ie: self
) or a descendant as a parent (or master, depending on the terminology you prefer).
Here's a bit of a contrived example to show you the general concept:
try:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
except ImportError:
import Tkinter as tk # python 2
import tkFont as tkfont # python 2
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()
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()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
If you find the concept of creating instance in a class confusing, or if different pages need different arguments during construction, you can explicitly call each class separately. The loop serves mainly to illustrate the point that each class is identical.
For example, to create the classes individually you can remove the loop (for F in (StartPage, ...)
with this:
self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["PageOne"] = PageOne(parent=container, controller=self)
self.frames["PageTwo"] = PageTwo(parent=container, controller=self)
self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.frames["PageOne"].grid(row=0, column=0, sticky="nsew")
self.frames["PageTwo"].grid(row=0, column=0, sticky="nsew")
Over time people have asked other questions using this code (or an online tutorial that copied this code) as a starting point. You might want to read the answers to these questions:
- Understanding parent and controller in Tkinter __init__
- Tkinter! Understanding how to switch frames
- How to get variable data from a class
- Calling functions from a Tkinter Frame to another
- How to access variables from different classes in tkinter?
- How would I make a method which is run every time a frame is shown in tkinter
- Tkinter Frame Resize
- Tkinter have code for pages in separate files
- Refresh a tkinter frame on button press
Related Topics
Multithreaded Web Server in Python
Get Files Names Inside a Zip File on Ftp Server Without Downloading Whole Archive
Keep Same Dummy Variable in Training and Testing Data
Check If Value Already Exists Within List of Dictionaries
Python Imaging Library - Text Rendering
Importerror: No Module Named Win32Api
Building Lxml for Python 2.7 on Windows
Scikit-Learn Gridsearchcv with Multiple Repetitions
How to Construct a Set Out of List Items in Python
Python List Comprehension - Want to Avoid Repeated Evaluation
Anaconda/Conda - Install a Specific Package Version
How to Write Strategy Pattern in Python Differently Than Example in Wikipedia
How to Pip or Easy_Install Tkinter on Windows
Can a Decorator of an Instance Method Access the Class
Serving Dynamically Generated Zip Archives in Django
Do You Use the "Global" Statement in Python