Question

I am trying to get a simplistic ttk TreeView table going with per-column sorting using the heading 'command' tag, but it doesn't seem to work right. I'm using the answer to this question for implementing the functionality: Tk treeview column sort

My code:

import tkinter as tk
from tkinter import ttk

def treeview_sort_column(tv, col, reverse):
    print('sorting %s!' % col)
    l = [(tv.set(k, col), k) for k in tv.get_children('')]
    l.sort(reverse=reverse)

    # rearrange items in sorted positions
    for index, (val, k) in enumerate(l):
        print('Moving Index:%r, Value:%r, k:%r' % (index, val, k))
        tv.move(k, '', index)

    # reverse sort next time
    tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))

cols = ('name', 'path', 'time', 'pb')

root = tk.Tk()
root.geometry("700x500")
listbox = ttk.Treeview(root, columns=cols, show="headings")
for each in ('name', 'path', 'time','pb'):
    listbox.heading(each,text=each.capitalize(),command=lambda: treeview_sort_column(listbox, each, False))
    listbox.column( each, width=tk.font.Font().measure(each.title() ))
    if not each == 'path':
        listbox.column(each,stretch=False)
    if not each == 'name':
        listbox.column( each, anchor='center')

listbox.pack(expand=True, fill=tk.BOTH)

root.mainloop()

The problem I am running into is every time I run this, it only sorts by the last column, not by the column your clicking on (verified with the print statement in the treeview_sort_column function). Output I get from clicking on any column in the resulting window:

sorting pb!
sorting pb!
sorting pb!
sorting pb!
sorting pb!
sorting pb!
sorting pb!
sorting pb!

If I change from dynamic creation of each command in the for loop to explicit ones by adding this after the loop then it works as expected (i.e. each column sorts itself).

listbox.heading('name', command=lambda: treeview_sort_column(listbox, 'name', False))
listbox.heading('path', command=lambda: treeview_sort_column(listbox, 'path', False))
listbox.heading('time', command=lambda: treeview_sort_column(listbox, 'time', False))
listbox.heading('pb', command=lambda: treeview_sort_column(listbox, 'pb', False))

produces:

sorting name!
sorting name!
sorting path!
sorting path!
sorting time!
sorting time!
sorting pb!
sorting pb!

Obviously this is a very simplistic example, and my final application actually inserts data into the columns (also has more columns) but since I can't even get this simplified version to work, I'm at a loss. What is wrong with my loop that's causing the lambda functions to get screwed up?

My system:

  • Windows 7, 64 bit
  • Python 3.3.2
Was it helpful?

Solution

You should use lambda in other way. In your code every lambda gets a pointer to the same variable, that is why all functions do the same thing. You should, e.g, copy variable to make lambda unique. An example how to do it:

for each in ('name', 'path', 'time','pb'):
    listbox.heading(each,text=each.capitalize(),command=lambda each_=each: treeview_sort_column(listbox, each_, False))

Simple example:

funcs = []
for item in ('abc', 'def', 'ghi'):
    funcs.append(lambda : print(item))
for f in funcs:
    f()

It prints:

ghi
ghi
ghi

But version with fixed lambda:

funcs = []
for item in ('abc', 'def', 'ghi'):
    funcs.append(lambda item_=item: print(item_))
for f in funcs:
    f()

prints

abc
def
ghi
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top