I am building a UI based on a Tkinter "notebook" object with several tabs. Whenever the user clicks a tab I want to update the info on the frame according to the current state of the underlying object before displaying it to the user.
This does not seem to work:
The following demo builds a notebook with three pages. The routine load_page is bound to the NotebookTabChanged event and should be called every time a tab is clicked. This routine should open a message window identifying the tab that fired it, and updates the info on the page (with current time):
import tkinter as tk
import tkinter.messagebox as msg
from tkinter import ttk
import time
class DemoPage(tk.Frame): # A notebook page
def __init__(self, container, name: str = ""):
# call the superclass init to build the page
tk.Frame.__init__(self, container, highlightbackground="blue", highlightthickness=1)
self._name = name # The name of the page (appears on the page tab)
container.add(self, text=name) # add the notebook tab & associate w. page
# event binding
container.bind("<<NotebookTabChanged>>", func=self.load_page) # load current table
# insert texts
self.const_label = tk.Label(self, text="This is " self._name)
self.const_label.pack(side='top', fill='x')
self.var_label = tk.Label(self, text="Updated " time.ctime())
self.var_label.pack(side='top', fill='x')
def load_page(self, event):
self.var_label["text"] = "Updated " time.ctime()
msg.showinfo(self._name, "Tab clicked")
if __name__ == "__main__":
tk_win = tk.Tk() # establish the root tkinter window
tk_win.title("Master Sequence")
tk_win.geometry("600x400")
# initiate UI
nb = ttk.Notebook(tk_win) # establish notebook
nb.pack(expand=True, fill='both') # widget geometry: Notebook will fill the root window
page1 = DemoPage(nb,"Page 1") # initiate tab object in the notebook
page2 = DemoPage(nb,"Page 2") # initiate tab object in the notebook
page3 = DemoPage(nb,"Page 3") # initiate tab object in the notebook
# wait for user input
tk_win.mainloop()
Note that the event is triggered automatically for page 3 when the app is first run (without clicking its tab). Thereafter, every time any tab is clicked, the event is triggered for tab 3, and the info in the frame is updated in page 3 only
What am I doing wrong?
CodePudding user response:
NotebookTabChanged
is not only click
event - it can be generated also using code - and when you create new tabs then it also generate this event. You should first create all tabs and later bind event. it may need to use tk_win.update()
.
But this event work different then you expect - it is assigned to Notebook
, not to tabs - so you replace previus bind
with new bind
and finally it runs last assgned function which change time in last tab.
You should assing one function which checks which tab is active and change time in this tab. But it can be simpler if you keep tabs on list - so you can get index of active tab and use it to access tab on list.
def on_tab_change(event):
index = nb.index(nb.select())
print('index:', index)
pages[index].load_page()
pages = [
DemoPage(nb, "Page 1"),
DemoPage(nb, "Page 2"),
DemoPage(nb, "Page 3"),
]
nb.bind("<<NotebookTabChanged>>", on_tab_change)
Full working code.
import tkinter as tk
import tkinter.messagebox as msg
from tkinter import ttk
import time
class DemoPage(tk.Frame): # A notebook page
def __init__(self, container, name: str = ""):
# call the superclass init to build the page
tk.Frame.__init__(self, container, highlightbackground="blue", highlightthickness=1)
self._name = name # The name of the page (appears on the page tab)
container.add(self, text=name) # add the notebook tab & associate w. page
# insert texts
self.const_label = tk.Label(self, text="This is " self._name)
self.const_label.pack(side='top', fill='x')
self.var_label = tk.Label(self, text="Updated " time.ctime())
self.var_label.pack(side='top', fill='x')
def load_page(self):
self.var_label["text"] = "Updated " time.ctime()
msg.showinfo(self._name, "Tab clicked")
def on_tab_change(event):
index = nb.index(nb.select())
print('index:', index)
pages[index].load_page()
if __name__ == "__main__":
tk_win = tk.Tk() # establish the root tkinter window
tk_win.title("Master Sequence")
tk_win.geometry("600x400")
# initiate UI
nb = ttk.Notebook(tk_win) # establish notebook
nb.pack(expand=True, fill='both') # widget geometry: Notebook will fill the root window
pages = [
DemoPage(nb, "Page 1"),
DemoPage(nb, "Page 2"),
DemoPage(nb, "Page 3"),
]
tk_win.update() # update all tabs before assigning `event`
# assign only one function
nb.bind("<<NotebookTabChanged>>", on_tab_change)
tk_win.mainloop()
CodePudding user response:
Thank you furas.
Indeed, it seems that NotebookTabChanged does not operate in the way I need. A much simpler solution to deliver the same functionality would be to change the line:
container.bind("<<NotebookTabChanged>>", func=self.load_page)
to:
self.bind("<Expose>", func=self.load_page)
When the user clicks the relevant tab the "DemoPage" frame is exposed and fires off the event.