Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] - نسخه متنی

David Ascher, Alex Martelli, Anna Ravenscroft

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


Recipe 11.11. Supporting Multiple Values per Row in a Tkinter Listbox


Credit: Brent Burley, Pedro Werneck, Eric Rose


Problem


You need a Tkinter widget that works just
like a normal Listbox but with multiple values per
row.


Solution


When you find a functional limitation in Tkinter, most often the best
solution is to build your own widget as a Python class, subclassing
an appropriate existing Tkinter widget (often
Frame, so you can easily aggregate several native
Tkinter widgets into your own compound widget) and extending and
tweaking the widget's functionality as necessary.
Rather than solving a problem for just one application, this approach
gives you a component that you can reuse in many applications. For
example, here's a way to make a multicolumn
equivalent of a Tkinter Listbox:

from Tkinter import *
class MultiListbox(Frame):
def _ _init_ _(self, master, lists):
Frame._ _init_ _(self, master)
self.lists = [ ]
for l, w in lists:
frame = Frame(self)
frame.pack(side=LEFT, expand=YES, fill=BOTH)
Label(frame, text=l, borderwidth=1, relief=RAISED).pack(fill=X)
lb = Listbox(frame, width=w, borderwidth=0, selectborderwidth=0,
relief=FLAT, exportselection=FALSE)
lb.pack(expand=YES, fill=BOTH)
self.lists.append(lb)
lb.bind('<B1-Motion>', lambda e, s=self: s._select(e.y))
lb.bind('<Button-1>', lambda e, s=self: s._select(e.y))
lb.bind('<Leave>', lambda e: 'break')
lb.bind('<B2-Motion>', lambda e, s=self: s._b2motion(e.x, e.y))
lb.bind('<Button-2>', lambda e, s=self: s._button2(e.x, e.y))
frame = Frame(self)
frame.pack(side=LEFT, fill=Y)
Label(frame, borderwidth=1, relief=RAISED).pack(fill=X)
sb = Scrollbar(frame, orient=VERTICAL, command=self._scroll)
sb.pack(expand=YES, fill=Y)
self.lists[0]['yscrollcommand'] = sb.set
def _select(self, y):
row = self.lists[0].nearest(y)
self.selection_clear(0, END)
self.selection_set(row)
return 'break'
def _button2(self, x, y):
for l in self.lists:
l.scan_mark(x, y)
return 'break'
def _b2motion(self, x, y):
for l in self.lists
l.scan_dragto(x, y)
return 'break'
def _scroll(self, *args):
for l in self.lists:
apply(l.yview, args)
return 'break'
def curselection(self):
return self.lists[0].curselection( )
def delete(self, first, last=None):
for l in self.lists:
l.delete(first, last)
def get(self, first, last=None):
result = [ ]
for l in self.lists:
result.append(l.get(first,last))
if last: return apply(map, [None] + result)
return result
def index(self, index):
self.lists[0].index(index)
def insert(self, index, *elements):
for e in elements:
i = 0
for l in self.lists:
l.insert(index, e[i])
i = i + 1
def size(self):
return self.lists[0].size( )
def see(self, index):
for l in self.lists:
l.see(index)
def selection_anchor(self, index):
for l in self.lists:
l.selection_anchor(index)
def selection_clear(self, first, last=None):
for l in self.lists:
l.selection_clear(first, last)
def selection_includes(self, index):
return self.lists[0].selection_includes(index)
def selection_set(self, first, last=None):
for l in self.lists:
l.selection_set(first, last)
if _ _name_ _ == '_ _main_ _':
tk = Tk( )
Label(tk, text='MultiListbox').pack( )
mlb = MultiListbox(tk, (('Subject', 40), ('Sender', 20), ('Date', 10)))
for i in range(1000):
mlb.insert(END,
('Important Message: %d' % i, 'John Doe', '10/10/%04d' % (1900+i)))
mlb.pack(expand=YES, fill=BOTH)
tk.mainloop( )


Discussion


This recipe shows a compound widget that gangs multiple Tk
Listbox widgets to a single scrollbar to achieve a
simple multicolumn scrolled listbox. Most of the
Listbox API is mirrored, to make the widget act
like normal Listbox, but with multiple values per
row. The resulting widget is lightweight, fast, and easy to use. The
main drawback is that only text is supported, which is a fundamental
limitation of the underlying Listbox widget.

In this recipe's implementation, only single
selection is allowed, but the same idea could be extended to multiple
selection. User-resizable columns and auto-sorting by clicking on the
column label should also be possible. Auto-scrolling while dragging
Button-1 was disabled because it broke the synchronization between
the lists. However, scrolling with Button-2 works fine. Mice with
scroll wheels appear to behave in different ways depending on the
platform. For example, while things appear to work fine with the
preceding code on some platforms (such as Windows/XP), on other
platforms using X11 (such as Linux), I've observed
that mouse scroll wheel events correspond to Button-4 and Button-5,
so you could deal with them just by adding at the end of the
for loop in method _ _init_ _
the following two statements:

lb.bind('<Button-4>', lambda e, s=self: s._scroll(SCROLL, -1, UNITS))
lb.bind('<Button-5>', lambda e, s=self: s._scroll(SCROLL, +1, UNITS))

This addition should be innocuous on platforms such as Windows/XP.
You should check this issue on all platforms on which you need to
support mouse scroll wheels.

If you need to support sorting by column-header clicking, you can
obtain the hook needed for that functionality with a fairly modest
change to this recipe's code. Specifically, within
the for loop in method _ _init_
_
, you can change the current start:

        for l, w in lists:
frame = Frame(self)
frame.pack(side=LEFT, expand=YES, fill=BOTH)
Label(frame, text=l, borderwidth=1, relief=RAISED).pack(fill=X)

to the following richer code:

        for l, w, sort_command in lists:
frame = Frame(self)
frame.pack(side=LEFT, expand=YES, fill=BOTH)
Button(frame, text=l, borderwidth=1, relief=RAISED,
command=sort_command).pack(fill=X)

To take advantage of this hook, you then need to pass as the
lists' argument, rather than one
tuple of pairs, a list of three tuples, the third item of each tuple
being an object callable with no arguments to perform the appropriate
kind of sorting. In my applications, I've generally
found this specific refinement to be more trouble than
it's worth, but I'm presenting it
anyway (although not in the actual
"Solution" of this recipe!) just in
case your applications differ in this respect.
Maybe sorting by column header clicking is something
that's absolutely invaluable to you.

One note about the implementation: in the
MultiListbox._ _init_ _ method, several
lambda forms are used as the callable second
arguments (callbacks) of the bind method calls on
the contained Listbox widgets. This approach is
traditional, but if you share the widespread dislike for
lambda, you should know that
lambda is never truly necessary. In this case, the
easiest way to avoid the lambdas is to redefine
all the relevant methods (_select,
_button2, etc.) as taking two formal arguments
(self, e) and extract the data
they need from argument e. Then in the
bind calls, you can simply pass the bound
self._select method, and so on.


See Also


Information about Tkinter can be obtained from a variety of sources,
such as Pythonware's An Introduction to
Tkinter
, by Fredrik Lundh (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.

/ 394