Welcome, guest | Sign In | My Account | Store | Cart
# Version: 0.5
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))

try:
    from Tkinter import Frame, BOTH ,N,E,S, W, CENTER
    import tkFont as tkFont
    from ttk import Treeview, Scrollbar
except ImportError:
    from tkinter import Frame, BOTH ,N,E,S, W, CENTER
    import tkinter.font as tkFont
    from tkinter.ttk import Treeview, Scrollbar


def make_autoscroll(sbar, first, last):
    """Hide and show scrollbar as needed."""
    first, last = float(first), float(last)
    if first <= 0 and last >= 1:
        sbar.grid_remove()
    else:
        sbar.grid()
    sbar.set(first, last)

class Multicolumn_Listbox(Frame, object):

    def __init__(self, master, columns, anchor_heading = CENTER, anchor_data = W, style=None, autoscroll=True, vscrollbar=True, hscrollbar=False, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, select_mode=None, command=None, sort=True, adjust_heading_to_content=False):

        Frame.__init__(self, master, class_="Multicolumn_Listbox")
        
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        treeview_kwargs = {}
        if style is not None:
            treeview_kwargs["style"] = style

        if height is not None:
            treeview_kwargs["height"] = height
            
        if padding is not None:
            treeview_kwargs["padding"] = padding
        
        if select_mode is not None:
            treeview_kwargs["selectmode"] = select_mode

        self._treeview = Treeview(self,columns=columns, show="headings", **treeview_kwargs)
        self._treeview.grid(row=0, column=0, sticky= N+E+W+S)
        
        scrollbar_kwargs = {}
        if scrollbar_background is not None:
            scrollbar_kwargs["background"] = scrollbar_background
            
        if scrollbar_troughcolor is not None:
            scrollbar_kwargs["throughcolor"] = scrollbar_troughcolor

        if vscrollbar:
            self._vbar=Scrollbar(self,takefocus=0, command=self._treeview.yview, **scrollbar_kwargs)
            self._vbar.grid(row=0, column=1, sticky= N+S)
            
            if autoscroll:
                self._treeview.config(yscrollcommand=lambda f, l: make_autoscroll(self._vbar, f, l))
            else:
                self._treeview.config(yscrollcommand=self._vbar.set)

        if hscrollbar:
            self._hbar=Scrollbar(self,takefocus=0, command=self._treeview.xview, **scrollbar_kwargs)
            self._hbar.grid(row=0, column=1, sticky= E+W)
            
            if autoscroll:
                self._treeview.config(xscrollcommand=lambda f, l: make_autoscroll(self._hbar, f, l))
            else:
                self._treeview.config(xscrollcommand=self._hbar.set)
        
        if command is not None:
            self.on_select = command
            self._treeview.bind("<<TreeviewSelect>>", self._on_select)
        
        self._columns = columns
        self._number_of_columns = len(columns)

        for i in range(0, len(columns)):

            if sort:
                self._treeview.heading(i, text=columns[i], command=lambda col=i: self._sort_by(col, descending=False))
            else:
                self._treeview.heading(i, text=columns[i], anchor=anchor_heading)
                
            if adjust_heading_to_content:
                self._treeview.column(i, width=tkFont.Font().measure(columns[i]))

            self._treeview.column(i, anchor=anchor_data)

    def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None):
        kwargs = {}
        for config_name in ("width", "anchor", "stretch", "minwidth"):
            config_value = locals()[config_name]
            if config_value is not None:
                kwargs[config_name] = config_value
            
        self._treeview.column('#%s'%index, **kwargs)

    def get_row(self, index):
        try:
            item_ID = self._treeview.get_children()[index]
        except IndexError:
            raise ValueError("Index out of range: %d"%index)        

        return self._treeview.item(self._item_ID_to_row_data(item_ID))

    def add_row(self, data, position="end"):
        if len(data) != self._number_of_columns:
            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)

        self._treeview.insert('', position, values=data)
            
    def update_row(self, index, data):
        try:
            item = self._treeview.get_children()[index]
        except IndexError:
            raise ValueError("Index out of range: %d"%index)
            
        if len(data) == len(self._columns):
            self._treeview.item(item, values=data)
        else:
            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)
            
    def delete_row(self, index):
        try:
            item_ID = self._treeview.get_children()[index]
        except IndexError:
            raise ValueError("Index out of range: %d"%index)
        
        self._treeview.delete(item_ID)
        
    def update_column(self, index, data):
        for i, item_ID in enumerate(self._treeview.get_children()): 
            data_row = self._item_ID_to_row_data(item_ID)
            data_row[index] = data[i]

            self._treeview.item(item_ID, values=data_row)

        return data

    def clear(self):
        # Another possibility:
        #  self._treeview.delete(*self._treeview.get_children())

        for row in self._treeview.get_children():
            self._treeview.delete(row)
            
    def update_table(self, data):
        self.clear()

        for row in data:
            self.add_row(row)
            
    def focus(self, row=None):
        if row is None:
            return self._treeview.item(self._treeview.focus())
        else:
            item = self._treeview.get_children()[row]
            self._treeview.focus(item)

    def state(self, state=None):
        if stateSpec is None:
            return self._treeview.state()
        else:
            self._treeview.state(state)
    
    def xview(self, *args):
        self._treeview.xview(*args)

    def yview(self):
        self._treeview.yview(*args)

    @property
    def number_of_rows(self):
        return len(self._treeview.get_children())
        
    @property
    def number_of_columns(self):
        return self._number_of_columns
        
    def select_row(self, index):
        list_of_items = self._treeview.get_children()
        
        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Index out of range: %d"%index)

        self._treeview.selection_set(item_ID)

    def select_rows(self, indices):
        list_of_items = self._treeview.get_children()

        self._treeview.selection_set(" ".join(list_of_items[row_index] for row_index in indices))

    @property
    def selected_rows(self):
        data = []
        for item_ID in self._treeview.selection():
            data_row = self._item_ID_to_row_data(item_ID)
            data.append(data_row)
        
        return data

    def _on_select(self, event):
        for item_ID in event.widget.selection():
            data_row = self._item_ID_to_row_data(item_ID)
            self.on_select(data_row)

    def _item_ID_to_row_data(self, item_ID):
        item = self._treeview.item(item_ID)
        return item["values"]
    
    @property
    def data(self):
        data = []

        for item_ID in self._treeview.get_children():        
            data_row = self._item_ID_to_row_data(item_ID)
            data.append(data_row)

        return data
    
    @data.setter
    def data(self, data):
        self.update_table(data)

    def _sort_by(self, col, descending):
        """
        sort tree contents when a column header is clicked
        """
        # grab values to sort
        data = [(self._treeview.set(child_ID, col), child_ID) for child_ID in self._treeview.get_children('')]
        
        # if the data to be sorted is numeric change to float
        try:
            data = [(float(number), child_ID) for number, child_ID in data]
        except ValueError:
            pass

        # now sort the data in place
        data.sort(reverse=descending)
        for idx, item in enumerate(data):
            self._treeview.move(item[1], '', idx)
    
        # switch the heading so that it will sort in the opposite direction
        self._treeview.heading(col, command=lambda col=col: self._sort_by(col, not descending))
    
    def __getitem__(self, col):
        return [self._treeview.set(child_ID, col) for child_ID in self._treeview.get_children('')]
        
    def __setitem__(self, col, value):
        for child_ID, cell_data in zip(self._treeview.get_children(''), value):
            self._treeview.set(child_ID, col, cell_data)
        
    def bind(self, event, handler):
        self._treeview.bind(event, handler)

