Sample code usable Tkinter listbox

A

Alf P. Steinbach

In case Someone Else(TM) may need this.

This code is just how it currently looks, what I needed for my code, so it's not
a full-fledged or even tested class.

But it works.


<code language="Py3">
import tkinter as t
import tkinter.simpledialog
import tkinter.messagebox
t.askstring = tkinter.simpledialog.askstring
t.warningbox = tkinter.messagebox.showwarning

class UsableListbox( t.Frame ):
def __init__( self, parent_widget ):
t.Frame.__init__( self, parent_widget )
scrollbar = t.Scrollbar( self, orient = "vertical" )
self.lb = t.Listbox( self, yscrollcommand = scrollbar.set )
scrollbar.config( command = self.lb.yview )
scrollbar.pack( side = "right", fill = "y" )
self.lb.pack( side = "left", fill = "both", expand = 1 )

def current_index( self ):
indices = self.lb.curselection()
assert( len( indices ) <= 1 ) # TODO: about multi-selection.
return None if len( indices ) == 0 else int( indices[0] )

def current( self ):
#return self.lb.get( "active" ) # Incorrect with mousing
i = self.current_index()
return "" if i is None else self.lb.get( i )

def append( self, item ):
return self.lb.insert( "end", item )

def add_selection_event_handler( self, handler ):
"An event handler takes one argument, a Tkinter Event"
return self.lb.bind( "<<ListboxSelect>>", handler )
</code>


Cheers,

- Alf
 
R

rantingrick

In case Someone Else(TM) may need this.

This code is just how it currently looks, what I needed for my code, so it's not
a full-fledged or even tested class.

Thanks for sharing Alf,
Thats works fine "as-is" but what about inheriting from "tk.Listbox"
directly and having the "container frame" as an attribute? I prefer
this API because I hate to write the laborious
"megawidget.mainwidget.methodX()" when i could instead write
"megawidget.methodX()". What is your opinion on this?
 
A

Alf P. Steinbach

* rantingrick:
Thanks for sharing Alf,
Thats works fine "as-is" but what about inheriting from "tk.Listbox"
directly and having the "container frame" as an attribute? I prefer
this API because I hate to write the laborious
"megawidget.mainwidget.methodX()" when i could instead write
"megawidget.methodX()". What is your opinion on this?

Well the frame contains a listbox and scrollbar. And with the frame as attribute
the object that you have a direct reference to would then not be the complete
thing on screen, with respect to sizing and placement and such. I generally
don't like widgets that present stuff outside their bounding box. I guess that
could be fixed up somehow by overriding this and that, but I find it simpler to
just make the enclosing widget the widget that one has a reference to. And in a
way it's also good that it's more laborious to directly access the tkinter
listbox stuff, because what I discovered so far is that much of it requires work
arounds and fixups, i.e. that it should be wrapped in higher level methods.

I had to add some more such functionality after I posted that code.

