Tkinter Treeview как правильно выбрать несколько элементов мышью - PullRequest
0 голосов
/ 21 апреля 2020

Я пытаюсь использовать мышь для выбора и отмены выбора нескольких элементов. У меня это работает вроде как, но есть проблема, когда пользователь быстро перемещает мышь. Когда мышь перемещается быстро, некоторые элементы пропускаются и вообще не выбираются. Должно быть, я поступаю об этом неправильно.

Обновление 1: Я решил использовать свою собственную систему выбора, но получаю те же результаты, что и выше. Некоторые элементы пропускаются при быстром перемещении мыши, и поэтому они не получают правильную цветовую метку и остаются без изменений. Если мышь перемещается медленно, все элементы выбираются правильно. Ниже приведен новый код и другое изображение проблемы.

Обновление 2: Я решил эту проблему и опубликовал рабочий код только для полноты и помощи другим в будущем. Я закончил тем, что использовал свою собственную систему выбора выбора вместо встроенной.

Sample Image

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

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...