Я пытаюсь использовать мышь для выбора и отмены выбора нескольких элементов. У меня это работает вроде как, но есть проблема, когда пользователь быстро перемещает мышь. Когда мышь перемещается быстро, некоторые элементы пропускаются и вообще не выбираются. Должно быть, я поступаю об этом неправильно.
Обновление 1: Я решил использовать свою собственную систему выбора, но получаю те же результаты, что и выше. Некоторые элементы пропускаются при быстром перемещении мыши, и поэтому они не получают правильную цветовую метку и остаются без изменений. Если мышь перемещается медленно, все элементы выбираются правильно. Ниже приведен новый код и другое изображение проблемы.
Обновление 2: Я решил эту проблему и опубликовал рабочий код только для полноты и помощи другим в будущем. Я закончил тем, что использовал свою собственную систему выбора выбора вместо встроенной.
![Sample Image](https://i.stack.imgur.com/qPM0q.png)
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title('Treeview Demo')
self.geometry('300x650')
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
tv = self.tv = ttk.Treeview(self)
tv.heading('#0', text='Name')
# Populate tree with test data.
for idx in range(0, 4):
tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', tags='TkTextFont', open=1)
iid = f'!{idx}_!{idx}'
tv.insert(f'!{idx}', '0', iid, text=f'Python {idx+1}', tags='TkTextFont', open=1)
for i in range(0, 5):
tv.insert(iid, f'{i}', f'{iid}_!{i}', text=f'Sub item {i+1}', tags='TkTextFont')
tv.grid(sticky='NSEW')
self.active_item = None
def motion(_):
x, y = tv.winfo_pointerxy()
item = tv.identify('item', x - tv.winfo_rootx(), y - tv.winfo_rooty())
if not item or item == self.active_item:
return
if not self.active_item:
self.active_item = item
tv.selection_toggle(item)
self.active_item = item
def escape(_):
tv.selection_remove(tv.selection())
def button_press(_):
self.bind('<Motion>', motion)
def button_release(_):
self.unbind('<Motion>')
self.active_item = None
self.bind('<Escape>', escape)
self.bind('<Button-1>', button_press)
self.bind('<ButtonRelease-1>', button_release)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
Вторая попытка:
![Sample Image 2](https://i.stack.imgur.com/FUXlL.png)
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title('Treeview Demo')
self.geometry('700x650')
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
tv = self.tv = ttk.Treeview(self)
tv.heading('#0', text='Name')
tv.tag_configure('odd', background='#aaaaaa')
tv.tag_configure('even', background='#ffffff')
tv.tag_configure('selected_odd', background='#25a625')
tv.tag_configure('selected_even', background='#b0eab2')
tag = 'odd'
# Populate tree with test data.
for idx in range(0, 4):
tag = 'even' if tag == 'odd' else 'odd'
tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', open=1, tags=(tag,))
tag = 'even' if tag == 'odd' else 'odd'
iid = f'!{idx}_!{idx}'
tv.insert(f'!{idx}', '0', iid, text=f'Python {idx+1}', open=1, tags=(tag,))
for i in range(0, 5):
tag = 'even' if tag == 'odd' else 'odd'
tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(tag,))
tv.config(selectmode="none")
tv.grid(sticky='NSEW')
dw = tk.Toplevel()
dw.overrideredirect(True)
dw.wait_visibility(self)
dw.wm_attributes('-alpha', 0.2)
dw.wm_attributes("-topmost", True)
dw.config(bg='#00aaff')
dw.withdraw()
self.selected = False
self.active_item = None
def motion(event):
x, y = self.winfo_pointerxy()
width = event.x-self.anchor_x
height = event.y-self.anchor_y
if width < 0:
coord_x = event.x+self.winfo_rootx()
width = self.anchor_x - event.x
else:
coord_x = self.anchor_x+self.winfo_rootx()
if coord_x+width > self.winfo_rootx()+self.winfo_width():
width -= (coord_x+width)-(self.winfo_rootx()+self.winfo_width())
elif x < self.winfo_rootx():
width -= (self.winfo_rootx() - x)
coord_x = self.winfo_rootx()
if height < 0:
coord_y = event.y+self.winfo_rooty()
height = self.anchor_y - event.y
else:
coord_y = self.anchor_y+self.winfo_rooty()
if coord_y+height > self.winfo_rooty()+self.winfo_height():
height -= (coord_y+height)-(self.winfo_rooty()+self.winfo_height())
elif y < self.winfo_rooty():
height -= (self.winfo_rooty() - y)
coord_y = self.winfo_rooty()
dw.geometry(f'{width}x{height}+{coord_x}+{coord_y}')
item = tv.identify('item', coord_x, coord_y-40)
if not item or item == self.active_item:
self.active_item = None
return
self.active_item = item
tags = list(tv.item(item, 'tags'))
if 'odd' in tags:
tags.pop(tags.index('odd'))
tags.append('selected_odd')
if 'even' in tags:
tags.pop(tags.index('even'))
tags.append('selected_even')
tv .item(item, tags=tags)
def escape(_=None):
for item in tv.tag_has('selected_odd'):
tags = list(tv.item(item, 'tags'))
tags.pop(tags.index('selected_odd'))
tags.append('odd')
tv.item(item, tags=tags)
for item in tv.tag_has('selected_even'):
tags = list(tv.item(item, 'tags'))
tags.pop(tags.index('selected_even'))
tags.append('even')
tv.item(item, tags=tags)
def button_press(event):
if self.selected and not event.state & 1 << 2:
escape()
self.selected = False
dw.deiconify()
self.anchor_item = tv.identify('item', event.x, event.y-40)
self.anchor_x, self.anchor_y = event.x, event.y
self.bind('<Motion>', motion)
self.selected = True
def button_release(event):
dw.withdraw()
dw.geometry('0x0+0+0')
self.unbind('<Motion>')
self.bind('<Escape>', escape)
self.bind('<Button-1>', button_press)
self.bind('<ButtonRelease-1>', button_release)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
Рабочий пример:
Протестировано и работает в Windows и Linux
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
class App(tk.Tk):
def __init__(self):
super().__init__()
def tag_replace(item, old, new):
if tv.tag_has(old, item):
tags_remove(item, old)
tags_add(item, new)
def tags_add(item, tags):
if isinstance(tags, str):
tags = (tags,)
_tags = list(tv.item(item)['tags'])
_tags = [] if not _tags else _tags
for _tag in tags:
if _tag not in _tags:
_tags.append(_tag)
tv.item(item, tags=_tags)
def tags_reset(_=None):
tags_replace_all('selected_odd', 'odd')
tags_replace_all('selected_even', 'even')
def tags_remove(item, tags):
if isinstance(tags, str):
tags = (tags,)
_tags = list(tv.item(item, 'tags'))
for _tag in tags:
if _tag in _tags:
_tags.pop(_tags.index(_tag))
tv.item(item, tags=_tags)
def tags_remove_all(*tags):
def do_remove(node):
tags_remove(node, tags)
for node in tv.get_children(node):
do_remove(node)
for child in tv.get_children():
do_remove(child)
def tags_replace_all(old, new):
for item in tv.tag_has(old):
tag_replace(item, old, new)
def escape(_):
tags_remove_all('selected', '_selected')
tags_reset()
def fixed_map(option):
return [elm for elm in style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected")]
def button_press(event):
self.selected = []
self.anchor_x, self.anchor_y = event.x, event.y
item = self.anchor_item = self.active_item = tv.identify('item', event.x, event.y)
if not item:
return
if event.state & 1 << 2:
tags_add(item, ('selected', ))
if tv.tag_has('odd', item):
tag_replace(item, 'odd', 'selected_odd')
elif tv.tag_has('selected_odd', item):
tag_replace(item, 'selected_odd', 'odd')
elif tv.tag_has('even', item):
tag_replace(item, 'even', 'selected_even')
elif tv.tag_has('selected_even', item):
tag_replace(item, 'selected_even', 'even')
else:
tags_remove_all('selected', '_selected')
tags_reset()
tags_add(item, 'selected')
if tv.tag_has('odd', item):
tag_replace(item, 'odd', 'selected_odd')
elif tv.tag_has('even', item):
tag_replace(item, 'even', 'selected_even')
dw.geometry('0x0+0+0')
dw.deiconify()
self.bind('<Motion>', selection_window)
def button_release(_):
dw.withdraw()
self.unbind('<Motion>')
tags_replace_all('_selected', 'selected')
def set_row_colors(event):
def get_selected():
selected_items = []
window_y = int(self.geometry().rsplit('+', 1)[-1])
tbh = tv.winfo_rooty() - window_y
start = dw.winfo_rooty() - tbh - window_y
end = dw.winfo_rooty() + dw.winfo_height() - tbh - window_y
while start < end:
start += 1
node = tv.identify('item', event.x, start)
if not node or node in selected_items:
continue
selected_items.append(node)
return sorted(selected_items)
items = get_selected()
self.unbind('<Motion>')
for item in items:
if tv.tag_has('selected', item):
if item == self.anchor_item:
continue
tags_add(item, '_selected')
if tv.tag_has('selected_odd', item):
tag_replace(item, 'selected_odd', 'odd')
elif tv.tag_has('selected_even', item):
tag_replace(item, 'selected_even', 'even')
else:
if tv.tag_has('odd', item):
tag_replace(item, 'odd', 'selected_odd')
tags_add(item, '_selected')
self.active_item = item
elif tv.tag_has('even', item):
tag_replace(item, 'even', 'selected_even')
tags_add(item, '_selected')
self.active_item = item
for item in tv.tag_has('_selected'):
if item not in items:
tags_remove(item, '_selected')
if tv.tag_has('odd', item):
tag_replace(item, 'odd', 'selected_odd')
elif tv.tag_has('even', item):
tag_replace(item, 'even', 'selected_even')
elif tv.tag_has('selected_odd', item):
tag_replace(item, 'selected_odd', 'odd')
elif tv.tag_has('selected_even', item):
tag_replace(item, 'selected_even', 'even')
_selected = tv.tag_has('_selected')
self.bind('<Motion>', selection_window)
def selection_window(event):
root_x = self.winfo_rootx()
if event.x < self.anchor_x:
width = self.anchor_x - event.x
coord_x = root_x + event.x
else:
width = event.x - self.anchor_x
coord_x = root_x + self.anchor_x
if coord_x+width > root_x+self.winfo_width():
width -= (coord_x+width)-(root_x+self.winfo_width())
elif self.winfo_pointerx() < root_x:
width -= (root_x - self.winfo_pointerx())
coord_x = root_x
root_y = self.winfo_rooty()
if event.y < self.anchor_y:
height = self.anchor_y - event.y
coord_y = root_y + event.y
else:
height = event.y - self.anchor_y
coord_y = root_y + self.anchor_y
if coord_y+height > root_y+self.winfo_height():
height -= (coord_y+height)-(root_y+self.winfo_height())
elif self.winfo_pointery() < root_y + linespace:
height -= (root_y - self.winfo_pointery() + linespace)
coord_y = root_y + linespace
if height < 0:
height = tv.winfo_rooty() + self.anchor_y
dw.geometry(f'{width}x{height}+{coord_x}+{coord_y}')
dw.update_idletasks()
set_row_colors(event)
self.title('Treeview Demo')
self.geometry('275x650+2000+100')
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
f = ttk.Frame(self)
f.rowconfigure(0, weight=1)
f.columnconfigure(0, weight=1)
f.grid(sticky='nsew')
tv = self.tv = ttk.Treeview(f)
tv.heading('#0', text='Name')
tv.tag_configure('odd', background='#aaaaaa')
tv.tag_configure('even', background='#ffffff')
tv.tag_configure('selected_odd', background='#25a625')
tv.tag_configure('selected_even', background='#b0eab2')
tag = 'odd'
for idx in range(0, 4):
# Populating the tree with test data.
tag = 'even' if tag == 'odd' else 'odd'
tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', open=1, tags=(tag,))
tag = 'even' if tag == 'odd' else 'odd'
iid = f'!{idx}_!{0}'
tv.insert(f'!{idx}', '0', iid, text=f'Menu {idx+1}', open=1, tags=(tag,))
for i in range(0, 5):
tag = 'even' if tag == 'odd' else 'odd'
tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(tag,))
tag = 'even' if tag == 'odd' else 'odd'
tv.insert(iid, 5, f'{iid}_!{5}', text=f'Another Menu {idx+1}', open=1, tags=(tag,))
iid = f'{iid}_!{5}'
for i in range(0, 3):
tag = 'even' if tag == 'odd' else 'odd'
tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(tag,))
tv.config(selectmode="none")
tv.grid(sticky='NSEW')
dw = tk.Toplevel(self)
dw.overrideredirect(True)
dw.wait_visibility(self)
dw.wm_attributes('-alpha', 0.3)
dw.wm_attributes("-topmost", True)
dw.config(bg='#00aaff')
dw.withdraw()
self.active_item = ''
font = tkfont.nametofont('TkTextFont')
linespace = font.metrics('linespace') + 5
style = ttk.Style()
style.map("Treeview", foreground=fixed_map("foreground"), background=fixed_map("background"))
self.bind('<Escape>', escape)
self.bind('<Button-1>', button_press)
self.bind('<ButtonRelease-1>', button_release)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()