如何在 Tkinter 列表框中启用多行选择?
How to enable selection of multiple rows in Tkinter listboxes?
我正在使用下面的 class 绘制带有 select 可用元素的可滚动表格。当用户单击其中一项时,我需要突出显示整行(有效)。但目前您一次只能 select 一行。如何修改下面的代码以允许一次 select 离子(和突出显示)多行?
from Tkinter import *
import Tkinter as tk
class MultiListbox_fuse(Frame):
def __init__(self,master,lists):
Frame.__init__(self,master,borderwidth=1,relief=SUNKEN)
self.lists = []
self.columns=[]
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, height=30, borderwidth=0, selectborderwidth=0,
relief=FLAT, exportselection=FALSE, selectmode=MULTIPLE)
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))
lb.bind('<Button-4>', lambda e, s=self: s._scroll(SCROLL, 1, PAGES))
lb.bind('<Button-5>', lambda e, s=self: s._scroll(SCROLL, -1, PAGES))
lb.bind("<MouseWheel>", self.OnMouseWheel)
#self.add(frame)
Label(master, borderwidth=1, relief=FLAT).pack(fill=X)
sb = Scrollbar(master, orient=VERTICAL, command=self._scroll,borderwidth=1)
sb.pack(fill=Y,side=RIGHT,expand=NO)
for l in self.lists:
l['yscrollcommand']=sb.set
#self.add(frame)
self.pack(expand=YES,fill=BOTH)
self.sortedBy=-1
self.previousWheel=0
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if state==16:self.selection_clear(0, END)
self.selection_set(row)
## print self.curselection()
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 clickon(self,e):
self._sortBy(self.columns.index(e.widget['text']))
def _sortBy(self, column):
""" Sort by a given column. """
if column == self.sortedBy:
direction = -1 * self.direction
else:
direction = 1
elements = self.get(0, END)
self.delete(0, END)
elements.sort(lambda x, y: self._sortAssist(column, direction, x, y))
self.insert(END, *elements)
self.sortedBy = column
self.direction = direction
def _sortAssist(self, column, direction, x, y):
c = cmp(x[column], y[column])
if c:
return direction * c
else:
return direction * cmp(x, y)
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)
def OnMouseWheel(self, event):
for l in self.lists:
l.yview("scroll", event.delta,"units")
# this prevents default bindings from firing, which
# would end up scrolling the widget twice
return "break"
root = Tk()
root.minsize(width=650, height=580)
root.maxsize(width=650, height=580)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
x = (w/2) - 300
y = (h/2) - 250
root.geometry('%dx%d+%d+%d' % (650, 550, x, y-80))
root.wm_title("Results displayed")
tabelka = MultiListbox_fuse(root, (('Costam1',8), ('Costam2',8)))
tabelka.pack()
for a in range(1,100):
tabelka.insert(END, (str("abc1"), str("def2")))
root.mainloop()
这段代码不太好理解,几乎没有注释。绑定语句中的 labbdas 并不容易。
当我尝试使用滚动条而不是使用鼠标滚轮时出现错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\qwerty\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
File "C:\Users\qwerty\Documents\Python\test.py", line 58, in _scroll
apply(l.yview, args)
NameError: name 'apply' is not defined
这是一次尝试:
# Add binding in __init()__ with the other bindings
lb.bind('<Shift-Button-1>', self.more)
# Create the function to handle <Shift-Button-1>
def more(self, event):
first = self.lists[0].curselection()[0] # The previous selection
last = self.lists[0].nearest(event.y) # Current mouse click
self.selection_set(first, last=last) # Set selection
这标记了所有列表中的选择。我没有考虑到第一选择不存在的可能性等等。
在您的 _select
方法中,您在添加被点击的选项之前清除了选择:
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if state==16:self.selection_clear(0, END) # This clears the selection
self.selection_set(row)
## print self.curselection()
return 'break'
如果您想要从选择中删除该项目(如果它已经存在),请执行以下操作:
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if row in self.curselection(): # Check if the row already is in the selection
self.selection_clear(row) # If it is, remove it
else:
self.selection_set(row) # If it isn't, add it
return 'break'
因为您还将此方法绑定到 <B1-Motion>
这在移动同一行时确实有点奇怪,因为它不断地添加和删除同一行。您可以使用类似下面的方法来解决这个问题,它会检查您是否在拖动时移动到另一行。
self.current_row = None
for l,w in lists:
...
lb.bind('<B1-Motion>', lambda e, s=self: s.drag(e.y))
...
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if row in self.curselection():
self.selection_clear(row)
else:
self.selection_set(row)
self.current_row = row
return 'break'
def drag(self, y):
row = self.lists[0].nearest(y)
if not row == self.current_row:
self._select(y)
我正在使用下面的 class 绘制带有 select 可用元素的可滚动表格。当用户单击其中一项时,我需要突出显示整行(有效)。但目前您一次只能 select 一行。如何修改下面的代码以允许一次 select 离子(和突出显示)多行?
from Tkinter import *
import Tkinter as tk
class MultiListbox_fuse(Frame):
def __init__(self,master,lists):
Frame.__init__(self,master,borderwidth=1,relief=SUNKEN)
self.lists = []
self.columns=[]
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, height=30, borderwidth=0, selectborderwidth=0,
relief=FLAT, exportselection=FALSE, selectmode=MULTIPLE)
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))
lb.bind('<Button-4>', lambda e, s=self: s._scroll(SCROLL, 1, PAGES))
lb.bind('<Button-5>', lambda e, s=self: s._scroll(SCROLL, -1, PAGES))
lb.bind("<MouseWheel>", self.OnMouseWheel)
#self.add(frame)
Label(master, borderwidth=1, relief=FLAT).pack(fill=X)
sb = Scrollbar(master, orient=VERTICAL, command=self._scroll,borderwidth=1)
sb.pack(fill=Y,side=RIGHT,expand=NO)
for l in self.lists:
l['yscrollcommand']=sb.set
#self.add(frame)
self.pack(expand=YES,fill=BOTH)
self.sortedBy=-1
self.previousWheel=0
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if state==16:self.selection_clear(0, END)
self.selection_set(row)
## print self.curselection()
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 clickon(self,e):
self._sortBy(self.columns.index(e.widget['text']))
def _sortBy(self, column):
""" Sort by a given column. """
if column == self.sortedBy:
direction = -1 * self.direction
else:
direction = 1
elements = self.get(0, END)
self.delete(0, END)
elements.sort(lambda x, y: self._sortAssist(column, direction, x, y))
self.insert(END, *elements)
self.sortedBy = column
self.direction = direction
def _sortAssist(self, column, direction, x, y):
c = cmp(x[column], y[column])
if c:
return direction * c
else:
return direction * cmp(x, y)
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)
def OnMouseWheel(self, event):
for l in self.lists:
l.yview("scroll", event.delta,"units")
# this prevents default bindings from firing, which
# would end up scrolling the widget twice
return "break"
root = Tk()
root.minsize(width=650, height=580)
root.maxsize(width=650, height=580)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
x = (w/2) - 300
y = (h/2) - 250
root.geometry('%dx%d+%d+%d' % (650, 550, x, y-80))
root.wm_title("Results displayed")
tabelka = MultiListbox_fuse(root, (('Costam1',8), ('Costam2',8)))
tabelka.pack()
for a in range(1,100):
tabelka.insert(END, (str("abc1"), str("def2")))
root.mainloop()
这段代码不太好理解,几乎没有注释。绑定语句中的 labbdas 并不容易。
当我尝试使用滚动条而不是使用鼠标滚轮时出现错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\qwerty\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
File "C:\Users\qwerty\Documents\Python\test.py", line 58, in _scroll
apply(l.yview, args)
NameError: name 'apply' is not defined
这是一次尝试:
# Add binding in __init()__ with the other bindings
lb.bind('<Shift-Button-1>', self.more)
# Create the function to handle <Shift-Button-1>
def more(self, event):
first = self.lists[0].curselection()[0] # The previous selection
last = self.lists[0].nearest(event.y) # Current mouse click
self.selection_set(first, last=last) # Set selection
这标记了所有列表中的选择。我没有考虑到第一选择不存在的可能性等等。
在您的 _select
方法中,您在添加被点击的选项之前清除了选择:
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if state==16:self.selection_clear(0, END) # This clears the selection
self.selection_set(row)
## print self.curselection()
return 'break'
如果您想要从选择中删除该项目(如果它已经存在),请执行以下操作:
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if row in self.curselection(): # Check if the row already is in the selection
self.selection_clear(row) # If it is, remove it
else:
self.selection_set(row) # If it isn't, add it
return 'break'
因为您还将此方法绑定到 <B1-Motion>
这在移动同一行时确实有点奇怪,因为它不断地添加和删除同一行。您可以使用类似下面的方法来解决这个问题,它会检查您是否在拖动时移动到另一行。
self.current_row = None
for l,w in lists:
...
lb.bind('<B1-Motion>', lambda e, s=self: s.drag(e.y))
...
def _select(self, y,state=16):
row = self.lists[0].nearest(y)
if row in self.curselection():
self.selection_clear(row)
else:
self.selection_set(row)
self.current_row = row
return 'break'
def drag(self, y):
row = self.lists[0].nearest(y)
if not row == self.current_row:
self._select(y)