# 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()