【问题标题】:Tk treeview column sortTK 树视图列排序
【发布时间】:2010-12-30 07:55:46
【问题描述】:

有没有办法通过单击列对Tk Treeview 中的条目进行排序?令人惊讶的是,我找不到任何文档/教程。

【问题讨论】:

  • 除了#tcl,Tkinter 研究的其他适当资源是 Tkinter 邮件列表和 Wiki。

标签: python sorting treeview tkinter


【解决方案1】:

来自#tclpatthoyts 指出TreeView Tk 演示程序具有排序功能。这是它的 Python 等价物:

def treeview_sort_column(tv, col, reverse):
    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):
        tv.move(k, '', index)

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

[...]
columns = ('name', 'age')
treeview = ttk.TreeView(root, columns=columns, show='headings')
for col in columns:
    treeview.heading(col, text=col, command=lambda: \
                     treeview_sort_column(treeview, col, False))
[...]

【讨论】:

  • 这个答案在 Python 2.7 上对我不起作用——它总是将最后一列的名称传递给 treeview_sort_columnThe solution that madonius posted 在 Python 2.7 上确实为我工作。
  • 是的。在其他答案中有一个很好的解释为什么这不起作用
  • 将第二行到最后一行中的“False”更改为“True”,以允许第一次单击将列表从其初始(有组织)状态反转。例如,如果您的列表最初位于 A->Z 中,则按菜单将不会注意到,因为 reverse 是错误的。你必须再次点击才能崇敬。这可以通过将该 arg 更改为 True 来解决。
【解决方案2】:

这在 python3 中不起作用。由于变量是通过引用传递的,所有的 lambdas 最终都引用了列中相同的最后一个元素。

这对我有用:

for col in columns:
    treeview.heading(col, text=col, command=lambda _col=col: \
                     treeview_sort_column(treeview, _col, False))

【讨论】:

  • 也适用于 Python 2.7。
  • 我花了一段时间才理解这个答案,所以我用一个完整的例子重写了它。我本来希望编辑这个答案,但它刚刚被拒绝:/
  • 当我第一次访问这个页面时,我在看到 Sridhar Ratnakumar 的回答后停止了阅读。使用 for 循环时,我无法让它工作。如果我一个一个地添加标题,我可以让它工作。我很高兴我重新访问了这个页面。微小的变化效果很好!
【解决方案3】:

ma​​donius 是对的,但这里有完整的示例和正确、可理解的解释

Sridhar Ratnakumar 提供的答案在 python3 中不起作用(显然在 python2.7 中):由于变量是通过引用传递的,所有 lambdas 最终都引用相同的最后一个元素在列中。

你只需要更改这个for loop

for col in columns:
    treeview.heading(col, text=col, command=lambda _col=col: \
                     treeview_sort_column(treeview, _col, False))

同样的变化必须应用到 treeview_sort_column 中的 lambda 函数

所以完整的解决方案如下所示:

def treeview_sort_column(tv, col, reverse):
    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):
        tv.move(k, '', index)

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

[...]
columns = ('name', 'age')
treeview = ttk.TreeView(root, columns=columns, show='headings')
for col in columns:
    treeview.heading(col, text=col, command=lambda _col=col: \
                     treeview_sort_column(treeview, _col, False))
[...]

【讨论】:

  • 作为快速补充:l.sort(reverse=reverse, key=lambda tup: int(tup[0])) 可以正确按数值排序(或更改为需要的值),另外l.sort(reverse=False, key=lambda tup: int(tup[1])) 会将表格恢复到原来的顺序。
【解决方案4】:

我在尝试为数据库创建视图时遇到了同样的问题,
灵感来自Sridhar Ratnakumar 答案,
我宁愿采取与他相同的原则并升级 Treeview 类。

class MyTreeview(ttk.Treeview):
    def heading(self, column, sort_by=None, **kwargs):
        if sort_by and not hasattr(kwargs, 'command'):
            func = getattr(self, f"_sort_by_{sort_by}", None)
            if func:
                kwargs['command'] = partial(func, column, False)
        return super().heading(column, **kwargs)

    def _sort(self, column, reverse, data_type, callback):
        l = [(self.set(k, column), k) for k in self.get_children('')]
        l.sort(key=lambda t: data_type(t[0]), reverse=reverse)
        for index, (_, k) in enumerate(l):
            self.move(k, '', index)
        self.heading(column, command=partial(callback, column, not reverse))

    def _sort_by_num(self, column, reverse):
        self._sort(column, reverse, int, self._sort_by_num)

    def _sort_by_name(self, column, reverse):
        self._sort(column, reverse, str, self._sort_by_name)

    def _sort_by_date(self, column, reverse):
        def _str_to_datetime(string):
            return datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
        self._sort(column, reverse, _str_to_datetime, self._sort_by_date)

...
# Some code
...

treeview.heading('number', text='number', sort_by='num')
treeview.heading('name', text='name', sort_by='name')
treeview.heading('date', text='date', sort_by='date')

把这个放在这里:)

【讨论】:

  • 非常感谢自定义树视图。我用它来定制它以满足我的需要。我有一个包含 10 列费用和收入的树形视图。每列都有字符串、数字、带有千位分隔符的数字(比如 1,000)、包含多个十进制值的行代码(比如 2.5.1.6)。我现在能够很好地对它们进行排序。工作代码附在下面作为答案。
【解决方案5】:

如果您的表中有整数,请对函数进行这个小改动 它看起来像这样。