if __name__ == '__main__':
    try:
        from Tkinter import Tk
        import tkMessageBox as messagebox
    except ImportError:
        from tkinter import Tk
        from tkinter import messagebox

    root = Tk()
    
    def on_select(data):
        print(data)
        
    def show_info(msg):
        messagebox.showinfo("Table Data", msg)
        
    table = Multicolumn_Listbox(root, columns=["column one","column two", "column three"], command=on_select)
    table.pack(expand=True, fill=BOTH)
    
    table.add_row([1,2,3])
    show_info("""table.add_row([1,2,3])""")

    table.update_row(0, [4,5,6])
    show_info("""table.update_row(0, [4,5,6])""")
    
    table.update_table([[1,2,3], [4,5,6]])
    show_info("""table.update_table([1,2,3], [4,5,6])""")
    
    table.select_row(0)
    show_info("""table.select_row(0)""")

    print(table.selected_rows)
    print(table.data)
    
    print(table["column one"])
    table[1] = ["item1", "item2"]
    
    table.update_column(2, [8,9])
    show_info("""table.update_column(2, [8,9])""")
    
    table.clear()
    show_info("""table.clear()""")
    
    table.data = [[1,2,3], [4,5,6], [7,8,9]]
    show_info("""table.data = [[1,2,3], [4,5,6], [7,8,9]]""")

    root.mainloop()

