【问题标题】:How to amend (e.g. add/remove) the values of a ttk.Treeview tag (tkinter)?如何修改(例如添加/删除)ttk.Treeview 标签(tkinter)的值?
【发布时间】:2021-12-28 07:45:20
【问题描述】:

如何修改(例如添加/删除)ttk.Treeview 中选项 tags 的值?有没有我可以使用的内置 tkinter/widget 方法?

...
columns = list(range(1,3))
tree = ttk.Treeview(root, columns=columns)

tree.insert('', 'end', iid=1, values=(1,1), tags='1')
...
  1. 如何将值“NEWTAG”添加到tags='1'tags 的值应为“1”和“NEWTAG”。
  2. 如何使用“NEWTAG”添加替换“1”? tags 值最终应该是“NEWTAG”。
  3. 如果tags 的值已经是“1”和“NEWTAG”,我该如何删除“NEWTAG”以便tags="1"?

【问题讨论】:

  • 你有没有看过tag_has IIRC 返回的 iid 列表,然后你可以使用这些 iid 来删除值
  • “值”是什么意思:“如何将值“NEWTAG”添加到....”。你是否对tagsiid 感到困惑
  • 也许你可以参考tcl-lang.org/man/tcl8.6/TkCmd/ttk_treeview.htm#M59,比如root.tk.call(tree, 'tag', 'add', 'NEWTAG', '1') ,或者root.tk.call(tree, 'tag', 'remove', 'NEWTAG, '1')
  • @CoolCloud 根据documentation,它说明了.insert() 方法的tags 选项,“您可以提供一个或多个与此项目关联的标签字符串。该值可以是单个字符串或字符串序列。"
  • @CoolCloud 你的回复是在我的问题之后。我误解了。道歉。 ;)

标签: python python-3.x tkinter treeview


【解决方案1】:

根据tcl documentationttk.Treeview 小部件确实具有从节点或节点列表中添加和删除标签的命令。但是,官方的 tkinter 包装器中没有提供这些方法;请参阅 /usr/lib/python3.8/tkinter/ttk.py 中的 Treeview 类。

基于@JasonYang 的评论和@CoolCloud 的回答,下面的测试代码说明了如何实现一个包装器来访问ttk.Treeview 小部件的tag addtag remove 命令。希望这个例子可以让 tkinter 用户受益。

测试代码:请参阅# Methods to add and remove tag(s) 部分。有关为什么使用小部件的._w 属性的说明,请阅读this question

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import tkinter as tk
import tkinter.ttk as ttk


class App(ttk.Frame):

    def __init__(self, parent=None, *args, **kwargs):
        super().__init__(parent)
        self.parent = parent
        self._create_treeview()
        self._test_tag_add_method()
        self._test_tag_remove_method()

    def _create_treeview(self):
        # Create Treeview
        self.tree = ttk.Treeview(self, column=('A', 'B'), selectmode='none',
                                 height=7)
        self.tree.grid(row=0, column=0, sticky='nsew')

        # Setup column heading
        self.tree.heading('#0', text='Point', anchor='center')
        self.tree.heading('#1', text='x', anchor='center')
        self.tree.heading('#2', text='y', anchor='center')
        # #0, #01, #02 denotes the 0, 1st, 2nd columns

        # Setup column
        self.tree.column('#0', anchor='center', width=50)
        self.tree.column('#1', anchor='center', width=30)
        self.tree.column('#2', anchor='center', width=30)

        # Insert nodes
        data = [(10, 20), (30, 40), (50, 60)]
        for n, d in enumerate(data):
            self.tree.insert('', 'end', iid='n'+str(n), text=str(n), value=d,
                             tag='Current')
        node = 'n5 xxx'
        self.tree.insert('', 'end', iid=node, text=node, value=d, tag='Current')
        print(f"Nodes with 'Current' tag: {self.tree.tag_has('Current')}")

    def _test_tag_add_method(self):
        # Add 'House' to tag of node 'n1'
        tag = 'House'
        self.tag_add(tag, items='n1')  # works but incorrect type for items
        print(f"Nodes with 'House' tag: {self.tree.tag_has(tag)}")
        self.tag_add(tag, items=['n5 xxx'])  # Correct way of submitting items
        print(f"Nodes with 'House' tag: {self.tree.tag_has(tag)}")

        # Add 'NEWTAG' to tag of all nodes
        tag = 'NEWTAG'
        self.tag_add(tag, items=self.tree.get_children())
        print(f"Nodes with 'NEWTAG' tag: {self.tree.tag_has(tag)}")

    def _test_tag_remove_method(self):
        # Remove 'House' to tag of node 'n1'
        tag = 'House'
        self.tag_remove(tag, items='n1')  # works but incorrect type for items
        print(f"Nodes with 'House' tag: {self.tree.tag_has(tag)}")
        self.tag_remove(tag, items=['n5 xxx'])  # Correct way of submitting items
        print(f"Nodes with 'House' tag: {self.tree.tag_has(tag)}")

        # Add 'NEWTAG' to tag of all nodes
        tag = 'NEWTAG'
        self.tag_remove(tag, items=self.tree.get_children())
        print(f"Nodes with 'NEWTAG' tag: {self.tree.tag_has(tag)}")

    ###########################################################################
    # Methods to add and remove tag(s)
    ###########################################################################
    def tag_add(self, tag, items=None):
        '''Adds the specified tag to each of the listed items. If tag is
        already present for a particular item, then the tags for that item are
        unchanged. Note: items refers to the iid of the tree nodes and must be
        a list object.
        '''
        if items is None:
            self.tk.call(self.tree._w, 'tag', 'add', tag)
        else:
            self.tk.call(self.tree._w, 'tag', 'add', tag, items)

    def tag_remove(self, tag, items=None):
        '''Removes the specified tag from each of the listed items. If items is
        omitted, removes tag from each item in the tree. If tag is not present
        for a particular item, then the tags for that item are unchanged.
        Note: items refers to the iid of the tree nodes and must be a list
        object.
        '''
        if items is None:
            self.tk.call(self.tree._w, 'tag', 'remove', tag)
        else:
            self.tk.call(self.tree._w, 'tag', 'remove', tag, items)
    ###########################################################################