def treeview_sort_column(treeview: ttk.Treeview, col, reverse: bool):
    """
    to sort the table by column when clicking in column
    """
    try:
        data_list = [
            (int(treeview.set(k, col)), k) for k in treeview.get_children("")
        ]
    except Exception:
        data_list = [(treeview.set(k, col), k) for k in treeview.get_children("")]

    data_list.sort(reverse=reverse)

    # rearrange items in sorted positions
    for index, (val, k) in enumerate(data_list):
        treeview.move(k, "", index)

    # reverse sort next time
    treeview.heading(
        column=col,
        text=col,
        command=lambda _col=col: treeview_sort_column(
            treeview, _col, not reverse
        ),
    )

【讨论】:

    【解决方案6】:

    这是对包含字符串、数字、带有千位分隔符的数字、带有多个十进制值的代码的列进行排序的工作代码。

    import tkinter as objTK
    from tkinter import ttk as objTTK
    from functools import partial
    import datetime as objDateTime
    
    class MyTreeview(objTTK.Treeview):
        def heading(self, column, sort_by=None, **kwargs):
            if sort_by and not hasattr(kwargs, 'command'):
                func = getattr(self, f"_sort_by_{sort_by}", None)
                if func:
                    kwargs['command'] = partial(func, column, False)
                # End of if
            # End of if
            return super().heading(column, **kwargs)
        # End of heading()
    
        def _sort(self, column, reverse, data_type, callback):
            l = [(self.set(k, column), k) for k in self.get_children('')]
            l.sort(key=lambda t: data_type(t[0]), reverse=reverse)
            for index, (_, k) in enumerate(l):
                self.move(k, '', index)
            # End of for loop
            self.heading(column, command=partial(callback, column, not reverse))
        # End of _sort()
    
        def _sort_by_num(self, column, reverse):
            self._sort(column, reverse, int, self._sort_by_num)
        # End of _sort_by_num()
    
        def _sort_by_name(self, column, reverse):
            self._sort(column, reverse, str, self._sort_by_name)
        # End of _sort_by_num()
    
        def _sort_by_date(self, column, reverse):
            def _str_to_datetime(string):
                return objDateTime.datetime.strptime(string, "%Y-%m-%d")
            # End of _str_to_datetime()
            
            self._sort(column, reverse, _str_to_datetime, self._sort_by_date)
        # End of _sort_by_num()
        
        def _sort_by_multidecimal(self, column, reverse):
            def _multidecimal_to_str(string):
                arrString = string.split(".")
                strNum = ""
                for iValue in arrString:
                    strValue = f"{int(iValue):02}"
                    strNum = "".join([strNum, str(strValue)])
                # End of for loop
                strNum = "".join([strNum, "0000000"])
                return int(strNum[:8])
            # End of _multidecimal_to_str()
            
            self._sort(column, reverse, _multidecimal_to_str, self._sort_by_multidecimal)
        # End of _sort_by_num() 
    
        def _sort_by_numcomma(self, column, reverse):
            def _numcomma_to_num(string):
                return int(string.replace(",", ""))
            # End of _numcomma_to_num()
            
            self._sort(column, reverse, _numcomma_to_num, self._sort_by_numcomma)
        # End of _sort_by_num() 
    
    # End of class MyTreeview
    
    objWindow = objTK.Tk()
    
    arrlbHeader = ["Type" , "Description", "C. Name", "C. code", "Amount", "Day", "Month ", "Year", "Date", "Comments"]
    treeview = MyTreeview(columns=arrlbHeader, show="headings")
    arrRows = [["Expenses", "Curds milk", "Dairy products", "2.5.2.1", "456", "31", "8", "2021", "2021-08-31", ""],
    ["Expenses", "Aug", "Maid", "2.12.4", "1,000", "31", "8", "2021", "2021-08-31", ""],
    ["Expenses", "Aug", "Water", "2.12.8", "200", "31", "8", "2021", "2021-08-31", "AAA"],
    ["Income", "Aug", "Electricity", "2.12.2", "190", "31", "8", "2021", "2021-08-31", "OMG"],
    ["Expenses", "Aug - garbage collection", "Miscellaneous", "2.12.9", "20", "31", "8", "2021", "2021-08-31", "Test1"],
    ["Expenses", "Bread", "Bakery", "2.5.1.1", "10", "29", "8", "2021", "2021-08-29", ""],
    ["Income", "Veggies", "Vegetables", "2.5.2.7", "21", "28", "8", "2021", "2021-08-28", ""],
    ["Expenses", "Groceries", "Grains", "2.5.2.3", "76", "28", "8", "2021", "2021-08-28", "Test"],
    ["Expenses", "Phenyl", "Toiletries", "2.16", "34", "28", "8", "2021", "2021-08-28", ""]]
    arrColWidth = [57, 53, 85, 69, 55, 30, 45, 33, 68, 100]
    arrColAlignment = ["center", "e", "w", "w", "e", "center", "center", "center", "center", "w"]
    
    arrSortType = ["name", "name", "name", "multidecimal", "numcomma", "num", "num", "num", "date", "name"]
    for iCount in range(len(arrlbHeader)):
        strHdr = arrlbHeader[iCount]
        treeview.heading(strHdr, text=strHdr.title(), sort_by=arrSortType[iCount])
        treeview.column(arrlbHeader[iCount], width=arrColWidth[iCount], stretch=True, anchor=arrColAlignment[iCount])
    # End of for loop
    
    treeview.pack()
    
    for iCount in range(len(arrRows)):
        treeview.insert("", "end", values=arrRows[iCount])
    # End of for loop
    
    objWindow.bind("<Escape>", lambda funcWinSer: objWindow.destroy())
    
    objWindow.mainloop()
    

    【讨论】:

      猜你喜欢
      • 2022-09-28
      • 1970-01-01
      • 1970-01-01
      • 2021-03-29
      • 2012-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多