# Version: 0.6
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
import platform
try:
from Tkinter import Frame, BOTH ,N,E,S, W, END, CENTER, Entry
import tkFont as tkFont
from ttk import Treeview, Scrollbar, Style
except ImportError:
from tkinter import Frame, BOTH ,N,E,S, W, END, CENTER, Entry
import tkinter.font as tkFont
from tkinter.ttk import Treeview, Scrollbar, Style
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)
OS = platform.system()
class Table_Row(object):
def __init__(self, multicolumn_listbox):
self._multicolumn_listbox = multicolumn_listbox
def get(self, index):
return self._multicolumn_listbox.get_row(index)
def add(self, data, index=END):
self._multicolumn_listbox.add_row(data, index)
def delete(self, index):
self._multicolumn_listbox.delete_row(index)
def update(self, index, data):
self._multicolumn_listbox.update_row(index, data)
def select(self, index):
self._multicolumn_listbox.select_row(index)
def deselect(self, index):
self._multicolumn_listbox.deselect_row(index)
def set_selection(self, indices):
self._multicolumn_listbox.set_selection(indices)
def __getitem__(self, index):
return self._multicolumn_listbox.get_row(index)
def __setitem__(self, index, value):
return self._multicolumn_listbox.update_row(index, value)
def __delitem__(self, index):
self._multicolumn_listbox.delete_row(index)
def __len__(self):
return self._multicolumn_listbox.number_of_rows
class Table_Column(object):
def __init__(self, multicolumn_listbox):
self._multicolumn_listbox = multicolumn_listbox
def get(self, index):
return self._multicolumn_listbox.get_column(index)
def add(self, data, index=END):
self._multicolumn_listbox.add_column(data, index)
def delete(self, index):
self._multicolumn_listbox.delete_column(index)
def update(self, index, data):
self._multicolumn_listbox.update_column(index, data)
def __getitem__(self, index):
return self._multicolumn_listbox.get_column(index)
def __setitem__(self, index, value):
return self._multicolumn_listbox.update_column(index, value)
def __delitem__(self, index):
self._multicolumn_listbox.delete_column(index)
def __len__(self):
return self._multicolumn_listbox.number_of_columns
def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1):
if not scrolled_widget:
binding_widget = scrolled_widget
view_command = getattr(scrolled_widget, orient+'view')
if OS == 'Linux':
if callback:
def onMouseWheel(event):
if event.num == 4:
view_command("scroll",(-1)*factor,"units" )
elif event.num == 5:
view_command("scroll",factor,"units" )
callback()
else:
def onMouseWheel(event):
if event.num == 4:
view_command("scroll",(-1)*factor,"units" )
elif event.num == 5:
view_command("scroll",factor,"units" )
elif OS == 'Windows':
if callback:
def onMouseWheel(event):
view_command("scroll",(-1)*int((event.delta/120)*factor),"units" )
callback()
else:
def onMouseWheel(event):
view_command("scroll",(-1)*int((event.delta/120)*factor),"units" )
elif OS == 'Darwin':
if callback:
def onMouseWheel(event):
view_command("scroll",event.delta,"units" )
callback()
else:
def onMouseWheel(event):
view_command("scroll",event.delta,"units" )
if OS == "Linux" :
binding_widget.bind('<4>', onMouseWheel, add='+')
binding_widget.bind('<5>', onMouseWheel, add='+')
else:
# Windows and MacOS
binding_widget.bind("<MouseWheel>", onMouseWheel, add='+')
return onMouseWheel
class Multicolumn_Listbox(Frame, object):
_style_index = 0
def __init__(self, master, columns, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, anchor_heading = CENTER, anchor_data = W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, entry_background="#1BA1E2", selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, fieldbackground=None, heading_font= None, heading_background=None, heading_foreground=None):
Frame.__init__(self, master, class_="Multicolumn_Listbox")
self._stripped_rows = stripped_rows
self._entry_background = entry_background
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
treeview_kwargs = {}
s = Style()
if style is None:
style_name = "Multicolumn_Listbox%s.Treeview"%self._style_index
self._style_index += 1
else:
style_name = style
treeview_kwargs["style"] = style_name
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
style_map = {}
if selection_background is not None:
style_map["background"] = [('selected', selection_background)]
if selection_foreground is not None:
style_map["foeground"] = [('selected', selection_foreground)]
if style_map is not None:
s.map(style_name, **style_map)
style_config = {}
if background is not None:
style_config["background"] = background
if foreground is not None:
style_config["foreground"] = foreground
if font is not None:
style_config["font"] = font
if fieldbackground is not None:
style_config["fieldbackground"] = fieldbackground
s.configure(style_name, **style_config)
heading_style_config = {}
if heading_font is not None:
heading_style_config["font"] = heading_font
if heading_background is not None:
heading_style_config["background"] = heading_background
if heading_foreground is not None:
heading_style_config["foreground"] = heading_foreground
heading_style_name = style_name + ".Heading"
s.configure(heading_style_name, **heading_style_config)
self._treeview = Treeview(self,columns=columns, show="headings", **treeview_kwargs)
self._treeview.grid(row=0, column=0, sticky= N+E+W+S)
if command is not None:
self._command = command
self._treeview.bind("<<TreeviewSelect>>", self._on_select)
if editable:
self._selected_cell = None
self._entry_popup = None
self._treeview.bind("<1>", self._edit_cell)
def configure(event):
"""
if self._entry_popup:
self._entry_popup.destroy()
return
"""
self._treeview.update_idletasks()
self._update_position_of_entry()
self._treeview.bind("<Configure>", configure)
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:
if editable:
yview_command = self._yview
else:
yview_command = self._treeview.yview
self._vbar=Scrollbar(self,takefocus=0, command=yview_command, **scrollbar_kwargs)
self._vbar.grid(row=0, column=1, sticky= N+S)
if autoscroll:
if editable:
def yscrollcommand(f,l, self=self):
make_autoscroll(self._vbar, f, l)
self._update_position_of_entry()
else:
def yscrollcommand(f,l, vbar=self._vbar):
make_autoscroll(vbar, f, l)
self._treeview.config(yscrollcommand=yscrollcommand)
else:
self._treeview.config(yscrollcommand=self._vbar.set)
if hscrollbar:
if editable:
xview_command = self._xview
else:
xview_command = self._treeview.xview
self._hbar=Scrollbar(self,takefocus=0, command=xview_command, **scrollbar_kwargs)
self._hbar.grid(row=0, column=1, sticky= E+W)
if autoscroll:
if editable:
def xscrollcommand(f,l, self=self):
make_autoscroll(self._hbar, f, l)
self._update_position_of_entry()
else:
def xscrollcommand(f,l, hbar=self._hbar):
make_autoscroll(hbar, f, l)
self._treeview.config(xscrollcommand=xscrollcommand)
else:
self._treeview.config(xscrollcommand=self._hbar.set)
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)
self.row = Table_Row(self)
self.column = Table_Column(self)
# No funciona la rueda encima del entry
def _edit_cell(self, event):
'''Executed, when a row is clicked. Opens an entry popup above the cell, so it is possible
to select text '''
# close previous popups
if self._entry_popup:
self._entry_popup.destroy()
# what row and column was clicked on
item_ID = self._treeview.identify_row(event.y)
if not item_ID: return
column = self._treeview.identify_column(event.x)
# get column position info
x,y,width,height = self._treeview.bbox(item_ID, column)
# place Entry popup properly
column_number = int(column[1:])-1
data = self._item_ID_to_row_data(item_ID)[column_number]
self._entry_popup = Entry(self._treeview, exportselection=True, selectbackground=self._entry_background)
self._entry_popup.place(x=x, y=y, width=width, height=height)
self._entry_popup.insert(0, data)
self._entry_popup.focus_force()
self._entry_popup.bind("<Control-a>", self._on_entry_data_selection)
self._entry_popup.bind("<Escape>", lambda event: self._destroy_entry())
# No funciona bien udpate position
bind_function_onMouseWheel(self._treeview, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry)
#self.bind('<Unmap>', lambda *ignore: self.destroy())
self._entry_popup.bind("<Return>", lambda event, itemd_ID=item_ID, column_number=column_number: self._update_cell(item_ID, column_number))
self._selected_cell = item_ID, column
def _on_entry_data_selection(self, event):
''' Set selection on the whole text '''
self._entry_popup.selection_range(0, 'end')
# returns 'break' to interrupt default key-bindings
return 'break'
def _destroy_entry(self):
self._entry_popup.destroy()
self._entry_popup = None
self._selected_cell = None
def _update_cell(self, item_ID, column_number):
data = self._entry_popup.get()
row_data = self._item_ID_to_row_data(item_ID)
row_data[column_number] = data
self._treeview.item(item_ID, values=row_data)
self._destroy_entry()
def _xview(self, *args):
self._treeview.xview(*args)
self._update_position_of_entry()
def _yview(self, *args):
self._treeview.yview(*args)
self._update_position_of_entry()
def _update_position_of_entry(self):
if self._selected_cell:
bbox = self._treeview.bbox(*self._selected_cell)
if bbox == "":
self._entry_popup.place_forget()
else:
x,y,width,height = bbox
self._entry_popup.place(x=x, y=y, width=width, height=height)
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 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):
list_of_items = self._treeview.get_children()
number_of_rows = len(list_of_items)
if index == END:
index = number_of_rows-1
elif index < 0:
index = 0
elif index >= number_of_rows:
index = number_of_rows-1
item_ID = list_of_items[index]
self._treeview.delete(item_ID)
if self._stripped_rows:
for i in range(index+1, number_of_rows):
self._treeview.tag_configure(list_of_items[i], background=self._stripped_rows[(i-1)%2])
def add_row(self, data, index=END):
if len(data) != self._number_of_columns:
raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)
item_ID = self._treeview.insert('', index, values=data)
self._treeview.item(item_ID, tags=item_ID)
if self._stripped_rows:
list_of_items = self._treeview.get_children()
number_of_rows = len(list_of_items)
if index == END:
index = number_of_rows-1
elif index < 0:
index = 0
elif index >= number_of_rows:
index = number_of_rows-1
self._treeview.tag_configure(item_ID, background=self._stripped_rows[index%2])
for i in range(index+1, number_of_rows):
self._treeview.tag_configure(list_of_items[i], background=self._stripped_rows[(i+1)%2])
def get_column(self, index):
return [self._treeview.set(child_ID, index) for child_ID in self._treeview.get_children('')]
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)
@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_add(item_ID)
def deselect_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_remove(item_ID)
def set_selection(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._command(data_row)
def _item_ID_to_row_data(self, item_ID):
item = self._treeview.item(item_ID)
return item["values"]
@property
def table_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
@table_data.setter
def table_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, index):
if isinstance(index, tuple):
row, column = index
return self._treeview.set(self._treeview.get_children('')[row], column)
else:
raise Exception("Row and column indices are required")
def __setitem__(self, index, value):
row, column = index
item_ID = self._treeview.get_children()[row]
data = self._item_ID_to_row_data(item_ID)
data[column] = value
self._treeview.item(item_ID, values=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.table_data)
print(table[0,1])
table.column[1] = ["item1", "item2"]
table.update_column(2, [8,9])
show_info("table.update_column(2, [8,9])")
table.clear()
show_info("table.clear()")
table.table_data = [[1,2,3], [4,5,6], [7,8,9]]
show_info("table.table_data = [[1,2,3], [4,5,6], [7,8,9]]")
table.destroy()
# The next table is editable: Click on the table to edit the cell
table = Multicolumn_Listbox(root, columns=["column one","column two", "column three"], stripped_rows = ("white","#f2f2f2"), select_mode="none")
table.pack(expand=True, fill=BOTH)
table.table_data = [[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11],
[12, 13, 14],
[15, 16, 17],
[18, 19, 20],
[21, 22, 23],
[24, 25, 26],
[27, 28, 29]]
table.add_row([1,2,3])
show_info("We create an editable (click on a cell) and zebra style table")
root.mainloop()
Diff to Previous Revision
--- revision 7 2017-02-11 23:22:46
+++ revision 8 2017-02-20 23:30:56
@@ -1,17 +1,18 @@
-# Version: 0.5
+# Version: 0.6
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
+import platform
+
try:
- from Tkinter import Frame, BOTH ,N,E,S, W, CENTER
+ from Tkinter import Frame, BOTH ,N,E,S, W, END, CENTER, Entry
import tkFont as tkFont
- from ttk import Treeview, Scrollbar
+ from ttk import Treeview, Scrollbar, Style
except ImportError:
- from tkinter import Frame, BOTH ,N,E,S, W, CENTER
+ from tkinter import Frame, BOTH ,N,E,S, W, END, CENTER, Entry
import tkinter.font as tkFont
- from tkinter.ttk import Treeview, Scrollbar
-
+ from tkinter.ttk import Treeview, Scrollbar, Style
def make_autoscroll(sbar, first, last):
"""Hide and show scrollbar as needed."""
@@ -22,18 +23,146 @@
sbar.grid()
sbar.set(first, last)
+OS = platform.system()
+
+
+class Table_Row(object):
+ def __init__(self, multicolumn_listbox):
+ self._multicolumn_listbox = multicolumn_listbox
+
+ def get(self, index):
+ return self._multicolumn_listbox.get_row(index)
+
+ def add(self, data, index=END):
+ self._multicolumn_listbox.add_row(data, index)
+
+ def delete(self, index):
+ self._multicolumn_listbox.delete_row(index)
+
+ def update(self, index, data):
+ self._multicolumn_listbox.update_row(index, data)
+
+ def select(self, index):
+ self._multicolumn_listbox.select_row(index)
+
+ def deselect(self, index):
+ self._multicolumn_listbox.deselect_row(index)
+
+ def set_selection(self, indices):
+ self._multicolumn_listbox.set_selection(indices)
+
+ def __getitem__(self, index):
+ return self._multicolumn_listbox.get_row(index)
+
+ def __setitem__(self, index, value):
+ return self._multicolumn_listbox.update_row(index, value)
+
+ def __delitem__(self, index):
+ self._multicolumn_listbox.delete_row(index)
+
+ def __len__(self):
+ return self._multicolumn_listbox.number_of_rows
+
+class Table_Column(object):
+ def __init__(self, multicolumn_listbox):
+ self._multicolumn_listbox = multicolumn_listbox
+
+ def get(self, index):
+ return self._multicolumn_listbox.get_column(index)
+
+ def add(self, data, index=END):
+ self._multicolumn_listbox.add_column(data, index)
+
+ def delete(self, index):
+ self._multicolumn_listbox.delete_column(index)
+
+ def update(self, index, data):
+ self._multicolumn_listbox.update_column(index, data)
+
+ def __getitem__(self, index):
+ return self._multicolumn_listbox.get_column(index)
+
+ def __setitem__(self, index, value):
+ return self._multicolumn_listbox.update_column(index, value)
+
+ def __delitem__(self, index):
+ self._multicolumn_listbox.delete_column(index)
+
+ def __len__(self):
+ return self._multicolumn_listbox.number_of_columns
+
+def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1):
+ if not scrolled_widget:
+ binding_widget = scrolled_widget
+
+ view_command = getattr(scrolled_widget, orient+'view')
+
+ if OS == 'Linux':
+ if callback:
+ def onMouseWheel(event):
+ if event.num == 4:
+ view_command("scroll",(-1)*factor,"units" )
+ elif event.num == 5:
+ view_command("scroll",factor,"units" )
+
+ callback()
+ else:
+ def onMouseWheel(event):
+ if event.num == 4:
+ view_command("scroll",(-1)*factor,"units" )
+ elif event.num == 5:
+ view_command("scroll",factor,"units" )
+
+ elif OS == 'Windows':
+ if callback:
+ def onMouseWheel(event):
+ view_command("scroll",(-1)*int((event.delta/120)*factor),"units" )
+ callback()
+ else:
+ def onMouseWheel(event):
+ view_command("scroll",(-1)*int((event.delta/120)*factor),"units" )
+
+ elif OS == 'Darwin':
+ if callback:
+ def onMouseWheel(event):
+ view_command("scroll",event.delta,"units" )
+ callback()
+ else:
+ def onMouseWheel(event):
+ view_command("scroll",event.delta,"units" )
+
+ if OS == "Linux" :
+ binding_widget.bind('<4>', onMouseWheel, add='+')
+ binding_widget.bind('<5>', onMouseWheel, add='+')
+ else:
+ # Windows and MacOS
+ binding_widget.bind("<MouseWheel>", onMouseWheel, add='+')
+
+ return onMouseWheel
+
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):
+ _style_index = 0
+
+ def __init__(self, master, columns, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, anchor_heading = CENTER, anchor_data = W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, entry_background="#1BA1E2", selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, fieldbackground=None, heading_font= None, heading_background=None, heading_foreground=None):
Frame.__init__(self, master, class_="Multicolumn_Listbox")
+
+ self._stripped_rows = stripped_rows
+ self._entry_background = entry_background
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
treeview_kwargs = {}
- if style is not None:
- treeview_kwargs["style"] = style
+ s = Style()
+
+ if style is None:
+ style_name = "Multicolumn_Listbox%s.Treeview"%self._style_index
+ self._style_index += 1
+ else:
+ style_name = style
+
+ treeview_kwargs["style"] = style_name
if height is not None:
treeview_kwargs["height"] = height
@@ -43,10 +172,68 @@
if select_mode is not None:
treeview_kwargs["selectmode"] = select_mode
-
+
+ style_map = {}
+ if selection_background is not None:
+ style_map["background"] = [('selected', selection_background)]
+
+ if selection_foreground is not None:
+ style_map["foeground"] = [('selected', selection_foreground)]
+
+ if style_map is not None:
+ s.map(style_name, **style_map)
+
+ style_config = {}
+ if background is not None:
+ style_config["background"] = background
+
+ if foreground is not None:
+ style_config["foreground"] = foreground
+
+ if font is not None:
+ style_config["font"] = font
+
+ if fieldbackground is not None:
+ style_config["fieldbackground"] = fieldbackground
+
+ s.configure(style_name, **style_config)
+
+ heading_style_config = {}
+ if heading_font is not None:
+ heading_style_config["font"] = heading_font
+ if heading_background is not None:
+ heading_style_config["background"] = heading_background
+ if heading_foreground is not None:
+ heading_style_config["foreground"] = heading_foreground
+
+ heading_style_name = style_name + ".Heading"
+ s.configure(heading_style_name, **heading_style_config)
+
self._treeview = Treeview(self,columns=columns, show="headings", **treeview_kwargs)
self._treeview.grid(row=0, column=0, sticky= N+E+W+S)
-
+
+ if command is not None:
+ self._command = command
+ self._treeview.bind("<<TreeviewSelect>>", self._on_select)
+
+ if editable:
+ self._selected_cell = None
+ self._entry_popup = None
+
+ self._treeview.bind("<1>", self._edit_cell)
+
+ def configure(event):
+ """
+ if self._entry_popup:
+ self._entry_popup.destroy()
+ return
+ """
+
+ self._treeview.update_idletasks()
+ self._update_position_of_entry()
+
+ self._treeview.bind("<Configure>", configure)
+
scrollbar_kwargs = {}
if scrollbar_background is not None:
scrollbar_kwargs["background"] = scrollbar_background
@@ -55,27 +242,49 @@
scrollbar_kwargs["throughcolor"] = scrollbar_troughcolor
if vscrollbar:
- self._vbar=Scrollbar(self,takefocus=0, command=self._treeview.yview, **scrollbar_kwargs)
+ if editable:
+ yview_command = self._yview
+ else:
+ yview_command = self._treeview.yview
+
+ self._vbar=Scrollbar(self,takefocus=0, command=yview_command, **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))
+ if editable:
+ def yscrollcommand(f,l, self=self):
+ make_autoscroll(self._vbar, f, l)
+ self._update_position_of_entry()
+ else:
+ def yscrollcommand(f,l, vbar=self._vbar):
+ make_autoscroll(vbar, f, l)
+
+ self._treeview.config(yscrollcommand=yscrollcommand)
else:
self._treeview.config(yscrollcommand=self._vbar.set)
if hscrollbar:
- self._hbar=Scrollbar(self,takefocus=0, command=self._treeview.xview, **scrollbar_kwargs)
+ if editable:
+ xview_command = self._xview
+ else:
+ xview_command = self._treeview.xview
+
+ self._hbar=Scrollbar(self,takefocus=0, command=xview_command, **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))
+ if editable:
+ def xscrollcommand(f,l, self=self):
+ make_autoscroll(self._hbar, f, l)
+ self._update_position_of_entry()
+ else:
+ def xscrollcommand(f,l, hbar=self._hbar):
+ make_autoscroll(hbar, f, l)
+
+ self._treeview.config(xscrollcommand=xscrollcommand)
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)
@@ -90,6 +299,88 @@
self._treeview.column(i, width=tkFont.Font().measure(columns[i]))
self._treeview.column(i, anchor=anchor_data)
+
+ self.row = Table_Row(self)
+ self.column = Table_Column(self)
+
+ # No funciona la rueda encima del entry
+ def _edit_cell(self, event):
+ '''Executed, when a row is clicked. Opens an entry popup above the cell, so it is possible
+ to select text '''
+
+ # close previous popups
+ if self._entry_popup:
+ self._entry_popup.destroy()
+
+ # what row and column was clicked on
+ item_ID = self._treeview.identify_row(event.y)
+ if not item_ID: return
+
+ column = self._treeview.identify_column(event.x)
+
+ # get column position info
+ x,y,width,height = self._treeview.bbox(item_ID, column)
+
+ # place Entry popup properly
+ column_number = int(column[1:])-1
+ data = self._item_ID_to_row_data(item_ID)[column_number]
+
+
+ self._entry_popup = Entry(self._treeview, exportselection=True, selectbackground=self._entry_background)
+ self._entry_popup.place(x=x, y=y, width=width, height=height)
+
+ self._entry_popup.insert(0, data)
+ self._entry_popup.focus_force()
+
+ self._entry_popup.bind("<Control-a>", self._on_entry_data_selection)
+ self._entry_popup.bind("<Escape>", lambda event: self._destroy_entry())
+
+ # No funciona bien udpate position
+ bind_function_onMouseWheel(self._treeview, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry)
+ #self.bind('<Unmap>', lambda *ignore: self.destroy())
+
+ self._entry_popup.bind("<Return>", lambda event, itemd_ID=item_ID, column_number=column_number: self._update_cell(item_ID, column_number))
+
+ self._selected_cell = item_ID, column
+
+ def _on_entry_data_selection(self, event):
+ ''' Set selection on the whole text '''
+ self._entry_popup.selection_range(0, 'end')
+
+ # returns 'break' to interrupt default key-bindings
+ return 'break'
+
+ def _destroy_entry(self):
+ self._entry_popup.destroy()
+
+ self._entry_popup = None
+ self._selected_cell = None
+
+ def _update_cell(self, item_ID, column_number):
+ data = self._entry_popup.get()
+
+ row_data = self._item_ID_to_row_data(item_ID)
+ row_data[column_number] = data
+ self._treeview.item(item_ID, values=row_data)
+
+ self._destroy_entry()
+
+ def _xview(self, *args):
+ self._treeview.xview(*args)
+ self._update_position_of_entry()
+
+ def _yview(self, *args):
+ self._treeview.yview(*args)
+ self._update_position_of_entry()
+
+ def _update_position_of_entry(self):
+ if self._selected_cell:
+ bbox = self._treeview.bbox(*self._selected_cell)
+ if bbox == "":
+ self._entry_popup.place_forget()
+ else:
+ x,y,width,height = bbox
+ self._entry_popup.place(x=x, y=y, width=width, height=height)
def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None):
kwargs = {}
@@ -107,12 +398,6 @@
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:
@@ -124,15 +409,53 @@
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)
-
+ list_of_items = self._treeview.get_children()
+ number_of_rows = len(list_of_items)
+
+ if index == END:
+ index = number_of_rows-1
+ elif index < 0:
+ index = 0
+ elif index >= number_of_rows:
+ index = number_of_rows-1
+
+ item_ID = list_of_items[index]
+
self._treeview.delete(item_ID)
-
+
+ if self._stripped_rows:
+
+ for i in range(index+1, number_of_rows):
+ self._treeview.tag_configure(list_of_items[i], background=self._stripped_rows[(i-1)%2])
+
+ def add_row(self, data, index=END):
+ if len(data) != self._number_of_columns:
+ raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)
+
+ item_ID = self._treeview.insert('', index, values=data)
+ self._treeview.item(item_ID, tags=item_ID)
+
+ if self._stripped_rows:
+ list_of_items = self._treeview.get_children()
+ number_of_rows = len(list_of_items)
+
+ if index == END:
+ index = number_of_rows-1
+ elif index < 0:
+ index = 0
+ elif index >= number_of_rows:
+ index = number_of_rows-1
+
+ self._treeview.tag_configure(item_ID, background=self._stripped_rows[index%2])
+
+ for i in range(index+1, number_of_rows):
+ self._treeview.tag_configure(list_of_items[i], background=self._stripped_rows[(i+1)%2])
+
+ def get_column(self, index):
+ return [self._treeview.set(child_ID, index) for child_ID in self._treeview.get_children('')]
+
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)
@@ -167,12 +490,6 @@
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):
@@ -190,9 +507,19 @@
except IndexError:
raise ValueError("Index out of range: %d"%index)
- self._treeview.selection_set(item_ID)
-
- def select_rows(self, indices):
+ self._treeview.selection_add(item_ID)
+
+ def deselect_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_remove(item_ID)
+
+ def set_selection(self, indices):
list_of_items = self._treeview.get_children()
self._treeview.selection_set(" ".join(list_of_items[row_index] for row_index in indices))
@@ -209,14 +536,14 @@
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)
+ self._command(data_row)
def _item_ID_to_row_data(self, item_ID):
item = self._treeview.item(item_ID)
return item["values"]
@property
- def data(self):
+ def table_data(self):
data = []
for item_ID in self._treeview.get_children():
@@ -225,8 +552,8 @@
return data
- @data.setter
- def data(self, data):
+ @table_data.setter
+ def table_data(self, data):
self.update_table(data)
def _sort_by(self, col, descending):
@@ -250,13 +577,22 @@
# 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 __getitem__(self, index):
+ if isinstance(index, tuple):
+ row, column = index
+ return self._treeview.set(self._treeview.get_children('')[row], column)
+ else:
+ raise Exception("Row and column indices are required")
+
+ def __setitem__(self, index, value):
+ row, column = index
+ item_ID = self._treeview.get_children()[row]
+
+ data = self._item_ID_to_row_data(item_ID)
+
+ data[column] = value
+ self._treeview.item(item_ID, values=data)
+
def bind(self, event, handler):
self._treeview.bind(event, handler)
@@ -280,30 +616,51 @@
table.pack(expand=True, fill=BOTH)
table.add_row([1,2,3])
- show_info("""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])""")
+ 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])""")
+ show_info("table.update_table([1,2,3], [4,5,6])")
table.select_row(0)
- show_info("""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"]
-
+ print(table.table_data)
+
+ print(table[0,1])
+ table.column[1] = ["item1", "item2"]
+
table.update_column(2, [8,9])
- show_info("""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]]""")
-
+ show_info("table.clear()")
+
+ table.table_data = [[1,2,3], [4,5,6], [7,8,9]]
+ show_info("table.table_data = [[1,2,3], [4,5,6], [7,8,9]]")
+
+ table.destroy()
+
+ # The next table is editable: Click on the table to edit the cell
+ table = Multicolumn_Listbox(root, columns=["column one","column two", "column three"], stripped_rows = ("white","#f2f2f2"), select_mode="none")
+ table.pack(expand=True, fill=BOTH)
+
+
+ table.table_data = [[0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11],
+ [12, 13, 14],
+ [15, 16, 17],
+ [18, 19, 20],
+ [21, 22, 23],
+ [24, 25, 26],
+ [27, 28, 29]]
+
+ table.add_row([1,2,3])
+
+ show_info("We create an editable (click on a cell) and zebra style table")
root.mainloop()