Diff to Previous Revision

--- revision 6 2017-01-28 00:26:14
+++ revision 7 2017-02-11 23:22:46
@@ -1,33 +1,40 @@
-# Version: 0.4
+# Version: 0.5
 # Author: Miguel Martinez Lopez
 # Uncomment the next line to see my email
 # print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
 
 try:
-    from Tkinter import Frame, BOTH ,W, CENTER
+    from Tkinter import Frame, BOTH ,N,E,S, W, CENTER
     import tkFont as tkFont
-    from tk import Treeview
+    from ttk import Treeview, Scrollbar
 except ImportError:
-    from tkinter import Frame, BOTH ,W, CENTER
+    from tkinter import Frame, BOTH ,N,E,S, W, CENTER
     import tkinter.font as tkFont
-    from tkinter.ttk import Treeview
-
-class Multicolumn_Listbox(Frame):
-
-    def __init__(self, parent, columns, anchor_heading = CENTER, anchor_data = W, style=None, xscrollcommand=None, yscrollcommand=None, height=None, padding=None, select_mode=None, command=None, sort=True, adjust_heading_to_content=False):
-
-        Frame.__init__(self, parent, class_="Multicolumn_Listbox")
-            
+    from tkinter.ttk import Treeview, Scrollbar
+
+
+def make_autoscroll(sbar, first, last):
+    """Hide and show scrollbar as needed."""
+    first, last = float(first), float(last)
+    if first <= 0 and last >= 1:
+        sbar.grid_remove()
+    else:
+        sbar.grid()
+    sbar.set(first, last)
+
+class Multicolumn_Listbox(Frame, object):
+
+    def __init__(self, master, columns, anchor_heading = CENTER, anchor_data = W, style=None, autoscroll=True, vscrollbar=True, hscrollbar=False, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, select_mode=None, command=None, sort=True, adjust_heading_to_content=False):
+
+        Frame.__init__(self, master, class_="Multicolumn_Listbox")
+        
+        self.grid_rowconfigure(0, weight=1)
+        self.grid_columnconfigure(0, weight=1)
+
         treeview_kwargs = {}
         if style is not None:
             treeview_kwargs["style"] = style
-            
-        if xscrollcommand is not None:
-            treeview_kwargs["xscrollcommand"] = xscrollcommand
-            
-        if yscrollcommand is not None:
-            treeview_kwargs["yscrollcommand"] = yscrollcommand
-            
+
         if height is not None:
             treeview_kwargs["height"] = height
             
@@ -37,12 +44,37 @@
         if select_mode is not None:
             treeview_kwargs["selectmode"] = select_mode
 
