6

I have a python tkinter application that contains a ttk.treeview widget.

The widget displays a list of files found on a specific directory tree with a specific extension - this was trivial to build with tt.treeview widget.

There is a request to enable "on-the-fly" filtering of the tree - e.g., the user types in an Entry some string, and as he/she types, the tree removes elements that don't match the typed string so far.

I was exploring the Treeview documentation, tried the detach and reattach methods but with no luck.

detach indeed removes the non-matched elements from the tree, but if the user hit Backspace, I can no longer iterate correctly on the tree to restore those detached elements as get_children method will not return them.

def filter_tree(self):
    search_by = self.search_entry.get()
    self.tree_detach_leaf_by_regex(self.current_loaded_folder, search_by, "")

def tree_detach_leaf_by_regex(self, root, regex, parent):
    if self.treeview.get_children(root):
        for child in self.treeview.get_children(root):
            self.tree_detach_leaf_by_regex(child, regex, root)
    else:
        if not re.match(regex, self.treeview.item(root)["text"]):
            self.elements_index_within_parent[root] = self.treeview.index(root)
            self.elements_parents[parent] = 1
            self.treeview.detach(root)
        else:
            self.treeview.reattach(root, parent, self.elements_index_within_parent[root])

Looking forward to read your advice.

NirMH
  • 4,769
  • 3
  • 44
  • 69
  • What did you do finally? I have no answer, actually I have the same question as yours. – keepAlive Oct 30 '17 at 17:06
  • @Kanak: as nobody answered, i had to drop the whole idea and try something else. While tkinter allows a fast-develop of GUI application, it is far less than any other "standard" IDE/library. I will eventually move to something else (C#) – NirMH Nov 01 '17 at 12:38
  • *I answered* your question. As you know/mention, `get_children` only returns ids of attached items,but working with something like `list(self._detached) + list(self.tree.get_children())` ***does the job***. See the definition of my method whose name is `_columns_searcher` for an example case. In short, the only thing you need to retrieve detached elements is their id, which means that you must save them when detaching them the first time. Even removed, they still belong to the working environment. – keepAlive Nov 15 '17 at 00:01

2 Answers2

5

To make my answer reusable by anybody, I have to tell more than directly answering your question. If you directly want to see how I do to get detached items (thus without using the method get_children which cannot get detached items' id), jump to the definition of the method whose name is _columns_searcher.


Introduction

Let's first define some attributes.

@property
def _to_search(self):
    key = 'to_search'
    if key not in self._cache:
        self._cache[key] = tk.StringVar()
    return self._cache[key] 

def _set_search_entry(self):  
    ent = ttk.Entry(
        self.root, # or canvas, or frame ...
        #...
        textvariable=self._to_search
    )
    ent.grid(
        #...
    )
    ent.bind(
        '<Return>',
        self._columns_searcher
    )
    return ent

@property
def search_entry(self):
    key = 'search_entry'
    if key not in self._cache:
        self._cache[key] = self._set_search_entry()
    return self._cache[key]


Core answer

What follows is the part which directly show how to re-attach user-detached items. First note that, as the OP mentions, get_children only return ids of attached items. Second note that the only thing you need to re-attach detached items is their id. Which implies thus to trace/save them when they are detached so as to be able to re-attach them.

_detached = set()
def _columns_searcher(self, event):
    #              originally a set            returns a tuple
    children = list(self._detached) + list(self.tree.get_children())
    self._detached = set()
    query = self._to_search.get()

    self._brut_searcher(children, query.lower())

Note that children above contains all items, be them detached.

def _brut_searcher(self, children, query):
    i_r = -1
    for item_id in children:
        text = self.tree.item(item_id)['text'] # already contains the strin-concatenation (over columns) of the row's values
        if query in text:
            i_r += 1
            self.tree.reattach(item_id, '', i_r)
        else:
            self._detached.add(item_id)
            self.tree.detach(item_id)
keepAlive
  • 6,369
  • 5
  • 24
  • 39
  • Thanks for the detailed answer - but it is not what i've asked... i know how to filter the content, but once I remove the filtered elements from the tree, how can I re-attach them once the user removes the filter? – NirMH Nov 02 '17 at 07:21
  • @NirMH I re-attach elements as asked when/if needed... I simply apply a new filter which actually filters nothing and thus re-attach everything. – keepAlive Nov 02 '17 at 10:57
  • @NirMH. Note that I do not (only) rely on `get_children` since, as you know/mention, it can only return ids of attached items, but to `list(self._detached) + list(self.tree.get_children())`. See the definition of my method whose name is `_columns_searcher`. Indeed, the only thing you need to retrieve detached elements is their id. – keepAlive Nov 03 '17 at 07:39
  • @NirMH. While the content of my answer did not change (see edits), I made it clearer and concentrated on what you asked. – keepAlive Nov 18 '17 at 20:00
0

From what I see, detach is almost same as delete. row is gone and you have no access to it. You have to make a copy of "detached" items, just id, name or more, if you have advance treeview structure, and then go over elements in both lists and sort it out. Difference is that if you detach item and check it's id with "exists" function, it should return true

Dylan
  • 1