Tkinter Treeview如何用鼠标正确选择多个项目

2023-12-24

我正在尝试使用鼠标选择和取消选择多个项目。我可以正常工作,但是当用户快速移动鼠标时会出现问题。当鼠标快速移动时,某些项目会被跳过并且根本不会被选择。我一定是以错误的方式处理这件事的。

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

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

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 上运行

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

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()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Tkinter Treeview如何用鼠标正确选择多个项目 的相关文章

随机推荐

  • 从 python 中的函数返回左值

    抱歉 我是 Python 新手 虽然这似乎是一个非常基本的问题 但在询问听众之前 我做了尽职调查 试图避免真正愚蠢的问题 我试图找出从函数返回左值的正确习惯用法 假设我有一个包含 64 个对象的容器 并且我希望能够返回对这些对象的引用 cl
  • Wordpress 中的 PhP 联系表单

    所以我买了一个主题 它有一个简单的 PHP 联系表单 允许用户互相发送消息 但是当发件人填写表单并提交时 电子邮件来自我的默认 WordPress 管理员电子邮件 而不是用户输入的电子邮件地址表格 因此 如果收件人尝试回复他们的电子邮件 邮
  • Google Apps 脚本 - 从模板创建的每个文档中都出现“需要授权”

    我有一个 Google 文档 它有一个自定义 UI 菜单 其中包含一个选项 可以将新页面添加到文档并用一些数据填充它 我使用此文档作为模板 通过 Web 应用程序为单个用户创建许多其他文档 因此 我的用户来到我的 web 应用程序 单击生成
  • Boost::bind 和 Boost Phoenix::bind 有什么区别?

    Boost bind 和 Boost Phoenix bind 有什么区别 phoenix bind就好像lambda bind返回一个表达式模板的函数 该模板记录它必须调用给定的函数 它们被设计为分别与phoenix 和lambda 一起
  • 使用 jQuery 克隆

    我有一个允许用户推荐朋友的表格 我想在 朋友电子邮件 和 朋友姓名 这两个输入字段下方添加一个链接 该链接将克隆 朋友框 一次并将其添加到下面 另外 好友邮箱 和 好友姓名 的name属性为name friend email 0 and n
  • 如何将张量转换为字符串

    我正在测试 tf data 这是现在批量提供数据的推荐方法 但是 我正在加载自定义数据集 因此我需要 str 格式的文件名 但是当创建 tf Dataset from tensor slices 时 它们是 Tensor 对象 def lo
  • 根据列聚合过滤选定的列

    我希望仅选择唯一值少于 3 个的列 我可以通过生成布尔掩码pl all n unique lt 3 但我不知道是否可以通过 Polars API 使用该掩码 目前 我正在通过 python 解决这个问题 有更惯用的方法吗 import po
  • Logcat 消息在短时间内消失

    有时 eclipse logcat 消息在关闭应用程序一小段时间后就会消失 大多不会那么短 如何让它永远不会自动消失 编辑 我的设备仍然连接 以及如何将其设置为即使在设备断开连接并在下次启动之前清除后也显示日志 这也发生在我身上 设备已连接
  • Perl 正则表达式 'e' (eval) 修饰符带 s///

    我在理解 e 正则表达式修饰符的简单用法时遇到了一些困难 my var testing In this string we are var the e modifier s w 1 ee print 返回 在此字符串中 我们正在测试 e 修
  • 如何使用 DropDownList 的 SelectedIndexChanged 事件

    我有两个DropDownList在我的网络表单中 当我在第一个下拉列表中选择一个值时 我希望在第二个下拉列表中自动选择一个相关值 这就是我目前所拥有的 table tr td td tr table
  • 合并两个 Sass 文件

    我想即时将两个 sass 文件合并在一起 例如 如果我有 some class color white and some class background color red 那么最终结果将是 some class color white
  • 无法上传 > ~2GB 到 Google Cloud Storage

    追踪如下 相关的Python片段 bucket get bucket location bucket blob bucket blob location path blob upload from filename source path
  • 缩放用户控件中的裁剪像素

    我开发了一个用户控件 用户控件就像一个放大镜 用户控件有一个图像按钮 它显示逐像素裁剪的图像 StorageFile storageFile await StorageFile GetFileFromApplicationUriAsync
  • ASP.NET SimpleMembershipProvider 自动迁移

    因此 我尝试在新的 MVC 4 项目中使用自动迁移 但不知何故它不起作用 我关注了这篇博文 http blog longle net 2012 09 25 seeding users and roles with mvc4 simpleme
  • github 提交中的虚拟文件

    我正在尝试将一些更改推送到我的 github 存储库 但推送失败 因为 git 认为存储库中存在一个大文件 remote Error code 38865a38ccad9b1d9f394c35344906e6 remote warning
  • 使用 -viewWithTag: 当多个视图具有相同的 .tag 时,返回什么 UIView?

    假设我有 4 个 UIView 在 IB 中制作 所有标签属性 2 当我看到以下内容时 UIView thisView UIView self view viewWithTag 2 由于多个 UIView 具有相同的 tag 值 检索该 U
  • 如何使用窗口句柄更改窗口所有者

    我想制作一个 NET 表单作为另一个外部应用程序 与 NET 无关的纯 Win32 的最顶层表单 因此它保持在 Win32App 之上 但不在运行的其余应用程序之上 我有Win32App的句柄 由Win32App本身提供 并且我已经尝试过W
  • 使用“new”时未初始化的 std::complex 构造函数

    在分析我的程序时 我意识到 10 的代码都花在了愚蠢的事情上std complex
  • 使用gnuplot绘制进程树

    类似于这个问题here https stackoverflow com questions 20406346 how to plot tree graph web data on gnuplot我想绘制给定 PID 的进程树 我应该能够将该
  • Tkinter Treeview如何用鼠标正确选择多个项目

    我正在尝试使用鼠标选择和取消选择多个项目 我可以正常工作 但是当用户快速移动鼠标时会出现问题 当鼠标快速移动时 某些项目会被跳过并且根本不会被选择 我一定是以错误的方式处理这件事的 更新1 我决定使用自己的选择系统 但得到的结果与上面相同