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.

enter image description here

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.

Asked By: Noob

||

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()
Answered By: Bryan Oakley
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.