-        self.treeview = Treeview(self,columns=columns, show="headings", **treeview_kwargs)
-        self.treeview.pack(expand=True, fill=BOTH)
+        self._treeview = Treeview(self,columns=columns, show="headings", **treeview_kwargs)
+        self._treeview.grid(row=0, column=0, sticky= N+E+W+S)
+        
+        scrollbar_kwargs = {}
+        if scrollbar_background is not None:
+            scrollbar_kwargs["background"] = scrollbar_background
+            
+        if scrollbar_troughcolor is not None:
+            scrollbar_kwargs["throughcolor"] = scrollbar_troughcolor
+
+        if vscrollbar:
+            self._vbar=Scrollbar(self,takefocus=0, command=self._treeview.yview, **scrollbar_kwargs)
+            self._vbar.grid(row=0, column=1, sticky= N+S)
+            
+            if autoscroll:
+                self._treeview.config(yscrollcommand=lambda f, l: make_autoscroll(self._vbar, f, l))
+            else:
+                self._treeview.config(yscrollcommand=self._vbar.set)
+
+        if hscrollbar:
+            self._hbar=Scrollbar(self,takefocus=0, command=self._treeview.xview, **scrollbar_kwargs)
+            self._hbar.grid(row=0, column=1, sticky= E+W)
+            
+            if autoscroll:
+                self._treeview.config(xscrollcommand=lambda f, l: make_autoscroll(self._hbar, f, l))
+            else:
+                self._treeview.config(xscrollcommand=self._hbar.set)
         
         if command is not None:
             self.on_select = command
-            self.treeview.bind("<<TreeviewSelect>>", self._on_select)
+            self._treeview.bind("<<TreeviewSelect>>", self._on_select)
         
         self._columns = columns
         self._number_of_columns = len(columns)
@@ -50,14 +82,14 @@
         for i in range(0, len(columns)):
 
             if sort:
-                self.treeview.heading(i, text=columns[i], command=lambda col=i: self._sort_by(col, descending=False))
+                self._treeview.heading(i, text=columns[i], command=lambda col=i: self._sort_by(col, descending=False))
             else:
-                self.treeview.heading(i, text=columns[i], anchor=anchor_heading)
+                self._treeview.heading(i, text=columns[i], anchor=anchor_heading)
                 
             if adjust_heading_to_content:
-                self.treeview.column(i, width=tkFont.Font().measure(columns[i]))
-
-            self.treeview.column(i, anchor=anchor_data)
+                self._treeview.column(i, width=tkFont.Font().measure(columns[i]))
+
+            self._treeview.column(i, anchor=anchor_data)
 
     def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None):
         kwargs = {}
@@ -66,49 +98,58 @@
             if config_value is not None:
                 kwargs[config_name] = config_value
             
-        self.treeview.column('#%s'%index, **kwargs)
+        self._treeview.column('#%s'%index, **kwargs)
 
     def get_row(self, index):
         try:
-            iid = self.treeview.get_children()[index]
+            item_ID = self._treeview.get_children()[index]
         except IndexError:
             raise ValueError("Index out of range: %d"%index)        
 
-        return self.treeview.item(self._iid_to_row_data(iid))
+        return self._treeview.item(self._item_ID_to_row_data(item_ID))
 
     def add_row(self, data, position="end"):
         if len(data) != self._number_of_columns:
-            raise ValueError("The table has %d"%self._number_of_columns)
-
-        self.treeview.insert('', position, values=data)
-            
-    def edit_row(self, index, data):
-        try:
-            item = self.treeview.get_children()[index]
+            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)
+
+        self._treeview.insert('', position, values=data)
+            
+    def update_row(self, index, data):
+        try:
+            item = self._treeview.get_children()[index]
         except IndexError:
             raise ValueError("Index out of range: %d"%index)
             
         if len(data) == len(self._columns):