if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    app.grid(row=0, column=0, sticky='nsew')
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)
    root.mainloop()

备注:tag addtag remove 方法中,items 参数必须是列表对象,而不是字符串对象。 items 参数的元素必须是字符串对象。如果将带有空格的字符串对象作为items 传入,则会出现_tkinter.TclError

【讨论】:

    【解决方案2】:

    我认为您需要调用tkinter 不提供的一些互联网 tcl 小部件命令(如 cmets 中所述)。为此,我们使用tk.call 方法。我创建了两个函数并手动将这些函数分配给Treeview 对象:

    def add_tag(new_tag,iid):
        root.tk.call(tree,'tag','add',new_tag,iid)
    
    def replace_tag(new_tag,to_be_replaced,iid=''):
        """If iid is ommited, replaces the old_tag from all the items if it exists"""
    
        if iid:
            root.tk.call(tree,'tag','add',new_tag,iid)
            root.tk.call(tree,'tag','remove',to_be_replaced,iid)    
        else:
            iids = tree.tag_has(to_be_replaced)
            root.tk.call(tree,'tag','remove',to_be_replaced)
            for i in iids:
                root.tk.call(tree,'tag','add',new_tag,i)
    
    def delete_tag(tag_to_delete,iid=''):
        """If iid is ommited, deletes all the tag from all items"""
    
        if iid:
            root.tk.call(tree,'tag','remove',tag_to_delete,iid)
        else:
            root.tk.call(tree,'tag','remove',tag_to_delete)
    
    tree.add_tag = add_tag
    tree.replace_tag = replace_tag
    tree.delete_tag = delete_tag
    
    iids = tree.tag_has('OLDTAG')
    
    # Adding a tag to the first item in the treeview
    tree.add_tag('NEWTAG',iids[0]) # Choosing the first item
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('OLDTAG', 'NEWTAG')
    
    # Replacing the OLDTAG for all items
    tree.replace_tag('OLDEST','OLDTAG')
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('NEWTAG', 'OLDEST')
    
    # Delete NEWTAG for the first item
    tree.delete_tag('NEWTAG',iids[0])
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('OLDEST',)
    

    虽然创建自己的类会更好。请注意,“OLDTAG”是您预先为物品提供的任何标签。它并非完全万无一失,因此请随意玩耍。

    没有任何事件驱动程序的整个代码:

    from tkinter import *
    from tkinter import ttk
    
    root = Tk()
    
    tree = ttk.Treeview(root,columns=('No.','Name'),show='headings')
    tree.pack()
    
    def add_tag(new_tag,iid):
        root.tk.call(tree,'tag','add',new_tag,iid)
    
    def replace_tag(new_tag,to_be_replaced,iid=''):
        """If iid is not specified replaces the old_tag from all the items if it exists"""
    
        if iid:
            root.tk.call(tree,'tag','add',new_tag,iid)
            root.tk.call(tree,'tag','remove',to_be_replaced,iid)    
        else:
            iids = tree.tag_has(to_be_replaced)
            root.tk.call(tree,'tag','remove',to_be_replaced)
            for i in iids:
                root.tk.call(tree,'tag','add',new_tag,i)
    
    def delete_tag(tag_to_delete,iid=''):
        """If iid is ommited, deletes all the tag from all items"""
    
        if iid:
            root.tk.call(tree,'tag','remove',tag_to_delete,iid)
        else:
            root.tk.call(tree,'tag','remove',tag_to_delete)
    
    lst = [[1,'Me'],[2,'Myself'],[3,'I']]
    
    for i in ('No.','Name'):
        tree.heading(i,text=i)
        tree.column(i,width=100)
    
    for i in lst:
        tree.insert('','end',values=i,tags='OLDTAG')
    
    iids = tree.tag_has('OLDTAG')
    
    tree.add_tag = add_tag
    tree.replace_tag = replace_tag
    tree.delete_tag = delete_tag
    
    # Adding a tag to the first item in the treeview
    tree.add_tag('NEWTAG',iids[0]) # Choosing the first item
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('OLDTAG', 'NEWTAG')
    
    # Replacing the OLDTAG for all items
    tree.replace_tag('OLDEST','OLDTAG')
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('NEWTAG', 'OLDEST')
    
    # Delete NEWTAG for the first item
    tree.delete_tag('NEWTAG',iids[0])
    all_tags = tree.item(iids[0],'tags')
    print(f'All tag for the given iid: {all_tags}') # ('OLDEST',)
    
    root.mainloop()
    

    【讨论】:

    • 我从源代码中发现tk.call() 可以直接从小部件中调用。所以与其写成root.tk.call(),不如写成tree.tk.call()。这样做更方便,尤其是在将 tkinter 小部件编写为类对象时。
    • 根据tcl,添加标签的命令是pathName tag add tag items,在tkinter中,这个tcl命令可以通过widget.tk.call(widget, 'tag', 'add', new_tag, node_iids)来激活。 tk.call() 方法中的参数'tag', 'add', new_tag, node_iids 等效于 tcl 命令中的pathName tag add tag items
    • 感谢您的回答。欣赏。我添加了 2 个 cmets,以帮助其他 tkinter 用户根据我从您的回答中学到的知识了解如何使用 tk.call() 方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-05
    • 2021-01-13
    相关资源
    最近更新 更多