Recipe 11.13. Implementing a Tabbed Notebook for Tkinter
Credit: Iuri Wickert
Problem
You have some Tkinter applications, each
with a single top-level window, and want to organize them as panels
in a tabbed notebook with minimal changes to your original
applications' source code.
Solution
A simple widget class can implement a notebook with all the features
we need, including all possible orientations and the ability to add
and switch frames (panels) at will:
from Tkinter import *Just save this code as a notebook.py module,
class notebook(object):
def _ _init_ _(self, master, side=LEFT):
self.active_fr = None
self.count = 0
self.choice = IntVar(0)
if side in (TOP, BOTTOM): self.side = LEFT
else: self.side = TOP
self.rb_fr = Frame(master, borderwidth=2, relief=RIDGE)
self.rb_fr.pack(side=side, fill=BOTH)
self.screen_fr = Frame(master, borderwidth=2, relief=RIDGE)
self.screen_fr.pack(fill=BOTH)
def _ _call_ _(self):
return self.screen_fr
def add_screen(self, fr, title):
b = Radiobutton(self.rb_fr, text=title, indicatoron=0,
variable=self.choice,
value=self.count, command=lambda: self.display(fr))
b.pack(fill=BOTH, side=self.side)
if not self.active_fr:
fr.pack(fill=BOTH, expand=1)
self.active_fr = fr
self.count += 1
def display(self, fr):
self.active_fr.forget( )
fr.pack(fill=BOTH, expand=1)
self.active_fr = fr
somewhere on your Python sys.path, and you can
import and use it in your apps.
Discussion
The simplest way to show how this notebook class
works is with a simple demonstration program:
from Tkinter import *Tkinter is a simple GUI toolkit, easy to use but notoriously
from notebook import *
# make a toplevel with a notebook in it, with tabs on the left:
root = Tk( )
nb = notebook(root, LEFT)
# make a few diverse frames (panels), each using the NB as 'master':
f1 = Frame(nb( ))
b1 = Button(f1, text="Button 1")
e1 = Entry(f1)
# pack your widgets in the frame before adding the frame to the
# notebook, do NOT pack the frame itself!
b1.pack(fill=BOTH, expand=1)
e1.pack(fill=BOTH, expand=1)
f2 = Frame(nb( ))
b2 = Button(f2, text='Button 2')
b3 = Button(f2, text='Beep 2', command=Tk.bell)
b2.pack(fill=BOTH, expand=1)
b3.pack(fill=BOTH, expand=1)
f3 = Frame(nb( ))
# add the frames as notebook 'screens' and run this GUI app
nb.add_screen(f1, "Screen 1")
nb.add_screen(f2, "Screen 2")
nb.add_screen(f3, "dummy")
root.mainloop( )
feature-poor when compared to more advanced toolkits. And yet,
sometimes advanced features are not all that difficult to add! I
wondered how I could use a tabbed appearance, also known as a
notebook, to organize various pages of an
application, or various related applications, simply and elegantly. I
discovered that simulating a notebook widget by using standard
Tkinter frames and radio buttons was not only possible, but also
quite simple and effective.Tk has some "odd", and somewhat
unknown, corners, which make the whole task a snap. The
indicatoron option on a radio button reverts the
radio button default appearance back to the normal button
looka rectangle, which may not be a perfect-looking tab but is
plenty good enough for me. Each Tkinter frame has a
forget method, which allows easy and fast swapping
of "screens" (notebook panels,
application frames) within the single "screen
frame" of the notebook object.To convert any existing Tkinter app, based on a single top-level
window, to run inside a notebook panel, all you need to do is to
change the application master frame's root, which is
generally a top-level widget (an instance of
Tkinter's Tk class), to the one
provided by the notebook object when you call it. (The three
occurrences of nb( ) in the example code show how
to go about it.)The notebook implementations in other toolkits often have advanced
features such as the ability to exclude (remove) some frames as well
as adding others. I have not found this kind of thing to be
necessary, and so I have taken no trouble in this recipe to make it
possible: all references to the external frames are kept implicitly
in lambda closures, without any obvious way to
remove them. If you think you need the ability to remove frames, you
might consider an alternative architecture: keep the
frames' references in a list, indexed by the binding
variable of the radio buttons (i.e., the choice
attribute of each radio button). Doing so lets you
destroy a
"frame" and its associated radio
button in a reasonably clean way.
See Also
Information about Tkinter can be obtained from a variety of sources,
such as Fredrik Lundh, An Introduction to
Tkinter (PythonWare: http://www.pythonware.com/library), New
Mexico Tech's Tkinter
Reference (http://www.nmt.edu/tcc/help/lang/python/docsl),
Python in a Nutshell, and various other
books.