Tkinter Treeview 如何用鼠标正确选择多个项目 [英] Tkinter Treeview how to correctly select multiple items with the mouse

查看:42
本文介绍了Tkinter Treeview 如何用鼠标正确选择多个项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用鼠标来选择和取消选择多个项目.我有它的工作,但是当用户快速移动鼠标时会出现问题.当鼠标快速移动时,一些项目会被跳过并且根本没有被选中.我一定是走错了路.

I'm trying to use the mouse to select and deselect multiple items. I have it working sort of but there is a problem when the user moves the mouse to fast. When the mouse is moved fast some items are skipped and are not selected at all. I must be going about this the wrong way.

更新 1:我决定使用我自己的选择系统,但我得到了与上面相同的结果.当鼠标快速移动时,某些项目会被跳过,因此它们没有添加正确的颜色标签并保持不变.如果鼠标缓慢移动,所有项目都会被正确选择.下面是新代码和问题的另一个图像.

Update 1: I decided to use my own selecting system, but I get the same results as above. Some items are skipped when the mouse is moved to fast and therefore they don't get the correct color tag added and remain unchanged. If the mouse is moved slowly all items get selected correctly. Below is the new code and another Image of the problem.

更新 2:我已经解决了这个问题并发布了工作代码,只是为了完整性和将来帮助其他人.我最终使用了自己的选择系统,而不是内置的选择系统.

Update 2: I have solved this issue and posted the working code just for completeness and to help others in the future. I ended up using my own selection selecting system instead of the built in one.

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()

第二次尝试:

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 中测试和工作

Tested and works in Windows and Linux

更新:我已经更新了代码,在 Windows 和 Linux 中一切正常,尽管在 Windows 中蓝色透明窗口在从右到左调整大小时会抖动,从左到右很好.在 Linux 中,不知道为什么会发生抖动?如果有人可以让我知道下面的代码是否在 MacOS 中也能运行,那也很棒.

UPDATE: I have updated the code and everything works in Windows and Linux, although in Windows the blue transparent window is jittery when sizing from right to left, left to right is fine. In Linux the jitters don't happen anyone know why? If someone could let me know if the code below works in MacOS that would be great also.

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont


