A sortable table using tktable.

   1 __all__ = ['SortableTable']
   2 
   3 import tktable
   4 
   5 class SortableTable(tktable.Table):
   6     def __init__(self, **kwargs):
   7         self.keyfunc = kwargs.pop('keyfunc', None)
   8         self.sortable_columns = kwargs.pop('sortable_columns', ())
   9         if 'variable' not in kwargs:
  10             kwargs['variable'] = tktable.ArrayVar(kwargs.pop('master', None))
  11         tktable.Table.__init__(self, **kwargs)
  12         self.bind("<1>", self._sort)
  13 
  14         self._ascending = dict([(i, True)
  15             for i in xrange(int(self['cols']))
  16             if i in self.sortable_columns])
  17 
  18     def _wrap_keyfunc(self, item):
  19         return self.keyfunc(item[0])
  20 
  21     def _sort(self, event):
  22         row, col = map(int,
  23                 self.index('@%d,%d' % (event.x, event.y)).split(','))
  24         if row != int(self['roworigin']) or col not in self.sortable_columns:
  25             return
  26 
  27         rows, cols = (int(self['rows']) - int(self['titlerows']),
  28                 int(self['cols']))
  29         s = sorted([(self.get('%d,%d' % (i, col)), i) for i in range(rows)],
  30                 key=self._wrap_keyfunc, reverse=not self._ascending[col])
  31 
  32         self._ascending[col] = not self._ascending[col]
  33 
  34         sitems = {}
  35         for indx, (value, row) in enumerate(s):
  36             if indx == row:
  37                 continue
  38 
  39             self.set(**{'%d,%d' % (indx, col): value})
  40             for scol in xrange(cols):
  41                 if scol == col:
  42                     # this column is already sorted
  43                     continue
  44                 sitems['%d,%d' % (indx, scol)] = self.get('%d,%d' % (row, scol))
  45 
  46         if sitems:
  47             self.set(**sitems)
  48 
  49 
  50 def demo():
  51     import random
  52 
  53     rows = 25
  54     cols = 10
  55 
  56     # sample data
  57     tclarray = tktable.ArrayVar()
  58     for i in range(cols):
  59         tclarray["-1,%d" % i] = chr(ord("A") + i)
  60     for i in range(rows):
  61         for j in range(cols):
  62             tclarray["%d,%d" % (i, j)] = random.randint(0, 10)
  63 
  64     # sample sortable table, only even columns will be sorted in this example
  65     # and the values will be sorted considering their int value
  66     table = SortableTable(
  67             keyfunc=lambda x: int(x),
  68             sortable_columns=filter(lambda x: not (x & 1), range(cols)),
  69             variable=tclarray,
  70             rows=rows + 1, # compensating for title row
  71             cols=cols,
  72             roworigin=-1,
  73             titlerows=1)
  74 
  75     table.pack(expand=True, fill='both')
  76     table.mainloop()
  77 
  78 if __name__ == "__main__":
  79     demo()

To get some arrows to indicate the sort order you can apply the following patch:

--- sort_table.py       2009-01-01 23:08:26.000000000 -0200
+++ sort_table_witharrows.py    2009-01-01 23:07:48.000000000 -0200
@@ -1,34 +1,60 @@
 __all__ = ['SortableTable']
 
+import Tkinter
 import tktable
 
 class SortableTable(tktable.Table):
     def __init__(self, **kwargs):
         self.keyfunc = kwargs.pop('keyfunc', None)
         self.sortable_columns = kwargs.pop('sortable_columns', ())
+        master = kwargs.pop('master', None)
         if 'variable' not in kwargs:
-            kwargs['variable'] = tktable.ArrayVar(kwargs.pop('master', None))
+            kwargs['variable'] = tktable.ArrayVar(master)
         tktable.Table.__init__(self, **kwargs)
-        self.bind("<1>", self._sort)
+
+        self.master.tk.call("package", "require", "Img")
+
+        self._imgs = [
+                Tkinter.PhotoImage(master=master, file='arrow_up.png'),
+                Tkinter.PhotoImage(master=master, file='arrow_down.png')
+                ]
 
         self._ascending = dict([(i, True)
             for i in xrange(int(self['cols']))
             if i in self.sortable_columns])
 
