Tkinter: share single Frame accross multiple tabs (or dynamically reparent)
Question:
I have tkinter.Notebook
that contains multiple tabs.
I have tkinter.Frame
"run box" with set of controls, that I want share across several tabs. Specifically Test info
and Test list
should have it.
Currently I duplicate entire "run box" into each tab that needs it. And it works somewhat fine, but feels excessive and not right, because code behind "run box" controls is almost identical.
Ideally I want this "run box" to be single instance and dynamically shown on active tab. Can anyone advise how to detach"run box" from one tab and re-attach to different tab? I would do that on Notebook
switch event.
Answers:
A widget can only be in one place at a time. There is no way to share a frame among multiple notebook tabs without removing it from one tab and adding it to another when the active tab changes.
If that’s what you want to do, you can create the "runbox" once, and then add a container to each tab to act as a placeholder. You can add a binding to the <Visibility>
event to add the runbox to the placeholder when it becomes visible.
This might cause some flickering as you switch tabs.
The key to this working is to use the in_
parameter to pack
which lets you pack one widget inside another widget. It also uses the lift
mechanism to make sure that the "runbox" is higher in z-order than the placeholder. The runbox is re-packed whenever its visibility changes, by binding to the <Visibility>
event.
Here’s an example. It creates a single instance of Runbox
and swaps it between two tabs.
import tkinter as tk
from tkinter import ttk
class RunParameters():
"""Parameters to be shared by all tabs"""
def __init__(self):
self.magic_num = tk.IntVar(value=0)
self.iter_count = tk.IntVar(value=1)
self.duration = tk.IntVar(value=0)
self.result = "fail"
self.repeat = tk.BooleanVar(value=True)
class Runbox(tk.Frame):
def __init__(self, parent, run_parameters):
super().__init__(parent)
radiobuttons = []
for i in (range(8)):
rb = tk.Radiobutton(
self, text=i, variable=run_parameters.magic_num, value=i
)
radiobuttons.append(rb)
itercount_spinbox = tk.Spinbox(
self, from_=1, to=100,
textvariable=run_parameters.iter_count
)
duration_spinbox = tk.Spinbox(
self, from_=0, to=100,
textvariable=run_parameters.duration
)
radiobuttons[0].grid(row=0, column=0)
radiobuttons[1].grid(row=0, column=1)
radiobuttons[2].grid(row=0, column=2)
radiobuttons[3].grid(row=0, column=3)
radiobuttons[4].grid(row=1, column=0)
radiobuttons[5].grid(row=1, column=1)
radiobuttons[6].grid(row=1, column=2)
radiobuttons[7].grid(row=1, column=3)
itercount_spinbox.grid(row=3, column=0, columnspan=4)
duration_spinbox.grid(row=4, column=0, columnspan=4)
class TestInfo(tk.Frame):
def __init__(self, parent, runbox):
super().__init__(parent)
self.runbox = runbox
self._container = tk.Frame(self)
label = tk.Label(self, text="Something unique to the Test Info tab")
self._container.pack(side="top", fill="x")
label.pack(side="bottom", fill="both", expand=True)
self.bind("<Visibility>", self.on_vis)
def on_vis(self, event):
if self.winfo_viewable():
self.runbox.pack(in_=self._container, fill="both", expand=True)
self.runbox.lift()
class TestList(tk.Frame):
def __init__(self, parent, runbox):
super().__init__(parent)
self.runbox = runbox
self._container = tk.Frame(self)
listbox = tk.Listbox(self)
listbox.insert("end", "Test 1", "Test 2", "Test 3")
self._container.pack(side="top", fill="x")
listbox.pack(side="bottom", fill="both", expand=True)
self.bind("<Visibility>", self.on_vis)
def on_vis(self, event):
if self.winfo_viewable():
self.runbox.pack(in_=self._container, fill="both", expand=True)
self.runbox.lift()
class Trace32(tk.Frame):
pass
class TMEConfig(tk.Frame):
pass
root = tk.Tk()
params = RunParameters()
nb = ttk.Notebook(root)
nb.pack(fill="both", expand=True)
runbox = Runbox(nb, params)
test_info = TestInfo(nb, runbox = runbox)
test_list = TestList(nb, runbox = runbox)
trace32 = Trace32(nb)
tme_config = TMEConfig(nb)
nb.add(test_info, text="Test Info")
nb.add(test_list, text="Test List")
nb.add(trace32, text="Trace32")
nb.add(tme_config, text="TME config")
root.mainloop()
I have tkinter.Notebook
that contains multiple tabs.
I have tkinter.Frame
"run box" with set of controls, that I want share across several tabs. Specifically Test info
and Test list
should have it.
Currently I duplicate entire "run box" into each tab that needs it. And it works somewhat fine, but feels excessive and not right, because code behind "run box" controls is almost identical.
Ideally I want this "run box" to be single instance and dynamically shown on active tab. Can anyone advise how to detach"run box" from one tab and re-attach to different tab? I would do that on Notebook
switch event.
A widget can only be in one place at a time. There is no way to share a frame among multiple notebook tabs without removing it from one tab and adding it to another when the active tab changes.
If that’s what you want to do, you can create the "runbox" once, and then add a container to each tab to act as a placeholder. You can add a binding to the <Visibility>
event to add the runbox to the placeholder when it becomes visible.
This might cause some flickering as you switch tabs.
The key to this working is to use the in_
parameter to pack
which lets you pack one widget inside another widget. It also uses the lift
mechanism to make sure that the "runbox" is higher in z-order than the placeholder. The runbox is re-packed whenever its visibility changes, by binding to the <Visibility>
event.
Here’s an example. It creates a single instance of Runbox
and swaps it between two tabs.
import tkinter as tk
from tkinter import ttk
class RunParameters():
"""Parameters to be shared by all tabs"""
def __init__(self):
self.magic_num = tk.IntVar(value=0)
self.iter_count = tk.IntVar(value=1)
self.duration = tk.IntVar(value=0)
self.result = "fail"
self.repeat = tk.BooleanVar(value=True)
class Runbox(tk.Frame):
def __init__(self, parent, run_parameters):
super().__init__(parent)
radiobuttons = []
for i in (range(8)):
rb = tk.Radiobutton(
self, text=i, variable=run_parameters.magic_num, value=i
)
radiobuttons.append(rb)
itercount_spinbox = tk.Spinbox(
self, from_=1, to=100,
textvariable=run_parameters.iter_count
)
duration_spinbox = tk.Spinbox(
self, from_=0, to=100,
textvariable=run_parameters.duration
)
radiobuttons[0].grid(row=0, column=0)
radiobuttons[1].grid(row=0, column=1)
radiobuttons[2].grid(row=0, column=2)
radiobuttons[3].grid(row=0, column=3)
radiobuttons[4].grid(row=1, column=0)
radiobuttons[5].grid(row=1, column=1)
radiobuttons[6].grid(row=1, column=2)
radiobuttons[7].grid(row=1, column=3)
itercount_spinbox.grid(row=3, column=0, columnspan=4)
duration_spinbox.grid(row=4, column=0, columnspan=4)
class TestInfo(tk.Frame):
def __init__(self, parent, runbox):
super().__init__(parent)
self.runbox = runbox
self._container = tk.Frame(self)
label = tk.Label(self, text="Something unique to the Test Info tab")
self._container.pack(side="top", fill="x")
label.pack(side="bottom", fill="both", expand=True)
self.bind("<Visibility>", self.on_vis)
def on_vis(self, event):
if self.winfo_viewable():
self.runbox.pack(in_=self._container, fill="both", expand=True)
self.runbox.lift()
class TestList(tk.Frame):
def __init__(self, parent, runbox):
super().__init__(parent)
self.runbox = runbox
self._container = tk.Frame(self)
listbox = tk.Listbox(self)
listbox.insert("end", "Test 1", "Test 2", "Test 3")
self._container.pack(side="top", fill="x")
listbox.pack(side="bottom", fill="both", expand=True)
self.bind("<Visibility>", self.on_vis)
def on_vis(self, event):
if self.winfo_viewable():
self.runbox.pack(in_=self._container, fill="both", expand=True)
self.runbox.lift()
class Trace32(tk.Frame):
pass
class TMEConfig(tk.Frame):
pass
root = tk.Tk()
params = RunParameters()
nb = ttk.Notebook(root)
nb.pack(fill="both", expand=True)
runbox = Runbox(nb, params)
test_info = TestInfo(nb, runbox = runbox)
test_list = TestList(nb, runbox = runbox)
trace32 = Trace32(nb)
tme_config = TMEConfig(nb)
nb.add(test_info, text="Test Info")
nb.add(test_list, text="Test List")
nb.add(trace32, text="Trace32")
nb.add(tme_config, text="TME config")
root.mainloop()