Sortierung im ttk.Treeview funktioniert nicht

Fragen zu Tkinter.
Antworten
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

Hallo zusammen,

jetzt habe ich bereits ein paar Stunden Suche/Recherche hinter mir - leider erfolglos.

Wie erreicht man, dass bei einem Klick auf den Spaltenkopf das Treeview **richtig** sortiert wird?

Hier mein Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-


"""
Thanks to
---------------------------------------------------------------------

heavily inspired by:

    on http://stackoverflow.com/questions
    .../19749476/3-different-issues-with-ttk-treeviews-in-python

    on http://stackoverflow.com/questions
    .../1966929/tk-treeview-column-sort/1967793#1967793
"""


import os
import logging
from copy import copy
import ttk
try:
    import Tkinter as tk
    #import tkFont
except ImportError:  # py3k
    import tkinter as tk
    #import tkinter.font as tkFont


Logger = logging.getLogger("root")
NL = os.linesep
TAB = "    "
NTAB = NL + TAB
NLST = NL + " - "


class DatasetView(ttk.Frame):
    def __init__(self, master, header=(), datasets=(), id_at_first=False):
        Logger.debug("<DatasetView> startet mit master %s",
                     str(master) + NL +
                     "und headerset:" +
                     NLST + NLST.join(header) + NL +
                     "und datasets:" +
                     NLST + NLST.join([unicode(d) for d in datasets]))

        ttk.Frame.__init__(self, master)

        ###
        # model

        self.header = {}
        self.datasets = {}
        self.__sorted_view = {} # asc -> True, desc -> False,

        ###
        # view

        self.__setup_widgets()
        Logger.debug("<DatasetView> widgets created")

        ###
        # add Data

        if header:
            self.setup_header(header)

        if datasets:
            for dataset in datasets:
                self.add_dataset(dataset, id_at_first)

    def __setup_widgets(self):
        self.table = ttk.Treeview(self)

        ysb = ttk.Scrollbar(self,
                            orient='vertical',
                            command=self.table.yview)
        xsb = ttk.Scrollbar(self,
                            orient='horizontal',
                            command=self.table.xview)

        self.table.config(yscroll=ysb.set, xscroll=xsb.set)
        self.table.column('#0', width=50)

        #self.tree.heading('#0', text='Path', anchor='w')
        #self.tree.bind("<<TreeviewSelect>>", self.update_subtree)

        # add tree and scrollbars to frame

        self.table.grid(in_=self, row=0, column=0, sticky="nsew")
        ysb.grid(in_=self, row=0, column=1, sticky="ns")
        xsb.grid(in_=self, row=1, column=0, sticky="ew")

        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.Y)

    def setup_header(self, header, **kwargs):
        Logger.debug("<DatasetView> set_header startet [%s, %s]",
                     header, kwargs)

        self.table.config(columns=header)
        for i, item in enumerate(header):
            self.header[i] = item
            self.header[item] = i
            self.__sorted_view[i] = False
            self.table.heading(i,
                               text=item,
                                command=lambda: \
                                   self.sort(i, not self.__sorted_view[i]))

        Logger.debug("<DatasetView> set_header fertig")

    def add_dataset(self, dataset, id_at_first=True):
        Logger.debug("<DatasetView> add_dataset startet [%s]", dataset)
        data = list(dataset[:min(len(dataset), len(self.header))])

        if id_at_first:
            self.datasets[data[0]] = data
        else:
            key = len(self.datasets)
            self.datasets[key] = data
            data.insert(0, key)

        self.table.insert("", "end", iid=data[0], text=data[0],
                          values=data[1:])
        Logger.debug("<DatasetView> add_dataset fertig")

    def insert_dataset(self, pos, dataset):
        # TODO: to implement
        pass

    def sort(self, col, reverse=False):
        Logger.debug("<DatasetView> sort startet [%s, reverse=%s]",
                     col, reverse)
        nr = 0
        if isinstance(col, (str, unicode)):
            nr = self.header[col]
        elif isinstance(col, (int, long, float)):
            nr = int(col)

        spalte = [(self.table.set(k, nr), k)
                  for k in self.table.get_children('')]
        Logger.debug("<DatasetView> spalte: %s", spalte)

        spalte.sort(reverse=reverse)
        Logger.debug("<DatasetView> spalte: %s", spalte)

        for index, (val, k) in enumerate(spalte):
            self.table.move(k, '', index)

        self.__sorted_view[nr] = not self.__sorted_view[nr]
        Logger.debug("<DatasetView> fertig")


if __name__ == "__main__":
    FORMAT_TWOLINES = \
        "[%(lineno)4d:%(name)s.%(module)s.%(funcName)s]\n" + \
        "%(levelname)7s: %(message)s"
    logging.basicConfig(level=logging.DEBUG,
                        fmt=FORMAT_TWOLINES
                        )
    root = tk.Tk()
    root.title("DatasetView")

    personen = [
        ("Amann", "Amelie", "II"),
        ("Berger", u"Björn", "II"),
        ("Cole", "Celina", "II"),
        ("Dorfner", "Dieter", "II"),
        ("Ebner", "Emeli", "II"),
        ("Fruth", "Fabian", "II"),
        ("Gos", "Gerlinde", "II"),
    ]

    tabelle = DatasetView(root,
                          header=("Nachname", "Vorname", "Abteilung"),
                          datasets=personen,
                          id_at_first=False)
    tabelle.pack()
    root.mainloop()
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
sedi
User
Beiträge: 104
Registriert: Sonntag 9. Dezember 2007, 19:22

OK - ich habe jetzt selbst eine Lösung gefunden - die mir selbst aber etwas umständlich erscheint:

Um das Problem der Variablenübermittlung in Callbacks zu umgehen nutze ich die magische Funktion ``__call__``. Ich habe eine Aktionsklasse erstellt, die diese magische Methode implementiert. Diese wird aufgerufen, wenn ein zur Aktionsklasse gehöriges Objekt durch ``actionobject()`` aufgerufen wird. In dieser ``__call__`` - Methode kann man dann alles mögliche realisieren.

Hier meine Aktionsklasse:

Code: Alles auswählen

class _HeaderClick(object):
    def __init__(self, spalte, reverse, callback):
        self.spalte = spalte
        self.reverse = reverse
        self.handler = callback

    def __call__(self):
        self.handler(self.spalte, self.reverse)
        self.reverse = not self.reverse
Der Code der Methode ``def setup_header(self, header, **kwargs):`` aus obigem Code sieht nun wie folgt aus:

Code: Alles auswählen


    def setup_header(self, header, **kwargs):
        Logger.debug("<DatasetView> set_header startet [%s, %s]",
                     header, kwargs)

        self.table.config(columns=header)
        for i, item in enumerate(header):
            self.header[i] = item
            self.header[item] = i
            self.table.heading(i, text=item, command=
                _HeaderClick(i, False, self.sort))
        
        Logger.debug("<DatasetView> set_header fertig")

Ich hoffe das hilft so manchem - aber trotzdem:
Wie erreicht man, dass bei einem Klick auf den Spaltenkopf das Treeview AUF EINFACHE WEISE **richtig** sortiert wird? ;)
CU sedi
----------------------------------------------------------
Python 3.5; Python 3.6
LinuxMint18
Antworten