+        self._adjust_titlerow()
+
+    def _adjust_titlerow(self):
+        if not int(self['titlerows']):
+            return
+
+        # assuming a single title row
+        trow = int(self['roworigin'])
+        for col in xrange(int(self['cols'])):
+            index = '%d,%d' % (trow, col)
+
+            lbl = Tkinter.Label(text=self.get(index), bg='grey')
+            if col in self._ascending:
+                lbl.configure(image=self._imgs[0], compound='right',
+                        padx=self._imgs[0].width())
+                lbl.bind("<1>", self._sort)
+                lbl.col = col
+            self.window_configure(index, sticky='ew', window=lbl)
+
+
     def _wrap_keyfunc(self, item):
         return self.keyfunc(item[0])
 
     def _sort(self, event):
-        row, col = map(int,
-                self.index('@%d,%d' % (event.x, event.y)).split(','))
-        if row != int(self['roworigin']) or col not in self.sortable_columns:
-            return
+        col = event.widget.col
 
         rows, cols = (int(self['rows']) - int(self['titlerows']),
                 int(self['cols']))
         s = sorted([(self.get('%d,%d' % (i, col)), i) for i in range(rows)],
                 key=self._wrap_keyfunc, reverse=not self._ascending[col])
 
+        event.widget['image'] = self._imgs[self._ascending[col]]
         self._ascending[col] = not self._ascending[col]
 
         sitems = {}

and get the attached images. Or if you prefer not using images for this, you could use some unicode symbols instead:

--- sort_table.py       2009-01-04 00:38:07.000000000 -0200
+++ sort_table2.py      2009-01-04 00:37:53.000000000 -0200
@@ -1,34 +1,59 @@
 __all__ = ['SortableTable']
 
+import Tkinter
 import tktable
 
 class SortableTable(tktable.Table):
     def __init__(self, **kwargs):
         self.keyfunc = kwargs.pop('keyfunc', None)
         self.sortable_columns = kwargs.pop('sortable_columns', ())
+        master = kwargs.pop('master', None)
         if 'variable' not in kwargs:
-            kwargs['variable'] = tktable.ArrayVar(kwargs.pop('master', None))
+            kwargs['variable'] = tktable.ArrayVar(master)
         tktable.Table.__init__(self, **kwargs)
-        self.bind("<1>", self._sort)
+
+        self._symbols = [
+                u'\u25b2', # up arrow
+                u'\u25bc', # down arrow
+                ]
 
         self._ascending = dict([(i, True)
             for i in xrange(int(self['cols']))
             if i in self.sortable_columns])
 
+        self._adjust_titlerow()
+
+    def _adjust_titlerow(self):
+        if not int(self['titlerows']):
+            return
+
+        # assuming a single title row
+        trow = int(self['roworigin'])
+        for col in xrange(int(self['cols'])):
+            index = '%d,%d' % (trow, col)
+
+            lbl = Tkinter.Label(text=self.get(index), bg='grey')
+            if col in self._ascending:
+                lbl['text'] += ' ' + self._symbols[0]
+                lbl.bind("<1>", self._sort)
+                lbl.col = col
+            self.window_configure(index, sticky='ew', window=lbl)
+
+
     def _wrap_keyfunc(self, item):
         return self.keyfunc(item[0])
 
     def _sort(self, event):
-        row, col = map(int,
-                self.index('@%d,%d' % (event.x, event.y)).split(','))
-        if row != int(self['roworigin']) or col not in self.sortable_columns:
-            return
+        col = event.widget.col
 
         rows, cols = (int(self['rows']) - int(self['titlerows']),
                 int(self['cols']))
         s = sorted([(self.get('%d,%d' % (i, col)), i) for i in range(rows)],
                 key=self._wrap_keyfunc, reverse=not self._ascending[col])
 
+        curr_text = event.widget['text']
+        event.widget['text'] = (curr_text.rsplit(' ', 1)[0] + ' ' +
+                self._symbols[self._ascending[col]])
         self._ascending[col] = not self._ascending[col]
 
         sitems = {}

Attachments

arrow_down

arrow_up

None: SorTable (last edited 2009-01-04 02:39:14 by GuilhermePolo)