So, current (this is untested except that it works for what I'm using it for!):


<code language="Py3">
class UsableListbox( t.Frame ):
def __init__( self, parent_widget ):
t.Frame.__init__( self, parent_widget )
scrollbar = t.Scrollbar( self, orient = "vertical" )
self.lb = t.Listbox( self, exportselection = 0, yscrollcommand =
scrollbar.set )
scrollbar.config( command = self.lb.yview )
scrollbar.pack( side = "right", fill = "y" )
self.lb.pack( side = "left", fill = "both", expand = 1 )

def current_index( self ):
indices = self.lb.curselection()
assert( len( indices ) <= 1 ) # TODO: about multi-selection.
return None if len( indices ) == 0 else int( indices[0] )

def current( self ):
#return self.lb.get( "active" ) # Incorrect with mousing
i = self.current_index()
return "" if i is None else self.lb.get( i )

def item_count( self ):
return self.lb.size()

def clear( self ):
self.lb.delete( 0, "end" )

def append( self, item ):
return self.lb.insert( "end", item )

def select_item( self, i ):
assert( 0 <= i < self.item_count() )
self.lb.selection_set( i )

def add_selection_event_handler( self, handler ):
"An event handler takes one argument, a Tkinter Event"
return self.lb.bind( "<<ListboxSelect>>", handler )
</code>


Cheers,

- Alf
 
R

rantingrick

* rantingrick:

I just hate overriding all the paths to each listbox method. How about
just overideing the Listboxe's geomerty methods once an your done?


#-- start script --#
import Tkinter as tk
from Tkconstants import *
import tkMessageBox as MB

class ScrolledList(tk.Listbox):
def __init__(self, master, **kw):
self.frame = tk.Frame(master)
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.hbar = tk.Scrollbar(self.frame, orient=HORIZONTAL)
self.block = tk.Frame(self.frame, width=18, height=18)
self.block.grid(row=1, column=1)
self.vbar = tk.Scrollbar(self.frame, orient=VERTICAL)
kw.setdefault('activestyle', 'none')
kw.setdefault('highlightthickness', 0)
if 'pack' in kw.keys() and kw.pop('pack') == 1:
self.frame.pack(fill=BOTH, expand=1)
tk.Listbox.__init__(self, self.frame, **kw)
self.grid(row=0, column=0, sticky=N+S+E+W)
self.hbar.configure(command=self.xview)
self.vbar.configure(command=self.yview)
self.config(
yscrollcommand=self.vbar.set,
xscrollcommand=self.hbar.set
)
self.hbar.grid(row=1, column=0, sticky=W+E)
self.vbar.grid(row=0, column=1, sticky=N+S)

self.pack = lambda **kw: self.frame.pack(**kw)
self.grid = lambda **kw: self.frame.grid(**kw)
self.place = lambda **kw: self.frame.place(**kw)
self.pack_config = lambda **kw: self.frame.pack_config(**kw)
self.grid_config = lambda **kw: self.frame.grid_config(**kw)
self.place_config = lambda **kw: self.frame.place_config(**kw)
self.pack_configure = lambda **kw:
self.frame.pack_config(**kw)
self.grid_configure = lambda **kw:
self.frame.grid_config(**kw)
self.place_configure = lambda **kw:
self.frame.place_config(**kw)

def gets(self):
return self.get(0, END)

def sets(self, arg):
self.delete(0, END)
try:
arg = arg.strip('\n').splitlines()
except AttributeError:
pass
if hasattr(arg, '__getitem__'):
for item in arg:
self.insert(END, str(item))
else:
raise TypeError("Scrolledlist.sets() requires a string of
iterable of strings")


if __name__ == '__main__':
root = tk.Tk()
listbox = ScrolledList(root, width=50, height=5, fg='green',
selectmode='Extended', pack=1)
#listbox.sets(1.25)
#listbox.sets('1\n2\n3\n4\n5\n\n\n')
listbox.sets(range(100))
root.mainloop()
#-- end script --#
 
A

Alf P. Steinbach

* Alf P. Steinbach:
In case Someone Else(TM) may need this.

This code is just how it currently looks, what I needed for my code, so
it's not a full-fledged or even tested class.

But it works.


That code evolved a little to cover more Tk listbox quirks (thanks to Ratingrick
for the "activestyle"):


<code>
class UsableListbox( t.Frame ):
def __init__( self, parent_widget ):
t.Frame.__init__( self, parent_widget )
scrollbar = t.Scrollbar( self, orient = "vertical" )
self.lb = t.Listbox(
self,
exportselection = 0, activestyle = "none", selectmode = "browse",
yscrollcommand = scrollbar.set
)
scrollbar.config( command = self.lb.yview )
scrollbar.pack( side = "right", fill = "y" )
self.lb.pack( side = "left", fill = "both", expand = 1 )

def current_index( self ):
indices = self.lb.curselection()
assert( len( indices ) <= 1 ) # TODO: about multi-selection.
return None if len( indices ) == 0 else int( indices[0] )

def item_at( self, i ):
assert( 0 <= i < self.item_count() )
return "" if i is None else self.lb.get( i )

def current( self ):
#return self.lb.get( "active" ) # Incorrect with mousing
return self.item_at( self.current_index() )

def item_count( self ):
return self.lb.size()

def clear( self ):
self.lb.delete( 0, "end" )

def append( self, item ):
self.lb.insert( "end", item )
return self.item_count() - 1

def scroll_into_view( self, i ):
self.lb.see( i )

def select_item( self, i ):
assert( 0 <= i < self.item_count() )
old_i = self.current_index();
self.scroll_into_view( i )
if old_i is not None and old_i == i:
return
self.lb.selection_set( i )
if old_i is not None:
self.lb.selection_clear( old_i )

def add_selection_event_handler( self, handler ):
"An event handler takes one argument, a Tkinter Event"
return self.lb.bind( "<<ListboxSelect>>", handler )
</code>


Cheers,

- Alf
 
R

rantingrick

Opps: found a few errors in that last ScrolledListbox class, try this
one...



import Tkinter as tk
from Tkconstants import *

class ScrolledList(tk.Listbox):
def __init__(self, master, **kw):
self.frame = tk.Frame(master)
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.hbar = tk.Scrollbar(self.frame, orient=HORIZONTAL)
self.block = tk.Frame(self.frame, width=18, height=18)
self.block.grid(row=1, column=1)
self.vbar = tk.Scrollbar(self.frame, orient=VERTICAL)
kw.setdefault('activestyle', 'none')
kw.setdefault('highlightthickness', 0)
kw.setdefault('pack', 0)
if kw.pop('pack') == 1:
self.frame.pack(fill=BOTH, expand=1)
tk.Listbox.__init__(self, self.frame, **kw)
self.grid(row=0, column=0, sticky=N+S+E+W)
self.hbar.configure(command=self.xview)
self.vbar.configure(command=self.yview)
self.config(
yscrollcommand=self.vbar.set,
xscrollcommand=self.hbar.set
)
self.hbar.grid(row=1, column=0, sticky=W+E)
self.vbar.grid(row=0, column=1, sticky=N+S)

self.pack = lambda **kw: self.frame.pack(**kw)
self.grid = lambda **kw: self.frame.grid(**kw)
self.place = lambda **kw: self.frame.place(**kw)
self.pack_config = lambda **kw: self.frame.pack_config(**kw)
self.grid_config = lambda **kw: self.frame.grid_config(**kw)
self.place_config = lambda **kw: self.frame.place_config(**kw)
self.pack_configure = lambda **kw:
self.frame.pack_config(**kw)
self.grid_configure = lambda **kw:
self.frame.grid_config(**kw)
self.place_configure = lambda **kw:
self.frame.place_config(**kw)

def gets(self):
return self.get(0, END)

def sets(self, arg):
self.delete(0, END)
try:
arg = arg.strip('\n').splitlines()
except AttributeError:
pass
if hasattr(arg, '__getitem__'):
for item in arg:
self.insert(END, str(item))
else:
raise TypeError("Scrolledlist.sets() requires a string or
iterable of strings")


if __name__ == '__main__':
root = tk.Tk()
listbox = ScrolledList(root, width=50, height=10, pack=1)
#listbox.sets(1.25)
#listbox.sets('1\n2\n3\n4\n5\n\n\n')
listbox.sets(range(100))
#listbox.grid(row=0, column=0)
root.mainloop()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,470
Messages
2,571,807
Members
48,797
Latest member
PeterSimpson
Top