Search code examples
pythontkintertreeview

How to reattach or move a ttk.Treeview item back to its original location?


The following sample script allows the detachment of root item(s) of a ttk.Treeview widget after the mouse button 1 is released after clicking it and reattaches the detached items when the ttk.Button widget is released. However, I achieved the reattachments via manipulating the item's text which I find cumbersome and not always possible in situations where its text does not contain any indexing.

Quoting the documentation of the move(or reattach) method of the ttk.Treeview widget

def move(self, item, parent, index):
    """Moves item to position index in parent's list of children.

    It is illegal to move an item under one of its descendants. If
    index is less than or equal to zero, item is moved to the
    beginning, if greater than or equal to the number of children,
    it is moved to the end. If item was detached it is reattached."""
    self.tk.call(self._w, "move", item, parent, index) 

Specifically, If item was detached it is reattached.

Hence, is there a simpler way to reattach the detached item(s) back to their original index location without having to do what my script is currently doing?

Script:

import tkinter as tk
from tkinter import ttk

class App:
    def __init__(self):
        self.detached_items = set()
        self.root = tk.Tk()
        self.tree = ttk.Treeview(self.root)
        self.tree.pack(side="top", fill="both")
        self.reinsert = ttk.Button(self.root, text= "Reinsert",
                                   command=self.reinsert_items)
        self.reinsert.pack(side="top", fill="both")
        self.tree.bind("<ButtonRelease-1>", self.detach_selection)

        for i in range(10):
            self.tree.insert("", "end", text="Item %s" % i)

        self.root.mainloop()

    def detach_selection(self, event):
        selected_items = event.widget.selection()
        print(f"{selected_items=}")
        for item in selected_items:
            self.detached_items.add(item)
        event.widget.detach(*selected_items)

    def reinsert_items(self):
        for iid in self.detached_items.copy():
            print(f"{iid=}")
            text = self.tree.item(iid)["text"]
            print(f"{text=}")
            if self.tree.exists(iid):
                self.tree.move(iid, "", int(text[5:]))
                # self.tree.move(iid, "", "")  # "" as index don't work
            else:
                self.tree.move(iid, "", "end")
            self.detached_items.remove(iid)
        print(f"{self.detached_items=}")

if __name__ == "__main__":
    app = App()

Solution

  • Thanks @acw1668.

    I have set up self.items_index as a reference to the dict object to store the index of each item as it is being detached. Thereafter, this dict object is accessed in a reverse order to acquire the corresponding item index to reattach the items.

    Note: This method of using a dict object is guaranteed to work for Python 3.7 and above as dictionary order is guaranteed to be according to the insertion order.

    Working script:

    import tkinter as tk
    from tkinter import ttk
    
    class App:
        def __init__(self):
            self.items_index = {}  # Added
            self.root = tk.Tk()
            self.tree = ttk.Treeview(self.root)
            self.tree.pack(side="top", fill="both")
            self.reinsert = ttk.Button(self.root, text= "Reinsert",
                                       command=self.reinsert_items)
            self.reinsert.pack(side="top", fill="both")
            self.tree.bind("<ButtonRelease-1>", self.detach_selection)
    
            for i in range(10):
                self.tree.insert("", "end", text="Item %s" % i)
    
            self.root.mainloop()
    
        def detach_selection(self, event):
            print(f"\nDetach")
            selected_items = event.widget.selection()
            print(f"{selected_items=}")
            for item in selected_items:
                self.items_index[item] = self.tree.index(item)
                event.widget.detach(item)
            print(f"{self.items_index=}\n")
    
        def reinsert_items(self):
            print(f"\nReinsert")
            iids = list(self.items_index.keys())
            print(f"{iids=}")
            for iid in iids[::-1]:
                index = self.items_index[iid]
                self.tree.move(iid, "", index)
                del self.items_index[iid]
            print(f"{self.items_index=}\n")
    
    
    if __name__ == "__main__":
        app = App()