[Solved] How to amend (e.g. add/remove) the values of a ttk.Treeview tag (tkinter)?


According to the tcl documentation, a ttk.Treeview widget does have commands to add and remove a tag from a node or a list of nodes. However, these methods are not provided in the official tkinter wrapper; see the Treeview class in /usr/lib/python3.8/tkinter/ttk.py.

Building on the comment by @JasonYang and answer by @CoolCloud, the test code below illustrates how to implement a wrapper to access the tag add and tag remove commands of the ttk.Treeview widget. Hopes this example can benefit tkinter users.

Test Code: Please see the section on # Methods to add and remove tag(s). For an explanation on why the ._w attribute of a widget is used, please read 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()

Remark: In both tag add and tag remove methods, the items argument must be a list object and not a string object. The element of the items argument must be a string object. In the event a string object with white space is passed in as items, a _tkinter.TclError would occur.

solved How to amend (e.g. add/remove) the values of a ttk.Treeview tag (tkinter)?