Reputation: 20076
I'm having trouble modifying an existing MultiListBox
implementation to allow me to select multiple items using the shift
key. The constructor of the class is:
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, selectmode=EXTENDED)
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
Even though I set the selectmode=EXTENDED
, there is no extended selection.
What function would I need to implement for multilistbox in order to support extended selection?
You can see the entire implementation of multilistbox here.
Upvotes: 1
Views: 3177
Reputation:
Unfortunately it looks like you have to roll your own, at least as far as I can tell. The code below is not complete as it will do thing like accept the same selection twice, but it's all I have time for. There is no down-arrow code but that it essentially the same as the up-arrow code. I assume you want a button press to display or use the items selected. Below they are just printed after Tkinter is closed.
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
import tkFont
import ttk
else:
import tkinter as tk ## Python 3.x
import tkinter.font as tkFont
import tkinter.ttk as ttk
'''
ttk_multicolumn_listbox2.py
Python31 includes the Tkinter Tile extension ttk.
Ttk comes with 17 widgets, 11 of which already exist in Tkinter:
Button, Checkbutton, Entry, Frame, Label, LabelFrame, Menubutton,
PanedWindow, Radiobutton, Scale and Scrollbar
he 6 new widget classes are:
Combobox, Notebook, Progressbar, Separator, Sizegrip and Treeview
For additional info see the Python31 manual:
http://gpolo.ath.cx:81/pydoc/library/ttk.html
Here the TreeView widget is configured as a multi-column listbox
with adjustable column width and column-header-click sorting.
Tested with Python 3.1.1 and Tkinter 8.5
'''
class McListBox(object):
"""use a ttk.TreeView as a multicolumn ListBox"""
def __init__(self, root):
self.root=root
self.tree = None
self._setup_widgets()
self._build_tree()
ttk.Button(self.root, text='Exit',
command=self.root.quit).grid(row=20)
self.selected_offsets=[]
def _setup_widgets(self):
container = ttk.Frame(self.root)
container.grid(sticky="nsew")
# create a treeview with dual scrollbars
self.tree = ttk.Treeview(columns=car_header, show="headings",
selectmode="extended")
vsb = ttk.Scrollbar(orient="vertical",
command=self.tree.yview)
hsb = ttk.Scrollbar(orient="horizontal",
command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set,
xscrollcommand=hsb.set)
self.tree.grid(column=0, row=0, sticky='nsew', in_=container)
vsb.grid(column=1, row=0, sticky='ns', in_=container)
hsb.grid(column=0, row=1, sticky='ew', in_=container)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=1)
self.tree.bind("<Shift-Up>", self.shift_up_arrow)
self.tree.bind("<Up>", self.up_arrow)
def _build_tree(self):
for col in car_header:
self.tree.heading(col, text=col.title())
# adjust the column's width to the header string
self.tree.column(col,
width=tkFont.Font().measure(col.title()))
self.item_id=[]
for item in car_list:
self.item_id.append(self.tree.insert('', 'end', values=item)) ## store tkinter id
# adjust column's width if necessary to fit each value
for ix, val in enumerate(item):
col_w = tkFont.Font().measure(val)
if self.tree.column(car_header[ix],width=None)<col_w:
self.tree.column(car_header[ix], width=col_w)
## set the focus in the middle for testing
new_id=self.item_id[5]
self.tree.focus_set() ## sets focus to the treeview
self.tree.selection_set((new_id, new_id)) ## updates background
self.tree.focus(new_id) ## sets new id as focus
def shift_up_arrow(self, event):
""" gets selected item(s) and stores them in a list
"""
id_selected=self.tree.focus()
for offset, id in enumerate(self.item_id):
## print offset, id
if id==id_selected:
self.selected_offsets.append(offset)## save selection
print offset, car_list[offset]
## change background color
## you could also give each row a unique tag and
## tag_configure the selected row's tag
self.tree.delete(id_selected)
self.tree.insert('', offset, id_selected,
values=car_list[offset], tags=("ABC",))
self.tree.tag_configure("ABC", background='yellow')
new_id=self.item_id[0]
if offset > 0:
new_id=self.item_id[offset]
self.tree.focus_set() ## sets focus to the treeview
self.tree.selection_set((new_id, new_id)) ## updates background
self.tree.focus(new_id) ## sets new id as focus
return
def up_arrow(self, event):
id_selected=self.tree.focus()
for offset, id in enumerate(self.item_id):
if id==id_selected and offset > 0:
new_id=self.item_id[offset]
self.tree.focus_set() ## sets focus to the treeview
self.tree.selection_set((new_id, new_id)) ## updates background
self.tree.focus(new_id) ## sets new id as focus
# the test data ...
car_header = ['car', 'repair']
car_list = [
('Hyundai', 'brakes') ,
('Honda', 'light') ,
('Lexus', 'battery') ,
('Benz', 'wiper') ,
('Ford', 'tire') ,
('Chevy', 'air') ,
('Chrysler', 'piston') ,
('Toyota', 'brake pedal') ,
('BMW', 'seat'),
('Audi', 'starter'),
('Fiat', 'shocks'),
('Porsche', 'fuel pump')
]
root = tk.Tk()
root.wm_title("multicolumn ListBox")
mc_listbox = McListBox(root)
root.mainloop()
##
## print selected items
print "Selections %s" % ("-"*50)
for offset in mc_listbox.selected_offsets:
print offset, car_list[offset]
Upvotes: 1
Reputation:
I prefer vegaseat's method using ttk.TreeView because all of the columns are linked to the row, which makes things easier. This slight modification of his example prints the id when any column in the row is selected. You do want to test/print the id's as you will have to convert the id to the row number/list offset, i.e. capture the return from self.tree.insert. Will look into multiple selection more tonight. And will try using keys instead of button.
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
import tkFont
import ttk
else:
import tkinter as tk ## Python 3.x
import tkinter.font as tkFont
import tkinter.ttk as ttk
'''
ttk_multicolumn_listbox2.py
Python31 includes the Tkinter Tile extension ttk.
Ttk comes with 17 widgets, 11 of which already exist in Tkinter:
Button, Checkbutton, Entry, Frame, Label, LabelFrame, Menubutton,
PanedWindow, Radiobutton, Scale and Scrollbar
he 6 new widget classes are:
Combobox, Notebook, Progressbar, Separator, Sizegrip and Treeview
Here the TreeView widget is configured as a multi-column listbox
with adjustable column width and column-header-click sorting.
Tested with Python 3.1.1 and Tkinter 8.5
'''
class McListBox(object):
"""use a ttk.TreeView as a multicolumn ListBox"""
def __init__(self, root):
self.root=root
self.tree = None
self._setup_widgets()
self._build_tree()
ttk.Button(self.root, text='Exit',
command=self.root.quit).grid(row=20)
def _setup_widgets(self):
# create a treeview with dual scrollbars
container = ttk.Frame(self.root)
container.grid(sticky="nsew")
self.tree = ttk.Treeview(columns=car_header, show="headings")
vsb = ttk.Scrollbar(orient="vertical",
command=self.tree.yview)
hsb = ttk.Scrollbar(orient="horizontal",
command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set,
xscrollcommand=hsb.set)
self.tree.grid(column=0, row=0, sticky='nsew', in_=container)
vsb.grid(column=1, row=0, sticky='ns', in_=container)
hsb.grid(column=0, row=1, sticky='ew', in_=container)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=1)
self.tree.bind("<Button1-Motion>", self.selection)
def _build_tree(self):
for col in car_header:
self.tree.heading(col, text=col.title())
# adjust the column's width to the header string
self.tree.column(col,
width=tkFont.Font().measure(col.title()))
for item in car_list:
print self.tree.insert('', 'end', values=item)
# adjust column's width if necessary to fit each value
for ix, val in enumerate(item):
col_w = tkFont.Font().measure(val)
if self.tree.column(car_header[ix],width=None)<col_w:
self.tree.column(car_header[ix], width=col_w)
def selection(self, event):
""" gets selected item id and prints them
"""
id = self.tree.identify_row(event.y)
print "selection", id
# the test data ...
car_header = ['car', 'repair']
car_list = [
('Hyundai', 'brakes') ,
('Honda', 'light') ,
('Lexus', 'battery') ,
('Benz', 'wiper') ,
('Ford', 'tire') ,
('Chevy', 'air') ,
('Chrysler', 'piston') ,
('Toyota', 'brake pedal') ,
('BMW', 'seat'),
('Audi', 'starter'),
('Fiat', 'shocks'),
('Porsche', 'fuel pump')
]
root = tk.Tk()
root.wm_title("multicolumn ListBox")
mc_listbox = McListBox(root)
root.mainloop()
Upvotes: 2