-            self.treeview.item(item, values=data)
+            self._treeview.item(item, values=data)
         else:
-            raise ValueError("The table has only %d columns!"%len(self._columns))
+            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)
             
     def delete_row(self, index):
         try:
-            item = self.treeview.get_children()[index]
+            item_ID = self._treeview.get_children()[index]
         except IndexError:
             raise ValueError("Index out of range: %d"%index)
         
-        self.treeview.delete(item)
-        
+        self._treeview.delete(item_ID)
+        
+    def update_column(self, index, data):
+        for i, item_ID in enumerate(self._treeview.get_children()): 
+            data_row = self._item_ID_to_row_data(item_ID)
+            data_row[index] = data[i]
+
+            self._treeview.item(item_ID, values=data_row)
+
+        return data
+
     def clear(self):
         # Another possibility:
-        #  self.treeview.delete(*self.treeview.get_children())
-
-        for row in self.treeview.get_children():
-            self.treeview.delete(row)
-            
-    def update(self, *data):
+        #  self._treeview.delete(*self._treeview.get_children())
+
+        for row in self._treeview.get_children():
+            self._treeview.delete(row)
+            
+    def update_table(self, data):
         self.clear()
 
         for row in data:
@@ -116,94 +157,109 @@
             
     def focus(self, row=None):
         if row is None:
-            return self.treeview.item(self.treeview.focus())
+            return self._treeview.item(self._treeview.focus())
         else:
-            item = self.treeview.get_children()[row]
-            self.treeview.focus(item)
+            item = self._treeview.get_children()[row]
+            self._treeview.focus(item)
 
     def state(self, state=None):
         if stateSpec is None:
-            return self.treeview.state()
+            return self._treeview.state()
         else:
-            self.treeview.state(state)
+            self._treeview.state(state)
     
     def xview(self, *args):
-        self.treeview.xview(*args)
+        self._treeview.xview(*args)
 
     def yview(self):
-        self.treeview.yview(*args)
+        self._treeview.yview(*args)
 
     @property
     def number_of_rows(self):
-        return len(self.treeview.get_children())
+        return len(self._treeview.get_children())
         
     @property
     def number_of_columns(self):
         return self._number_of_columns
         
-    def select(self, *rows):
-        if len(rows) ==0:
-            return self.selected_rows
-        else:
-            list_of_items = self.treeview.get_children()
-            self.treeview.selection_set(*[list_of_items[row] for row in rows])
+    def select_row(self, index):
+        list_of_items = self._treeview.get_children()
+        
+        try:
+            item_ID = list_of_items[index]
+        except IndexError:
+            raise ValueError("Index out of range: %d"%index)
+
+        self._treeview.selection_set(item_ID)
+
+    def select_rows(self, indices):
+        list_of_items = self._treeview.get_children()
+
+        self._treeview.selection_set(" ".join(list_of_items[row_index] for row_index in indices))
 
     @property
     def selected_rows(self):
         data = []
-        for iid in self.treeview.selection():
-            data_row = self._iid_to_row_data(iid)
+        for item_ID in self._treeview.selection():
+            data_row = self._item_ID_to_row_data(item_ID)
             data.append(data_row)
         
         return data
 
     def _on_select(self, event):
-        for iid in event.widget.selection():
-            data_row = self._iid_to_row_data(iid)
+        for item_ID in event.widget.selection():
+            data_row = self._item_ID_to_row_data(item_ID)
             self.on_select(data_row)
 
-    def _iid_to_row_data(self, iid):
-        item = self.treeview.item(iid)
+    def _item_ID_to_row_data(self, item_ID):
+        item = self._treeview.item(item_ID)
         return item["values"]
     
     @property
     def data(self):
         data = []
 
-        for iid in self.treeview.get_children():        
-            data_row = self._iid_to_row_data(iid)
+        for item_ID in self._treeview.get_children():        
+            data_row = self._item_ID_to_row_data(item_ID)
             data.append(data_row)
 
         return data
