Recipe 11.12. Copying Geometry Methods and Options Between Tkinter Widgets
Credit: Pedro Werneck
Problem
You want to create new Tkinter compound
widgets, not by inheriting from Frame and packing
other widgets inside, but rather by setting geometry methods and
options from other widget to another.
Solution
Here is an example of a compound widget built by this approach:
from Tkinter import *
class LabeledEntry(Entry):
"" An Entry widget with an attached Label ""
def _ _init_ _(self, master=None, **kw):
ekw = { } # Entry options dictionary
fkw = { } # Frame options dictionary
lkw = {'name':'label'} # Label options dictionary
skw = {'padx':0, 'pady':0, 'fill':'x', # Geometry manager opts dict
'side':'left'}
fmove = ('name',) # Opts to move to the Frame dict
lmove = ('text', 'textvariable',
'anchor','bitmap', 'image') # Opts to move to the Label dict
smove = ('side', 'padx', 'pady', # Opts to move to the Geometry
'fill') # manager dictionary
# dispatch each option towards the appropriate component
for k, v in kw:
if k in fmove: fkw[k] = v
elif k in lmove: lkw[k] = v
elif k in smove: skw[k] = v
else: ekw[k] = v
# make all components with the accumulated options
self.body = Frame(master, **fkw)
self.label = Label(self.body, **lkw)
self.label.pack(side='left', fill=skw['fill'],
padx=skw['padx'], pady=skw['pady'])
Entry._ _init_ _(self, self.body, **ekw)
self.pack(side=skw['side'], fill=skw['fill'],
padx=skw['padx'], pady=skw['pady'])
methods = (Pack._ _dict_ _.keys( ) +
# Set Frame geometry methods to self
Grid._ _dict_ _.keys( ) +
Place._ _dict_ _.keys( ))
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.body, m))
Discussion
Here is an example of use of this LabeledEntry
widget, presented, as usual, with a guard of if _ _name_ _
== '_ _main_ _' so we can make it part of the module
containing the class and have it run when the module is executed as a
"main script":
if _ _name_ _ == '_ _main_ _':The usual approach to defining new compound Tkinter widgets is to
root = Tk( )
le1 = LabeledEntry(root, name='label1', text='Label 1: ',
width=5, relief=SUNKEN, bg='white', padx=3)
le2 = LabeledEntry(root, name='label2', text='Label 2: ',
relief=SUNKEN, bg='red', padx=3)
le3 = LabeledEntry(root, name='label3', text='Label 3: ',
width=40, relief=SUNKEN, bg='yellow', padx=3)
le1.pack(expand=1, fill=X)
le2.pack(expand=1, fill=X)
le3.pack(expand=1, fill=X)
root.mainloop( )
inherit from Frame and pack your component widgets
inside. While simple and habitual, that approach has a few problems.
In particular, you need to invent, design, document, and implement
additional methods or options to access the component
widgets' attributes from outside of the compound
widget class. Using another alternative (which I've
often seen done, but it's still a practice that is
not advisable at all!), you can violate
encapsulation and Demeter's Law by having other code
access the component widgets directly. If you do violate
encapsulation, you'll pay for it in the not-so-long
run, when you find a need to tweak your compound widget and discover
that you can't do it without breaking lots of code
that depends on the compound widget's internal
structure. Those consequences are bad enough when you own all of the
code in question, but it's worse if you have
"published" your widget and
other people's code depends on
it.This recipe shows it doesn't have to be that bad, by
elaborating upon an idea I first saw used in the
ScrolledText widget, which deserves to be more
widely exposed. Instead of inheriting from Frame,
you inherit from the "main" widget
of your new compound widget. Then, you create a
Frame widget to be used as a body, pretty much
like in the more usual approach. Then, and here comes the interesting
novelty, you create dicts for each component
widget you contain and move to those
dictionaries the respective options that pertain to component
widgets.The novelty continues after you've packed the
"main" widget: at that point, you
can reset said widget's geometry methods to the base
Frame attributes (meaning, in this case, methods),
so that accessing the object methods will in fact access the inner
base Frame geometry methods. This transparent,
seamless delegation by juggling bound methods is uniquely Pythonic
and is part of what makes this recipe so novel and interesting!The main advantage of this recipe's approach is that
you can create your widget with options to all slave widgets inside
it in a single line, just like any other widget, instead of doing any
further w.configure or
w['option'] calls or accesses to set all details
exactly the way you want them. To be honest, there
is a potential disadvantage, too: in this
recipe's approach, it's hard to
handle options with the same name on different component widgets.
However, sometimes you can handle them by
renaming options: if two separate widgets need a
'foo' option that's also of
interest to the "main" widget, for
example, use, 'upper_foo' and
'lower_foo' variants and rename them appropriately
(with yet another auxiliary dictionary) at the same time
you're dispatching them to the appropriate
dictionary of component-widget options. You can't
sensibly keep doing that "forever",
as the number of component widgets competing for the same option
grows without bounds: if that happens, revert to the good old
tried-and-true approach. But for nine out of ten compound widgets you
find yourself programming, you'll find this
recipe's approach to be an interesting alternative
to the usual, traditional approach to compound-widget programming.
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.