class Treeview(ttk.Treeview):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)

        self.root = parent.winfo_toplevel()
        self.selected_items = []
        self.origin_x = \
            self.origin_y = \
            self.active_item = \
            self.origin_item = None

        sw = self.select_window = tk.Toplevel(self.root)
        sw.wait_visibility(self.root)
        sw.withdraw()
        sw.config(bg='#00aaff')
        sw.overrideredirect(True)
        sw.wm_attributes('-alpha', 0.3)
        sw.wm_attributes("-topmost", True)

        self.font = tkfont.nametofont('TkTextFont')
        self.style = parent.style
        self.linespace = self.font.metrics('linespace') + 5

        self.bind('<Escape>', self.tags_reset)
        self.bind('<Button-1>', self.button_press)
        self.bind('<ButtonRelease-1>', self.button_release)

    def fixed_map(self, option):
        return [elm for elm in self.style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected")]

    def tag_add(self, tags, item):
        self.tags_update('add', tags, item)

    def tag_remove(self, tags, item=None):
        self.tags_update('remove', tags, item)

    def tag_replace(self, old, new, item=None):
        for item in (item,) if item else self.tag_has(old):
            self.tags_update('add', new, item)
            self.tags_update('remove', old, item)

    def tags_reset(self, _=None):
        self.tag_remove(('selected', '_selected'))
        self.tag_replace('selected_odd', 'odd')
        self.tag_replace('selected_even', 'even')

    def tags_update(self, opt, tags, item):
        def get_items(node):
            items.append(node)
            for node in self.get_children(node):
                get_items(node)

        if not tags:
            return
        elif isinstance(tags, str):
            tags = (tags,)

        if not item:
            items = []
            for child in self.get_children():
                get_items(child)
        else:
            items = (item,)

        for item in items:
            _tags = list(self.item(item, 'tags'))
            for _tag in tags:
                if opt == 'add':
                    if _tag not in _tags:
                        _tags.append(_tag)
                elif opt == 'remove':
                    if _tag in _tags:
                        _tags.pop(_tags.index(_tag))
            self.item(item, tags=_tags)

    def button_press(self, event):
        self.origin_x, self.origin_y = event.x, event.y
        item = self.origin_item = self.active_item = self.identify('item', event.x, event.y)

        sw = self.select_window
        sw.geometry('0x0+0+0')
        sw.deiconify()

        self.bind('<Motion>', self.set_selected)

        if not item:
            if not event.state & 1 << 2:
                self.tags_reset()
            return

        if event.state & 1 << 2:
            if self.tag_has('odd', item):
                self.tag_add('selected', item)
                self.tag_replace('odd', 'selected_odd', item)
            elif self.tag_has('even', item):
                self.tag_add('selected', item)
                self.tag_replace('even', 'selected_even', item)
            elif self.tag_has('selected_odd', item):
                self.tag_replace('selected_odd', 'odd', item)
            elif self.tag_has('selected_even', item):
                self.tag_replace('selected_even', 'even', item)
        else:
            self.tags_reset()
            self.tag_add('selected', item)
            if self.tag_has('odd', item):
                self.tag_replace('odd', 'selected_odd', item)
            elif self.tag_has('even', item):
                self.tag_replace('even', 'selected_even', item)

    def button_release(self, _):
        self.select_window.withdraw()
        self.unbind('<Motion>')

        for item in self.selected_items:
            if self.tag_has('odd', item) or self.tag_has('even', item):
                self.tag_remove(('selected', '_selected'), item)
            else:
                self.tag_replace('_selected', 'selected', item)

    def get_selected(self):
        return sorted(self.tag_has('selected_odd') + self.tag_has('selected_even'))

    def set_selected(self, event):
        def selected_items():
            items = []
            window_y = int(self.root.geometry().rsplit('+', 1)[-1])
            titlebar_height = self.root.winfo_rooty() - window_y
            sw = self.select_window
            start = sw.winfo_rooty() - titlebar_height - window_y
            end = start + sw.winfo_height()

            while start < end:
                start += 1
                node = self.identify('item', event.x, start)
                if not node or node in items:
                    continue
                items.append(node)

            return sorted(items)

        def set_row_colors():
            items = self.selected_items = selected_items()

            for item in items:
                if self.tag_has('selected', item):
                    if item == self.origin_item:
                        continue

                    if self.tag_has('selected_odd', item):
                        self.tag_replace('selected_odd', 'odd', item)
                    elif self.tag_has('selected_even', item):
                        self.tag_replace('selected_even', 'even', item)

                elif self.tag_has('odd', item):
                    self.tag_replace('odd', 'selected_odd', item)
                elif self.tag_has('even', item):
                    self.tag_replace('even', 'selected_even', item)

                self.tag_add('_selected', item)

            for item in self.tag_has('_selected'):
                if item not in items:
                    self.tag_remove('_selected', item)
                    if self.tag_has('odd', item):
                        self.tag_replace('odd', 'selected_odd', item)
                    elif self.tag_has('even', item):
                        self.tag_replace('even', 'selected_even', item)
                    elif self.tag_has('selected_odd', item):
                        self.tag_replace('selected_odd', 'odd', item)
                    elif self.tag_has('selected_even', item):
                        self.tag_replace('selected_even', 'even', item)

        root_x = self.root.winfo_rootx()
        if event.x < self.origin_x:
            width = self.origin_x - event.x
            coord_x = root_x + event.x
        else:
            width = event.x - self.origin_x
            coord_x = root_x + self.origin_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.origin_y:
            height = self.origin_y - event.y
            coord_y = root_y + event.y
        else:
            height = event.y - self.origin_y
            coord_y = root_y + self.origin_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 + self.linespace:
            height -= (root_y - self.winfo_pointery() + self.linespace)
            coord_y = root_y + self.linespace
            if height < 0:
                height = self.winfo_rooty() + self.origin_y

        set_row_colors()
        self.select_window.geometry(f'{width}x{height}+{coord_x}+{coord_y}')


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        def print_selected_items(_):
            print(tv.get_selected())
            return 'break'

        style = self.style = ttk.Style()

        tv = Treeview(self)
        style.map("Treeview", foreground=tv.fixed_map("foreground"), background=tv.fixed_map("background"))

        tv.heading('#0', text='Name')
        tv.tag_configure('odd', background='#ffffff')
        tv.tag_configure('even', background='#aaaaaa')
        tv.tag_configure('selected_odd', background='#b0eab2')
        tv.tag_configure('selected_even', background='#25a625')

        color_tag = 'odd'
        for idx in range(0, 4):
            # Populating the tree with test data.
            color_tag = 'even' if color_tag == 'odd' else 'odd'
            tv.insert('', idx, f'{idx}', text=f'Item {idx+1}', open=1, tags=(color_tag,))
            color_tag = 'even' if color_tag == 'odd' else 'odd'
            iid = f'{idx}_{0}'
            tv.insert(f'{idx}', '0', iid, text=f'Menu {idx+1}', open=1, tags=(color_tag,))
            for i in range(0, 5):
                color_tag = 'even' if color_tag == 'odd' else 'odd'
                tv.insert(iid, i, f'{iid}_{i}', text=f'Sub item {i+1}', tags=(color_tag,))

            color_tag = 'even' if color_tag == 'odd' else 'odd'
            tv.insert(iid, 5, f'{iid}_{5}', text=f'Another Menu {idx+1}', open=1, tags=(color_tag,))
            iid = f'{iid}_{5}'
            for i in range(0, 3):
                color_tag = 'even' if color_tag == 'odd' else 'odd'
                tv.insert(iid, i, f'{iid}_{i}', text=f'Sub item {i+1}', tags=(color_tag,))

        self.title('Treeview Demo')
        self.geometry('275x650+3000+250')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        tv.config(selectmode="none")
        tv.grid(sticky='NSEW')

        button = ttk.Button(self, text='Get Selected Items')
        button.grid()
        button.bind('<Button-1>', print_selected_items)


def main():
    app = App()
    app.mainloop()


if __name__ == '__main__':
    main()

这篇关于Tkinter Treeview 如何用鼠标正确选择多个项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