+    
+    @data.setter
+    def data(self, data):
+        self.update_table(data)
 
     def _sort_by(self, col, descending):
         """
         sort tree contents when a column header is clicked
         """
         # grab values to sort
-        data = [(self.treeview.set(child_iid, col), child_iid) for child_iid in self.treeview.get_children('')]
+        data = [(self._treeview.set(child_ID, col), child_ID) for child_ID in self._treeview.get_children('')]
         
         # if the data to be sorted is numeric change to float
         try:
-            data = [(float(number), child_iid) for number, child_iid in data]
+            data = [(float(number), child_ID) for number, child_ID in data]
         except ValueError:
             pass
 
         # now sort the data in place
         data.sort(reverse=descending)
         for idx, item in enumerate(data):
-            self.treeview.move(item[1], '', idx)
+            self._treeview.move(item[1], '', idx)
     
         # switch the heading so that it will sort in the opposite direction
-        self.treeview.heading(col, command=lambda col=col: self._sort_by(col, not descending))
+        self._treeview.heading(col, command=lambda col=col: self._sort_by(col, not descending))
     
     def __getitem__(self, col):
-        return [self.treeview.set(child_iid, col) for child_iid in self.treeview.get_children('')]
+        return [self._treeview.set(child_ID, col) for child_ID in self._treeview.get_children('')]
         
     def __setitem__(self, col, value):
-        for child_iid, cell_data in zip(self.treeview.get_children(''), value):
-            self.treeview.set(child_iid, col, cell_data)
-        
+        for child_ID, cell_data in zip(self._treeview.get_children(''), value):
+            self._treeview.set(child_ID, col, cell_data)
+        
+    def bind(self, event, handler):
+        self._treeview.bind(event, handler)
+
 if __name__ == '__main__':
     try:
         from Tkinter import Tk
@@ -220,25 +276,34 @@
     def show_info(msg):
         messagebox.showinfo("Table Data", msg)
         
-    simple_table = Multicolumn_Listbox(root, ["column one","column two", "column three"], command=on_select)
-    simple_table.pack()
-    
-    simple_table.add_row([1,2,3])
-    show_info("""simple_table.add_row([1,2,3])""")
-
-    simple_table.edit_row(0, [4,5,6])
-    show_info("""simple_table.edit_row(0, [4,5,6])""")
-    
-    simple_table.update([1,2,3], [4,5,6])
-    show_info("""simple_table.edit([1,2,3], [4,5,6])""")
-    
-    simple_table.select(0)
-    show_info("""simple_table.select(0)""")
-
-    print(simple_table.selected_rows)
-    print(simple_table.data)
-    
-    print(simple_table["column one"])
-    simple_table[1] = ["item1", "item2"]
-    
+    table = Multicolumn_Listbox(root, columns=["column one","column two", "column three"], command=on_select)
+    table.pack(expand=True, fill=BOTH)
+    
+    table.add_row([1,2,3])
+    show_info("""table.add_row([1,2,3])""")
+
+    table.update_row(0, [4,5,6])
+    show_info("""table.update_row(0, [4,5,6])""")
+    
+    table.update_table([[1,2,3], [4,5,6]])
+    show_info("""table.update_table([1,2,3], [4,5,6])""")
+    
+    table.select_row(0)
+    show_info("""table.select_row(0)""")
+
+    print(table.selected_rows)
+    print(table.data)
+    
+    print(table["column one"])
+    table[1] = ["item1", "item2"]
+    
+    table.update_column(2, [8,9])
+    show_info("""table.update_column(2, [8,9])""")
+    
+    table.clear()
+    show_info("""table.clear()""")
+    
+    table.data = [[1,2,3], [4,5,6], [7,8,9]]
+    show_info("""table.data = [[1,2,3], [4,5,6], [7,8,9]]""")
+
     root.mainloop()

History