Search code examples
pythonpython-3.xtkintertreeview

Trying to get resizable columns and sortable columns working together in a tkinter 8.6 Treeview


I'm running into issues with trying to get several different bindings working on one tree. I have all of these functions working independently, but together some of them are activating when I don't want them to.

for col in COLS:
    self.tree.heading(col, text=str(col), command=lambda: self.sortby(col, False))
self.tree.bind("<Button-1>", self.single_click_tree)
self.tree.bind("<Double-1>", self.double_click_tree)

def double_click_tree(self, event):
    region = self.tree.identify( "region", event.x, event.y )
    if region == 'separator':
        self.resize_separator( event )
    elif region == 'cell':
        self.edit_cell( event )

def sortby(self, cid, descending=False):
    data = [(self.tree.set(child, cid), child) for child in self.tree.get_children()]
    data.sort(reverse=descending)
    for i, item in enumerate(data):
        self.tree.move(item[1], '', i)
    self.tree.heading(cid, command=lambda: self.sortby(cid, not descending))

Currently when I try to resize a column by double clicking the separator, it'll resize it and immediately afterwards sort the column that the cursor is now hovering over. Is there a better way to do this that'd be able to better avoid this issue? As I see it, the only way I'd be able to get around that is by passing an event to the sortby function and have it return if the event coordinates are on a separator but I've been running into an error when trying to run bind a new version of the sortby function with an event parameter:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1892, in __call__
    return self.func(*args)
TypeError: <lambda>() missing 1 required positional argument: 'event'

And the code that causes this error:

for col in COLS:
    self.tree.heading(col, text=str(col), command=lambda event: self.sortby(event, col, False))

Solution

  • I was able to get around this issue by setting up a flag in the sortby and resize functions called self.sortable. On double clicking a separator to resize the column, sortable is set False, and upon releasing the left mouse click button for the resizing sortable is set back to True. sortby() is only allowed to run if sortable is True.

    I ran into some issues with timing because it looks like the ButtonRelease-1 binding to the tree was running before the Button-1 column header binding, however this was resolved by running the ButtonRelease-1 function in a 10ms .after() call.

    The relevant portions of my new code:

    self.tree.bind("<Button-1>", self.single_click_tree)
    self.tree.bind("<Double-1>", self.double_click_tree)
    self.tree.bind("<ButtonRelease-1>",
        lambda event: self.parent.after(10, self.release_tree))
    
    self.resortable = True
    
    def release_tree(self):
        self.resortable = True
    
    def double_click_tree(self, event):
        region = self.tree.identify( "region", event.x, event.y )
        if region == 'separator':
            self.resize_separator( event )
        elif region == 'cell':
            self.edit_cell( event )
    
    def sortby(self, cid, descending=False):
        if not self.resortable:
            return
        
        data = [(self.tree.set(child, cid), child) for child in self.tree.get_children()]
        data.sort(reverse=descending)
        for i, item in enumerate(data):
            self.tree.move(item[1], '', i)
        self.tree.heading(cid, command=lambda: self.sortby(cid, not descending))
    
    def resize_separator(self, event):
        self.resortable = False
        ...