# Version: 2.0
# 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, Canvas, Label
from tkFont import Font, nametofont
from ttk import Treeview, Scrollbar, Style
except ImportError:
from tkinter import Frame, BOTH ,N,E,S, W, END, CENTER, Entry, Canvas, Label
from tkinter.font import Font, nametofont
from tkinter.ttk import Treeview, Scrollbar, Style
# Python 3 compatibility
try:
basestring
except NameError:
basestring = str
class Row(object):
def __init__(self, table, index):
self._multicolumn_listbox = table
self._index = index
def data(self):
return self._multicolumn_listbox.row_data(self._index)
def delete(self):
self._multicolumn_listbox.delete_row(self._index)
def update(self, data):
self._multicolumn_listbox.update_row(self._index, data)
def select(self):
self._multicolumn_listbox.select_row(self._index)
def deselect(self):
self._multicolumn_listbox.deselect_row(self._index)
def __str__(self):
return str(self.data())
def __len__(self):
return self._multicolumn_listbox.number_of_columns
class Column(object):
def __init__(self, table, index):
self._multicolumn_listbox = table
self._index = index
def data(self):
return self._multicolumn_listbox.column_data(self._index)
def delete(self):
self._multicolumn_listbox.delete_column(self._index)
def update(self, data):
self._multicolumn_listbox.update_column(self._index, data)
def __str__(self):
return str(self.data())
def __len__(self):
return self._multicolumn_listbox.number_of_rows
class Multicolumn_Listbox(object):
_style_index = 0
class List_Of_Rows(object):
def __init__(self, multicolumn_listbox):
self._multicolumn_listbox = multicolumn_listbox
def data(self, index):
return self._multicolumn_listbox.row_data(index)
def get(self, index):
return Row(self._multicolumn_listbox, index)
def insert(self, data, index=END):
self._multicolumn_listbox.insert_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.get(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 List_Of_Columns(object):
def __init__(self, multicolumn_listbox):
self._multicolumn_listbox = multicolumn_listbox
def data(self, index):
return self._multicolumn_listbox.get_column(index)
def get(self, index):
return Column(self._multicolumn_listbox, index)
def insert(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.get(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 __init__(self, master, columns, data=None, command=None, sort=True, select_mode=None, heading_anchor = CENTER, anchor=W, style=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, extra_cell_height=2, column_header=True):
self._stripped_rows = stripped_rows
self._columns = columns
self._number_of_columns = len(columns)
self._number_of_rows = 0
self.row = self.List_Of_Rows(self)
self.column = self.List_Of_Columns(self)
s = Style()
if style is None:
style_name = "Multicolumn_Listbox%s.Treeview"%self._style_index
self._style_index += 1
else:
style_name = style
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:
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 None:
font_name = s.lookup(style_name, "font")
font = nametofont(font_name)
else:
if not isinstance(font, Font):
if isinstance(font, basestring):
font = nametofont(font)
else:
if len(font) == 1:
font = Font(family=font[0])
elif len(font) == 2:
font = Font(family=font[0], size=font[1])
elif len(font) == 3:
font = Font(family=font[0], size=font[1], weight=font[2])
else:
raise ValueError("Not possible more than 3 values for font")
style_config["font"] = font
self._font = font
self._rowheight = font.metrics("linespace")+extra_cell_height
style_config["rowheight"]=self._rowheight
if field_background is not None:
style_config["fieldbackground"] = field_background
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)
treeview_kwargs = {"style": style_name}
if height is not None:
treeview_kwargs["height"] = height
if padding is not None:
treeview_kwargs["padding"] = padding
if column_header:
treeview_kwargs["show"] = "headings"
else:
treeview_kwargs["show"] = ""
if select_mode is not None:
treeview_kwargs["selectmode"] = select_mode
self.interior = Treeview(master, columns=columns, **treeview_kwargs)
if command is not None:
self._command = command
self.interior.bind("<<TreeviewSelect>>", self._on_select)
for i in range(0, self._number_of_columns):
if sort:
self.interior.heading(i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self.sort_by(col, descending=False))
else:
self.interior.heading(i, text=columns[i], anchor=heading_anchor)
if adjust_heading_to_content:
self.interior.column(i, width=Font().measure(columns[i]))
self.interior.column(i, anchor=anchor)
if data is not None:
for row in data:
self.insert_row(row)
@property
def row_height(self):
return self._rowheight
@property
def font(self):
return self._font
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.interior.column('#%s'%(index+1), **kwargs)
def row_data(self, index):
try:
item_ID = self.interior.get_children()[index]
except IndexError:
raise ValueError("Row index out of range: %d"%index)
return self.item_ID_to_row_data(item_ID)
def update_row(self, index, data):
try:
item = self.interior.get_children()[index]
except IndexError:
raise ValueError("Row index out of range: %d"%index)
if len(data) == len(self._columns):
self.interior.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.interior.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.interior.delete(item_ID)
self._number_of_rows -= 1
if self._stripped_rows:
for i in range(index+1, number_of_rows):
self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[(i-1)%2])
def insert_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.interior.insert('', index, values=data)
self.interior.item(item_ID, tags=item_ID)
self._number_of_rows += 1
number_of_rows = self._number_of_rows
if self._stripped_rows:
list_of_items = self.interior.get_children()
if index == END:
index = number_of_rows-1
elif index < 0:
index = 0
elif index >= number_of_rows:
index = number_of_rows-1
self.interior.tag_configure(item_ID, background=self._stripped_rows[index%2])
for i in range(index+1, number_of_rows):
self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[(i+1)%2])
def column_data(self, index):
return [self.interior.set(child_ID, index) for child_ID in self.interior.get_children('')]
def update_column(self, index, data):
for i, item_ID in enumerate(self.interior.get_children()):
data_row = self.item_ID_to_row_data(item_ID)
data_row[index] = data[i]
self.interior.item(item_ID, values=data_row)
return data
def clear(self):
# Another possibility:
# self.interior.delete(*self.interior.get_children())
for row in self.interior.get_children():
self.interior.delete(row)
self._number_of_rows = 0
def update(self, data):
self.clear()
for row in data:
self.insert_row(row)
def focus(self, index=None):
if index is None:
return self.interior.item(self.interior.focus())
else:
item = self.interior.get_children()[index]
self.interior.focus(item)
def state(self, state=None):
if stateSpec is None:
return self.interior.state()
else:
self.interior.state(state)
@property
def number_of_rows(self):
return len(self.interior.get_children())
@property
def number_of_columns(self):
return self._number_of_columns
def toogle_selection(self, index):
list_of_items = self.interior.get_children()
try:
item_ID = list_of_items[index]
except IndexError:
raise ValueError("Row index out of range: %d"%index)
self.interior.selection_toggle(item_ID)
def select_row(self, index):
list_of_items = self.interior.get_children()
try:
item_ID = list_of_items[index]
except IndexError:
raise ValueError("Row index out of range: %d"%index)
self.interior.selection_add(item_ID)
def deselect_row(self, index):
list_of_items = self.interior.get_children()
try:
item_ID = list_of_items[index]
except IndexError:
raise ValueError("Row index out of range: %d"%index)
self.interior.selection_remove(item_ID)
def deselect_all(self):
self.interior.selection_remove(self.interior.selection())
def set_selection(self, indices):
list_of_items = self.interior.get_children()
self.interior.selection_set(" ".join(list_of_items[row_index] for row_index in indices))
@property
def selected_rows(self):
data = []
for item_ID in self.interior.selection():
data_row = self.item_ID_to_row_data(item_ID)
data.append(data_row)
return data
@property
def indices_of_selected_rows(self):
list_of_indices = []
for index, item_ID in enumerate(self.interior.get_children()):
if item_ID in self.interior.selection():
list_of_indices.append(index)
return list_of_indices
def delete_all_selected_rows(self):
selected_items = self.interior.selection()
for item_ID in selected_items:
self.interior.delete(item_ID)
number_of_deleted_rows = len(selected_items)
self._number_of_rows -= number_of_deleted_rows
return number_of_deleted_rows
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.interior.item(item_ID)
return item["values"]
@property
def table_data(self):
data = []
for item_ID in self.interior.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(data)
def cell_data(self, row, column):
"""Get the value of a table cell"""
try:
item = self.interior.get_children()[row]
except IndexError:
raise ValueError("Row index out of range: %d"%row)
return self.interior.set(item, column)
def update_cell(self, row, column, value):
"""Set the value of a table cell"""
item_ID = self.interior.get_children()[row]
data = self.item_ID_to_row_data(item_ID)
data[column] = value
self.interior.item(item_ID, values=data)
def __getitem__(self, index):
if isinstance(index, tuple):
row, column = index
return self.cell_data(row, column)
else:
raise Exception("Row and column indices are required")
def __setitem__(self, index, value):
if isinstance(index, tuple):
row, column = index
self.update_cell(row, column, value)
else:
raise Exception("Row and column indices are required")
def bind(self, event, handler):
self.interior.bind(event, handler)
def sort_by(self, col, descending):
"""
sort tree contents when a column header is clicked
"""
# grab values to sort
data = [(self.interior.set(child_ID, col), child_ID) for child_ID in self.interior.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.interior.move(item[1], '', idx)
# switch the heading so that it will sort in the opposite direction
self.interior.heading(col, command=lambda col=col: self.sort_by(col, not descending))
if self._stripped_rows:
list_of_items = self.interior.get_children('')
for i in range(len(list_of_items)):
self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i%2])
def destroy(self):
self.interior.destroy()
def item_ID(self, index):
return self.interior.get_children()[index]
################################################
# The next code is only for Tk_Table class
#
#
OS = platform.system()
def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1, unit="units"):
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, unit)
elif event.num == 5:
view_command("scroll",factor, unit)
callback()
else:
def onMouseWheel(event):
if event.num == 4:
view_command("scroll",(-1)*factor, unit)
elif event.num == 5:
view_command("scroll",factor, unit)
elif OS == 'Windows':
if callback:
def onMouseWheel(event):
view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
callback()
else:
def onMouseWheel(event):
view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
elif OS == 'Darwin':
if callback:
def onMouseWheel(event):
view_command("scroll",event.delta, unit)
callback()
else:
def onMouseWheel(event):
view_command("scroll",event.delta, unit)
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='+')
def unbind_function_onMouseWheel(binding_widget):
if OS == "Linux" :
binding_widget.unbind('<4>')
binding_widget.unbind('<5>')
else:
binding_widget.unbind('<MouseWheel>')
class Row_Header(Canvas):
def __init__(self, master, font, row_height, row_minwidth, hover_background=None, background=None, anchor=None, onclick=None):
Canvas.__init__(self, master, bd=0, highlightthickness=0, yscrollincrement=row_height, width=0)
if background is not None:
self.configure(background=background)
self._frame_of_row_numbers = Frame(self, bd=0)
self.create_window(0, 0, window=self._frame_of_row_numbers, anchor=N+W)
self._width_of_row_numbers = 0
self._labelrow_kwargs = labelrow_kwargs= {}
labelrow_kwargs["font"] = font
if anchor is not None:
labelrow_kwargs["anchor"] = anchor
self._row_minwidth= row_minwidth
self._row_height = row_height
self._hover_background = hover_background
self._onclick = onclick
self._number_of_labels = 0
def pop(self, n_labels=1):
if self._number_of_labels == 0:
return
list_of_slaves = self._frame_of_row_numbers.pack_slaves()
for i in range(n_labels):
list_of_slaves.pop().destroy()
if list_of_slaves:
width_of_row_numbers= max(list_of_slaves[-1].winfo_reqwidth(), self._row_minwidth)
if width_of_row_numbers < self._width_of_row_numbers:
self._width_of_row_numbers = width_of_row_numbers
for slave in self._frame_of_row_numbers.pack_slaves():
slave.configure(width=width_of_row_numbers)
self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), len(list_of_slaves)*self._row_height))
self._number_of_labels -= n_labels
def delete_labels(self):
if self._number_of_labels == 0:
return
for slave in self._frame_of_row_numbers.pack_slaves():
slave.destroy()
self._number_of_labels = 0
def new_label(self):
# Creating a new row header
self._number_of_labels += 1
frame_button = Frame(self._frame_of_row_numbers, height=self._row_height)
frame_button.pack()
frame_button.pack_propagate(False)
row_label = Label(frame_button, text =self._number_of_labels, **self._labelrow_kwargs)
row_label.bind("<1>", lambda event, index=self._number_of_labels-1: self._on_click_label(index))
if self._hover_background:
row_label.bind("<Enter>", lambda event, row_label=row_label: row_label.configure(background=self._hover_background))
row_label.bind("<Leave>", lambda event, row_label=row_label: row_label.configure(background=self.cget("background")))
row_label.pack(expand=True, fill=BOTH)
width_of_row_numbers= max(row_label.winfo_reqwidth(), self._row_minwidth)
if width_of_row_numbers > self._width_of_row_numbers:
self._width_of_row_numbers = width_of_row_numbers
for slave in self._frame_of_row_numbers.pack_slaves():
slave.configure(width=width_of_row_numbers)
else:
frame_button.configure(width=width_of_row_numbers)
frame_button.update_idletasks()
self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), self._number_of_labels*self._row_height))
def _on_click_label(self, index):
if self._onclick:
self._onclick(index)
class Tk_Table(Frame, object):
def __init__(self, master, columns, data=None, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, heading_anchor = CENTER, anchor=W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, extra_cell_height=2, column_header=True, row_numbers=True, entry_background="#d6d6d6", entry_foreground=None, entry_validatecommand=None, entry_selectbackground="#1BA1E2", entry_selectborderwidth=None, entry_selectforeground=None, entry_font = "TkDefaultFont", rowlabel_anchor=E, rowlabel_minwidth=0, rowlabel_hoverbackground="#FFFFFF",frame_relief=None, frame_borderwidth=None, frame_background=None):
frame_kwargs = {}
if frame_relief is not None:
frame_kwargs["relief"] = frame_relief
if frame_borderwidth is not None:
frame_kwargs["borderwidth"] = frame_borderwidth
if frame_background is not None:
frame_kwargs["background"] = frame_background
Frame.__init__(self, master, class_="Multicolumn_Listbox", **frame_kwargs)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self._multicolumn_listbox = Multicolumn_Listbox(self, columns, data=data, command=command, sort=sort, select_mode=select_mode, heading_anchor = heading_anchor, anchor=anchor, style=style, height=height, padding=padding, adjust_heading_to_content=adjust_heading_to_content, stripped_rows=stripped_rows, selection_background=selection_background, selection_foreground=selection_foreground, background=background, foreground=foreground, font=font, field_background=field_background, heading_font=heading_font, heading_background=heading_background, heading_foreground=heading_foreground, extra_cell_height=extra_cell_height, column_header=column_header)
self._multicolumn_listbox.interior.grid(row=0, column=1, sticky= N+E+W+S)
self._mousewheel_detection = True
if row_numbers:
self._row_numbers = Row_Header(self, font=self._multicolumn_listbox.font, row_height=self._multicolumn_listbox.row_height, row_minwidth=rowlabel_minwidth, hover_background = rowlabel_hoverbackground, anchor=rowlabel_anchor, onclick=self._on_click_row_label)
self._row_numbers.grid(row=0, column=0, sticky= N+S+E)
self._multicolumn_listbox.interior.bind("<Map>", self._place_vertically_row_numbers)
else:
self._row_numbers = None
if editable:
self._selected_cell = None
self._entry_popup = None
self._multicolumn_listbox.interior.bind("<1>", self._edit_cell)
def configure(event):
"""
if self._entry_popup:
self._entry_popup.destroy()
return
"""
self._multicolumn_listbox.interior.update_idletasks()
self._update_position_of_entry()
self._multicolumn_listbox.interior.bind("<Configure>", configure)
self._entry_kwargs = entry_kwargs = {}
if entry_background is not None:
entry_kwargs["background"] = entry_background
if entry_foreground is not None:
entry_kwargs["foreground"] = entry_foreground
if entry_validatecommand is not None:
entry_kwargs["validatecommand"] = entry_validatecommand
if entry_selectbackground is not None:
entry_kwargs["selectbackground"] = entry_selectbackground
if entry_selectforeground is not None:
entry_kwargs["selectforeground"] = entry_selectforeground
if entry_font is not None:
entry_kwargs["font"] = entry_font
if command is not None:
self._command = command
self._multicolumn_listbox.interior.bind("<<TreeviewSelect>>", self._on_select)
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:
if row_numbers:
def yview_command(*args):
self._multicolumn_listbox.interior.yview(*args)
self._row_numbers.yview(*args)
self._update_position_of_entry()
else:
def yview_command(*args):
self._multicolumn_listbox.interior.yview(*args)
self._update_position_of_entry()
else:
if row_numbers:
def yview_command(*args):
self._multicolumn_listbox.interior.yview(*args)
self._row_numbers.yview(*args)
else:
yview_command = self._multicolumn_listbox.interior.yview
self._vbar=Scrollbar(self,takefocus=0, command=yview_command, **scrollbar_kwargs)
self._vbar.grid(row=0, column=2, sticky= N+S)
def yscrollcommand(first,last):
first, last = float(first), float(last)
if first <= 0 and last >= 1:
if self._mousewheel_detection:
if autoscroll:
self._vbar.grid_remove()
if row_numbers:
unbind_function_onMouseWheel(self._multicolumn_listbox.interior)
self._mousewheel_detection = False
else:
if not self._mousewheel_detection:
if autoscroll:
self._vbar.grid()
if row_numbers:
bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._multicolumn_listbox.interior, unit="pages")
self._mousewheel_detection = True
self._vbar.set(first, last)
if editable:
self._update_position_of_entry()
self._multicolumn_listbox.interior.config(yscrollcommand=yscrollcommand)
if hscrollbar:
if editable:
def xview_command(*args):
self._multicolumn_listbox.interior.xview(*args)
self._update_position_of_entry()
else:
xview_command = self._multicolumn_listbox.interior.xview
self._hbar=Scrollbar(self,takefocus=0, command=xview_command, **scrollbar_kwargs)
self._hbar.grid(row=1, 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._multicolumn_listbox.interior.config(xscrollcommand=xscrollcommand)
else:
self._multicolumn_listbox.interior.config(xscrollcommand=self._hbar.set)
def _place_vertically_row_numbers(self, event):
self._multicolumn_listbox.interior.unbind("<Map>")
item_ID = self._multicolumn_listbox.interior.insert('', 0, values=[""]*self._multicolumn_listbox.number_of_columns)
self._multicolumn_listbox.interior.update()
x,y,w,h = self._multicolumn_listbox.interior.bbox(item_ID)
self._multicolumn_listbox.interior.delete(item_ID)
self._row_numbers.grid_configure(pady=(y,0))
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._destroy_entry()
# what row and column was clicked on
item_ID = self._multicolumn_listbox.interior.identify_row(event.y)
if not item_ID: return
column = self._multicolumn_listbox.interior.identify_column(event.x)
if column == "": return
# get column position info
x,y,width,height = self._multicolumn_listbox.interior.bbox(item_ID, column)
# place Entry popup properly
column_number = int(column[1:])-1
cell_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)[column_number]
self._entry_popup = Entry(self._multicolumn_listbox.interior, exportselection=True, borderwidth=0, **self._entry_kwargs)
self._entry_popup.place(x=x, y=y, width=width, height=height)
self._entry_popup.insert(0, cell_data)
self._entry_popup.focus_force()
self._entry_popup.bind("<Control-a>", lambda event: self._select_all_entry_data)
self._entry_popup.bind("<Escape>", lambda event: self._destroy_entry())
self._entry_popup.bind("<FocusOut>", lambda event: self._destroy_entry())
bind_function_onMouseWheel(self._multicolumn_listbox.interior, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry, unit="pages")
if self._row_numbers:
bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._entry_popup, unit="pages")
self._entry_popup.bind("<Return>", self._on_update_cell)
self._selected_cell = item_ID, column, column_number
def _on_click_row_label(self, index):
if self._selected_cell and self._multicolumn_listbox.item_ID(index) == self._selected_cell[0]:
self._destroy_entry()
self._multicolumn_listbox.toogle_selection(index)
def _select_all_entry_data(self):
''' 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 _on_update_cell(self, event):
item_ID, column, column_number = self._selected_cell
data = self._entry_popup.get()
row_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)
row_data[column_number] = data
self._multicolumn_listbox.interior.item(item_ID, values=row_data)
self._destroy_entry()
def _update_position_of_entry(self):
if self._selected_cell:
bbox = self._multicolumn_listbox.interior.bbox(self._selected_cell[0], self._selected_cell[1])
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):
self._multicolumn_listbox.configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None)
def row_data(self, index):
return self._multicolumn_listbox.row_data(index)
def update_row(self, index, data):
self._multicolumn_listbox.update_row(index,data)
def delete_row(self, index):
self._multicolumn_listbox.delete_row(index)
if self._row_numbers:
self._row_numbers.pop()
def insert_row(self, data, index=END):
self._multicolumn_listbox.insert_row(data, index)
if self._row_numbers:
self._row_numbers.new_label()
def column_data(self, index):
return self._multicolumn_listbox.column_data(index)
def update_column(self, index, data):
self._multicolumn_listbox.update_column(index, data)
def clear(self):
self._multicolumn_listbox.clear()
if self._row_numbers:
self._row_numbers.delete_labels()
def update(self, data):
self._multicolumn_listbox.update(data)
if self._row_numbers:
for i in range(len(data)):
self._row_numbers.new_label()
def focus(self, index=None):
self._multicolumn_listbox.focus(index)
def state(self, state=None):
self._multicolumn_listbox.state(state)
@property
def number_of_rows(self):
return self._multicolumn_listbox.number_of_rows
@property
def number_of_columns(self):
return self._multicolumn_listbox.number_of_columns
def toogle_selection(self, index):
self._multicolumn_listbox.toogle_selection(index)
def select_row(self, index):
self._multicolumn_listbox.select_row(index)
def deselect_row(self, index):
self._multicolumn_listbox.deselect_row(index)
def deselect_all(self):
self._multicolumn_listbox.deselect_all()
def set_selection(self, indices):
self._multicolumn_listbox.set_selection(indices)
@property
def selected_rows(self):
return self._multicolumn_listbox.selected_rows
@property
def indices_of_selected_rows(self):
return self._multicolumn_listbox.indices_of_selected_rows
def delete_all_selected_rows(self):
number_of_deleted_rows = self._multicolumn_listbox.delete_all_selected_rows()
if self._row_numbers:
self._row_numbers.pop(n_labels=number_of_deleted_rows)
@property
def table_data(self):
return self._multicolumn_listbox.table_data
@table_data.setter
def table_data(self, data):
self._multicolumn_listbox.table_data = data
if self._row_numbers:
for i in range(len(data)):
self._row_numbers.new_label()
def cell_data(self, row, column):
return self._multicolumn_listbox.cell_data(row, column)
def update_cell(self, row, column, value):
self._multicolumn_listbox.update_cell(row, column, value)
def __getitem__(self, index):
return self._multicolumn_listbox[index]
def __setitem__(self, index, value):
self._multicolumn_listbox[index] = value
def bind(self, event, handler):
self._multicolumn_listbox.bind(event, handler)
def sort_by(self, col, descending):
self._multicolumn_listbox.sort_by(col, descending)
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("called command when row is selected")
print(data)
print("\n")
def show_info(msg):
messagebox.showinfo("Table Data", msg)
mcListbox = Multicolumn_Listbox(root, ["column one","column two", "column three"], command=on_select, anchor="center")
mcListbox.interior.pack()
mcListbox.insert_row([1,2,3])
show_info("mcListbox.insert_row([1,2,3])")
mcListbox.row.insert([4,5,7])
show_info("mcListbox.row.insert([4,5,7])")
mcListbox.update_row(0, [7,8,9])
show_info("mcListbox.update_row(0, [4,5,6])")
mcListbox.update([[1,2,3], [4,5,6]])
show_info("mcListbox.update([[1,2,3], [4,5,6]])")
mcListbox.select_row(0)
show_info("mcListbox.select_row(0)")
print("mcListbox.selected_rows")
print(mcListbox.selected_rows)
print("\n")
print("mcListbox.table_data")
print(mcListbox.table_data)
print("\n")
print("mcListbox.row[0]")
print(mcListbox.row[0])
print("\n")
print("mcListbox.row_data(0)")
print(mcListbox.row_data(0))
print("\n")
print("mcListbox.column[1]")
print(mcListbox.column[1])
print("\n")
print("mcListbox[0,1]")
print(mcListbox[0,1])
print("\n")
mcListbox.column[1] = ["item1", "item2"]
mcListbox.update_column(2, [8,9])
show_info("mcListbox.update_column(2, [8,9])")
mcListbox.clear()
show_info("mcListbox.clear()")
mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]
show_info("mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]")
mcListbox.delete_row(1)
show_info("mcListbox.delete_row(1)")
row = mcListbox.row[0].update([2,4,5])
show_info("mcListbox.row[0].update([2,4,5])")
mcListbox.destroy()
# The next table is editable: Click on the table to edit the cell
table = Tk_Table(root, ["column one","column two", "column three"], row_numbers=True, 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.insert_row([1,2,3])
show_info("We created an editable and zebra style table. Click on a cell to edit.")
root.mainloop()
Diff to Previous Revision
--- revision 27 2017-03-16 12:35:27
+++ revision 28 2017-03-31 20:58:39
@@ -1,4 +1,4 @@
-# Version: 1.8
+# Version: 2.0
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
@@ -22,66 +22,9 @@
basestring = str
-OS = platform.system()
-
-def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1, unit="units"):
- 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, unit)
- elif event.num == 5:
- view_command("scroll",factor, unit)
-
- callback()
- else:
- def onMouseWheel(event):
- if event.num == 4:
- view_command("scroll",(-1)*factor, unit)
- elif event.num == 5:
- view_command("scroll",factor, unit)
-
- elif OS == 'Windows':
- if callback:
- def onMouseWheel(event):
- view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
- callback()
- else:
- def onMouseWheel(event):
- view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
-
- elif OS == 'Darwin':
- if callback:
- def onMouseWheel(event):
- view_command("scroll",event.delta, unit)
- callback()
- else:
- def onMouseWheel(event):
- view_command("scroll",event.delta, unit)
-
- 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='+')
-
-def unbind_function_onMouseWheel(binding_widget):
- if OS == "Linux" :
- binding_widget.unbind('<4>')
- binding_widget.unbind('<5>')
- else:
- binding_widget.unbind('<MouseWheel>')
-
-
class Row(object):
- def __init__(self, multicolumn_listbox, index):
- self._multicolumn_listbox = multicolumn_listbox
+ def __init__(self, table, index):
+ self._multicolumn_listbox = table
self._index = index
def data(self):
@@ -106,8 +49,8 @@
return self._multicolumn_listbox.number_of_columns
class Column(object):
- def __init__(self, multicolumn_listbox, index):
- self._multicolumn_listbox = multicolumn_listbox
+ def __init__(self, table, index):
+ self._multicolumn_listbox = table
self._index = index
def data(self):
@@ -124,12 +67,10 @@
def __len__(self):
return self._multicolumn_listbox.number_of_rows
-
-
-class Multicolumn_Listbox(Frame, object):
-
+
+class Multicolumn_Listbox(object):
_style_index = 0
-
+
class List_Of_Rows(object):
def __init__(self, multicolumn_listbox):
self._multicolumn_listbox = multicolumn_listbox
@@ -172,7 +113,7 @@
class List_Of_Columns(object):
def __init__(self, multicolumn_listbox):
- self._multicolumn_listbox = multicolumn_listbox
+ self._multicolumn_listbox = multicolumn_listbox
def data(self, index):
return self._multicolumn_listbox.get_column(index)
@@ -201,26 +142,18 @@
def __len__(self):
return self._multicolumn_listbox.number_of_columns
- def __init__(self, master, columns, data=None, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, heading_anchor = CENTER, anchor=W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, extra_cell_height=2, column_header=True, row_header=True, entry_background="#d6d6d6", entry_foreground=None, entry_validatecommand=None, entry_selectbackground="#1BA1E2", entry_selectborderwidth=None, entry_selectforeground=None, entry_font = "TkDefaultFont", rowlabel_anchor=E, rowlabel_minwidth=0, rowlabel_hoverbackground="#FFFFFF",frame_relief=None, frame_borderwidth=None, frame_background=None):
-
- frame_kwargs = {}
-
- if frame_relief is not None:
- frame_kwargs["relief"] = frame_relief
-
- if frame_borderwidth is not None:
- frame_kwargs["borderwidth"] = frame_borderwidth
-
- if frame_background is not None:
- frame_kwargs["background"] = frame_background
-
- Frame.__init__(self, master, class_="Multicolumn_Listbox", **frame_kwargs)
-
+ def __init__(self, master, columns, data=None, command=None, sort=True, select_mode=None, heading_anchor = CENTER, anchor=W, style=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, extra_cell_height=2, column_header=True):
+
self._stripped_rows = stripped_rows
- self.grid_rowconfigure(0, weight=1)
- self.grid_columnconfigure(1, weight=1)
-
+ self._columns = columns
+ self._number_of_columns = len(columns)
+
+ self._number_of_rows = 0
+
+ self.row = self.List_Of_Rows(self)
+ self.column = self.List_Of_Columns(self)
+
s = Style()
if style is None:
@@ -265,6 +198,8 @@
raise ValueError("Not possible more than 3 values for font")
style_config["font"] = font
+
+ self._font = font
self._rowheight = font.metrics("linespace")+extra_cell_height
style_config["rowheight"]=self._rowheight
@@ -301,38 +236,507 @@
if select_mode is not None:
treeview_kwargs["selectmode"] = select_mode
- self._treeview = Treeview(self,columns=columns, **treeview_kwargs)
- self._treeview.grid(row=0, column=1, sticky= N+E+W+S)
+ self.interior = Treeview(master, columns=columns, **treeview_kwargs)
+
+ if command is not None:
+ self._command = command
+ self.interior.bind("<<TreeviewSelect>>", self._on_select)
+
+ for i in range(0, self._number_of_columns):
+
+ if sort:
+ self.interior.heading(i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self.sort_by(col, descending=False))
+ else:
+ self.interior.heading(i, text=columns[i], anchor=heading_anchor)
+
+ if adjust_heading_to_content:
+ self.interior.column(i, width=Font().measure(columns[i]))
+
+ self.interior.column(i, anchor=anchor)
+
+ if data is not None:
+ for row in data:
+ self.insert_row(row)
+
+ @property
+ def row_height(self):
+ return self._rowheight
+
+ @property
+ def font(self):
+ return self._font
+
+ 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.interior.column('#%s'%(index+1), **kwargs)
+
+ def row_data(self, index):
+ try:
+ item_ID = self.interior.get_children()[index]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%index)
+
+ return self.item_ID_to_row_data(item_ID)
+
+ def update_row(self, index, data):
+ try:
+ item = self.interior.get_children()[index]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%index)
+
+ if len(data) == len(self._columns):
+ self.interior.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.interior.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.interior.delete(item_ID)
+ self._number_of_rows -= 1
+
+ if self._stripped_rows:
+ for i in range(index+1, number_of_rows):
+ self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[(i-1)%2])
+
+ def insert_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.interior.insert('', index, values=data)
+ self.interior.item(item_ID, tags=item_ID)
+
+ self._number_of_rows += 1
+ number_of_rows = self._number_of_rows
+
+ if self._stripped_rows:
+ list_of_items = self.interior.get_children()
+
+ if index == END:
+ index = number_of_rows-1
+ elif index < 0:
+ index = 0
+ elif index >= number_of_rows:
+ index = number_of_rows-1
+
+ self.interior.tag_configure(item_ID, background=self._stripped_rows[index%2])
+
+ for i in range(index+1, number_of_rows):
+ self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[(i+1)%2])
+
+ def column_data(self, index):
+ return [self.interior.set(child_ID, index) for child_ID in self.interior.get_children('')]
+
+ def update_column(self, index, data):
+ for i, item_ID in enumerate(self.interior.get_children()):
+ data_row = self.item_ID_to_row_data(item_ID)
+ data_row[index] = data[i]
+
+ self.interior.item(item_ID, values=data_row)
+
+ return data
+
+ def clear(self):
+ # Another possibility:
+ # self.interior.delete(*self.interior.get_children())
+
+ for row in self.interior.get_children():
+ self.interior.delete(row)
+
+ self._number_of_rows = 0
+
+ def update(self, data):
+ self.clear()
+
+ for row in data:
+ self.insert_row(row)
+
+ def focus(self, index=None):
+ if index is None:
+ return self.interior.item(self.interior.focus())
+ else:
+ item = self.interior.get_children()[index]
+ self.interior.focus(item)
+
+ def state(self, state=None):
+ if stateSpec is None:
+ return self.interior.state()
+ else:
+ self.interior.state(state)
+
+ @property
+ def number_of_rows(self):
+ return len(self.interior.get_children())
+
+ @property
+ def number_of_columns(self):
+ return self._number_of_columns
+
+ def toogle_selection(self, index):
+ list_of_items = self.interior.get_children()
+
+ try:
+ item_ID = list_of_items[index]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%index)
+
+ self.interior.selection_toggle(item_ID)
+
+ def select_row(self, index):
+ list_of_items = self.interior.get_children()
+
+ try:
+ item_ID = list_of_items[index]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%index)
+
+ self.interior.selection_add(item_ID)
+
+ def deselect_row(self, index):
+ list_of_items = self.interior.get_children()
+
+ try:
+ item_ID = list_of_items[index]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%index)
+
+ self.interior.selection_remove(item_ID)
+
+ def deselect_all(self):
+ self.interior.selection_remove(self.interior.selection())
+
+ def set_selection(self, indices):
+ list_of_items = self.interior.get_children()
+
+ self.interior.selection_set(" ".join(list_of_items[row_index] for row_index in indices))
+
+ @property
+ def selected_rows(self):
+ data = []
+ for item_ID in self.interior.selection():
+ data_row = self.item_ID_to_row_data(item_ID)
+ data.append(data_row)
+
+ return data
+
+ @property
+ def indices_of_selected_rows(self):
+ list_of_indices = []
+ for index, item_ID in enumerate(self.interior.get_children()):
+ if item_ID in self.interior.selection():
+ list_of_indices.append(index)
+
+ return list_of_indices
+
+ def delete_all_selected_rows(self):
+ selected_items = self.interior.selection()
+ for item_ID in selected_items:
+ self.interior.delete(item_ID)
+
+ number_of_deleted_rows = len(selected_items)
+ self._number_of_rows -= number_of_deleted_rows
+
+ return number_of_deleted_rows
+
+ 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.interior.item(item_ID)
+ return item["values"]
+
+ @property
+ def table_data(self):
+ data = []
+
+ for item_ID in self.interior.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(data)
+
+ def cell_data(self, row, column):
+ """Get the value of a table cell"""
+ try:
+ item = self.interior.get_children()[row]
+ except IndexError:
+ raise ValueError("Row index out of range: %d"%row)
+
+ return self.interior.set(item, column)
+
+ def update_cell(self, row, column, value):
+ """Set the value of a table cell"""
+
+ item_ID = self.interior.get_children()[row]
+
+ data = self.item_ID_to_row_data(item_ID)
+
+ data[column] = value
+ self.interior.item(item_ID, values=data)
+
+ def __getitem__(self, index):
+ if isinstance(index, tuple):
+ row, column = index
+ return self.cell_data(row, column)
+ else:
+ raise Exception("Row and column indices are required")
+
+ def __setitem__(self, index, value):
+ if isinstance(index, tuple):
+ row, column = index
+ self.update_cell(row, column, value)
+ else:
+ raise Exception("Row and column indices are required")
+
+ def bind(self, event, handler):
+ self.interior.bind(event, handler)
+
+ def sort_by(self, col, descending):
+ """
+ sort tree contents when a column header is clicked
+ """
+ # grab values to sort
+ data = [(self.interior.set(child_ID, col), child_ID) for child_ID in self.interior.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.interior.move(item[1], '', idx)
+
+ # switch the heading so that it will sort in the opposite direction
+ self.interior.heading(col, command=lambda col=col: self.sort_by(col, not descending))
+
+ if self._stripped_rows:
+ list_of_items = self.interior.get_children('')
+ for i in range(len(list_of_items)):
+ self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i%2])
+
+ def destroy(self):
+ self.interior.destroy()
+
+ def item_ID(self, index):
+ return self.interior.get_children()[index]
+
+################################################
+# The next code is only for Tk_Table class
+#
+#
+
+OS = platform.system()
+
+def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1, unit="units"):
+ 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, unit)
+ elif event.num == 5:
+ view_command("scroll",factor, unit)
+
+ callback()
+ else:
+ def onMouseWheel(event):
+ if event.num == 4:
+ view_command("scroll",(-1)*factor, unit)
+ elif event.num == 5:
+ view_command("scroll",factor, unit)
+
+ elif OS == 'Windows':
+ if callback:
+ def onMouseWheel(event):
+ view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
+ callback()
+ else:
+ def onMouseWheel(event):
+ view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
+
+ elif OS == 'Darwin':
+ if callback:
+ def onMouseWheel(event):
+ view_command("scroll",event.delta, unit)
+ callback()
+ else:
+ def onMouseWheel(event):
+ view_command("scroll",event.delta, unit)
+
+ 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='+')
+
+def unbind_function_onMouseWheel(binding_widget):
+ if OS == "Linux" :
+ binding_widget.unbind('<4>')
+ binding_widget.unbind('<5>')
+ else:
+ binding_widget.unbind('<MouseWheel>')
+
+class Row_Header(Canvas):
+ def __init__(self, master, font, row_height, row_minwidth, hover_background=None, background=None, anchor=None, onclick=None):
+ Canvas.__init__(self, master, bd=0, highlightthickness=0, yscrollincrement=row_height, width=0)
+
+ if background is not None:
+ self.configure(background=background)
+
+ self._frame_of_row_numbers = Frame(self, bd=0)
+
+ self.create_window(0, 0, window=self._frame_of_row_numbers, anchor=N+W)
+ self._width_of_row_numbers = 0
+
+ self._labelrow_kwargs = labelrow_kwargs= {}
+
+ labelrow_kwargs["font"] = font
+
+ if anchor is not None:
+ labelrow_kwargs["anchor"] = anchor
+
+ self._row_minwidth= row_minwidth
+ self._row_height = row_height
+ self._hover_background = hover_background
+ self._onclick = onclick
+
+ self._number_of_labels = 0
+
+ def pop(self, n_labels=1):
+ if self._number_of_labels == 0:
+ return
+
+ list_of_slaves = self._frame_of_row_numbers.pack_slaves()
+
+ for i in range(n_labels):
+ list_of_slaves.pop().destroy()
+
+ if list_of_slaves:
+ width_of_row_numbers= max(list_of_slaves[-1].winfo_reqwidth(), self._row_minwidth)
+
+ if width_of_row_numbers < self._width_of_row_numbers:
+ self._width_of_row_numbers = width_of_row_numbers
+
+ for slave in self._frame_of_row_numbers.pack_slaves():
+ slave.configure(width=width_of_row_numbers)
+
+ self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
+ self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), len(list_of_slaves)*self._row_height))
+
+ self._number_of_labels -= n_labels
+
+ def delete_labels(self):
+ if self._number_of_labels == 0:
+ return
+
+ for slave in self._frame_of_row_numbers.pack_slaves():
+ slave.destroy()
+
+ self._number_of_labels = 0
+
+ def new_label(self):
+ # Creating a new row header
+
+ self._number_of_labels += 1
+
+ frame_button = Frame(self._frame_of_row_numbers, height=self._row_height)
+ frame_button.pack()
+ frame_button.pack_propagate(False)
+
+ row_label = Label(frame_button, text =self._number_of_labels, **self._labelrow_kwargs)
+ row_label.bind("<1>", lambda event, index=self._number_of_labels-1: self._on_click_label(index))
+
+ if self._hover_background:
+ row_label.bind("<Enter>", lambda event, row_label=row_label: row_label.configure(background=self._hover_background))
+ row_label.bind("<Leave>", lambda event, row_label=row_label: row_label.configure(background=self.cget("background")))
+
+ row_label.pack(expand=True, fill=BOTH)
+
+ width_of_row_numbers= max(row_label.winfo_reqwidth(), self._row_minwidth)
+
+ if width_of_row_numbers > self._width_of_row_numbers:
+ self._width_of_row_numbers = width_of_row_numbers
+
+ for slave in self._frame_of_row_numbers.pack_slaves():
+ slave.configure(width=width_of_row_numbers)
+ else:
+ frame_button.configure(width=width_of_row_numbers)
+
+ frame_button.update_idletasks()
+
+ self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
+ self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), self._number_of_labels*self._row_height))
+
+ def _on_click_label(self, index):
+ if self._onclick:
+ self._onclick(index)
+
+class Tk_Table(Frame, object):
+ def __init__(self, master, columns, data=None, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, heading_anchor = CENTER, anchor=W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, background=None, foreground=None, font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, extra_cell_height=2, column_header=True, row_numbers=True, entry_background="#d6d6d6", entry_foreground=None, entry_validatecommand=None, entry_selectbackground="#1BA1E2", entry_selectborderwidth=None, entry_selectforeground=None, entry_font = "TkDefaultFont", rowlabel_anchor=E, rowlabel_minwidth=0, rowlabel_hoverbackground="#FFFFFF",frame_relief=None, frame_borderwidth=None, frame_background=None):
+
+ frame_kwargs = {}
+
+ if frame_relief is not None:
+ frame_kwargs["relief"] = frame_relief
+
+ if frame_borderwidth is not None:
+ frame_kwargs["borderwidth"] = frame_borderwidth
+
+ if frame_background is not None:
+ frame_kwargs["background"] = frame_background
+
+ Frame.__init__(self, master, class_="Multicolumn_Listbox", **frame_kwargs)
+
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(1, weight=1)
+
+ self._multicolumn_listbox = Multicolumn_Listbox(self, columns, data=data, command=command, sort=sort, select_mode=select_mode, heading_anchor = heading_anchor, anchor=anchor, style=style, height=height, padding=padding, adjust_heading_to_content=adjust_heading_to_content, stripped_rows=stripped_rows, selection_background=selection_background, selection_foreground=selection_foreground, background=background, foreground=foreground, font=font, field_background=field_background, heading_font=heading_font, heading_background=heading_background, heading_foreground=heading_foreground, extra_cell_height=extra_cell_height, column_header=column_header)
+ self._multicolumn_listbox.interior.grid(row=0, column=1, sticky= N+E+W+S)
self._mousewheel_detection = True
-
- self._row_header = row_header
- if row_header:
- self._canvas = canvas = Canvas(self,bd=0, highlightthickness=0, yscrollincrement=self._rowheight, width=0)
- canvas.grid(row=0, column=0, sticky= N+S+E)
-
- self._frame_of_row_header = Frame(canvas, bd=0)
-
- canvas.create_window(0, 0, window=self._frame_of_row_header, anchor=N+W)
- self._width_of_row_header = 0
-
- self._labelrow_kwargs = labelrow_kwargs= {}
-
- labelrow_kwargs["font"] = font
-
- if rowlabel_anchor is not None:
- labelrow_kwargs["anchor"] = rowlabel_anchor
-
- self._rowlabel_minwidth= rowlabel_minwidth
- self._rowlabel_hoverbackground = rowlabel_hoverbackground
-
- self._treeview.bind("<Map>", self._set_pady_of_row_header)
+
+ if row_numbers:
+ self._row_numbers = Row_Header(self, font=self._multicolumn_listbox.font, row_height=self._multicolumn_listbox.row_height, row_minwidth=rowlabel_minwidth, hover_background = rowlabel_hoverbackground, anchor=rowlabel_anchor, onclick=self._on_click_row_label)
+ self._row_numbers.grid(row=0, column=0, sticky= N+S+E)
+
+ self._multicolumn_listbox.interior.bind("<Map>", self._place_vertically_row_numbers)
+ else:
+ self._row_numbers = None
if editable:
self._selected_cell = None
self._entry_popup = None
- self._treeview.bind("<1>", self._edit_cell)
+ self._multicolumn_listbox.interior.bind("<1>", self._edit_cell)
def configure(event):
"""
@@ -341,10 +745,10 @@
return
"""
- self._treeview.update_idletasks()
+ self._multicolumn_listbox.interior.update_idletasks()
self._update_position_of_entry()
- self._treeview.bind("<Configure>", configure)
+ self._multicolumn_listbox.interior.bind("<Configure>", configure)
self._entry_kwargs = entry_kwargs = {}
if entry_background is not None:
@@ -367,7 +771,7 @@
if command is not None:
self._command = command
- self._treeview.bind("<<TreeviewSelect>>", self._on_select)
+ self._multicolumn_listbox.interior.bind("<<TreeviewSelect>>", self._on_select)
scrollbar_kwargs = {}
if scrollbar_background is not None:
@@ -378,24 +782,24 @@
if vscrollbar:
if editable:
- if row_header:
+ if row_numbers:
def yview_command(*args):
- self._treeview.yview(*args)
- self._canvas.yview(*args)
+ self._multicolumn_listbox.interior.yview(*args)
+ self._row_numbers.yview(*args)
self._update_position_of_entry()
else:
def yview_command(*args):
- self._treeview.yview(*args)
+ self._multicolumn_listbox.interior.yview(*args)
self._update_position_of_entry()
else:
- if row_header:
+ if row_numbers:
def yview_command(*args):
- self._treeview.yview(*args)
- self._canvas.yview(*args)
+ self._multicolumn_listbox.interior.yview(*args)
+ self._row_numbers.yview(*args)
else:
- yview_command = self._treeview.yview
+ yview_command = self._multicolumn_listbox.interior.yview
self._vbar=Scrollbar(self,takefocus=0, command=yview_command, **scrollbar_kwargs)
self._vbar.grid(row=0, column=2, sticky= N+S)
@@ -407,31 +811,31 @@
if autoscroll:
self._vbar.grid_remove()
- if row_header:
- unbind_function_onMouseWheel(self._treeview)
+ if row_numbers:
+ unbind_function_onMouseWheel(self._multicolumn_listbox.interior)
self._mousewheel_detection = False
else:
if not self._mousewheel_detection:
if autoscroll:
self._vbar.grid()
- if row_header:
- bind_function_onMouseWheel(canvas, "y", binding_widget=self._treeview, unit="pages")
+ if row_numbers:
+ bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._multicolumn_listbox.interior, unit="pages")
self._mousewheel_detection = True
self._vbar.set(first, last)
if editable:
self._update_position_of_entry()
- self._treeview.config(yscrollcommand=yscrollcommand)
+ self._multicolumn_listbox.interior.config(yscrollcommand=yscrollcommand)
if hscrollbar:
if editable:
def xview_command(*args):
- self._treeview.xview(*args)
+ self._multicolumn_listbox.interior.xview(*args)
self._update_position_of_entry()
else:
- xview_command = self._treeview.xview
+ xview_command = self._multicolumn_listbox.interior.xview
self._hbar=Scrollbar(self,takefocus=0, command=xview_command, **scrollbar_kwargs)
self._hbar.grid(row=1, column=1, sticky= E+W)
@@ -445,42 +849,19 @@
def xscrollcommand(f,l, hbar=self._hbar):
make_autoscroll(hbar, f, l)
- self._treeview.config(xscrollcommand=xscrollcommand)
+ self._multicolumn_listbox.interior.config(xscrollcommand=xscrollcommand)
else:
- self._treeview.config(xscrollcommand=self._hbar.set)
-
- self._columns = columns
- self._number_of_columns = len(columns)
-
- self._number_of_rows = 0
-
- for i in range(0, self._number_of_columns):
-
- if sort:
- self._treeview.heading(i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self._sort_by(col, descending=False))
- else:
- self._treeview.heading(i, text=columns[i], anchor=heading_anchor)
-
- if adjust_heading_to_content:
- self._treeview.column(i, width=Font().measure(columns[i]))
-
- self._treeview.column(i, anchor=anchor)
-
- self.row = self.List_Of_Rows(self)
- self.column = self.List_Of_Columns(self)
-
- if data is not None:
- for row in data:
- self.insert_row(row)
-
- def _set_pady_of_row_header(self, event):
- item_ID = self._treeview.insert('', 0, values=[""]*self._number_of_columns)
- self._treeview.update()
- x,y,w,h = self._treeview.bbox(item_ID)
- self._canvas.grid_configure(pady=(y,0))
- self._treeview.delete(item_ID)
-
- self._treeview.unbind("<Map>")
+ self._multicolumn_listbox.interior.config(xscrollcommand=self._hbar.set)
+
+ def _place_vertically_row_numbers(self, event):
+ self._multicolumn_listbox.interior.unbind("<Map>")
+
+ item_ID = self._multicolumn_listbox.interior.insert('', 0, values=[""]*self._multicolumn_listbox.number_of_columns)
+ self._multicolumn_listbox.interior.update()
+ x,y,w,h = self._multicolumn_listbox.interior.bbox(item_ID)
+ self._multicolumn_listbox.interior.delete(item_ID)
+
+ self._row_numbers.grid_configure(pady=(y,0))
def _edit_cell(self, event):
'''Executed, when a row is clicked. Opens an entry popup above the cell, so it is possible
@@ -491,22 +872,22 @@
self._destroy_entry()
# what row and column was clicked on
- item_ID = self._treeview.identify_row(event.y)
+ item_ID = self._multicolumn_listbox.interior.identify_row(event.y)
if not item_ID: return
- column = self._treeview.identify_column(event.x)
+ column = self._multicolumn_listbox.interior.identify_column(event.x)
if column == "": return
# get column position info
- x,y,width,height = self._treeview.bbox(item_ID, column)
+ x,y,width,height = self._multicolumn_listbox.interior.bbox(item_ID, column)
# place Entry popup properly
column_number = int(column[1:])-1
- cell_data = self._item_ID_to_row_data(item_ID)[column_number]
-
-
- self._entry_popup = Entry(self._treeview, exportselection=True, borderwidth=0, **self._entry_kwargs)
+ cell_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)[column_number]
+
+
+ self._entry_popup = Entry(self._multicolumn_listbox.interior, exportselection=True, borderwidth=0, **self._entry_kwargs)
self._entry_popup.place(x=x, y=y, width=width, height=height)
self._entry_popup.insert(0, cell_data)
@@ -516,14 +897,20 @@
self._entry_popup.bind("<Escape>", lambda event: self._destroy_entry())
self._entry_popup.bind("<FocusOut>", lambda event: self._destroy_entry())
- bind_function_onMouseWheel(self._treeview, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry, unit="pages")
-
- if self._row_header:
- bind_function_onMouseWheel(self._canvas, "y", binding_widget=self._entry_popup, unit="pages")
+ bind_function_onMouseWheel(self._multicolumn_listbox.interior, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry, unit="pages")
+
+ if self._row_numbers:
+ bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._entry_popup, unit="pages")
self._entry_popup.bind("<Return>", self._on_update_cell)
self._selected_cell = item_ID, column, column_number
+
+ def _on_click_row_label(self, index):
+ if self._selected_cell and self._multicolumn_listbox.item_ID(index) == self._selected_cell[0]:
+ self._destroy_entry()
+
+ self._multicolumn_listbox.toogle_selection(index)
def _select_all_entry_data(self):
''' Set selection on the whole text '''
@@ -543,15 +930,15 @@
data = self._entry_popup.get()
- row_data = self._item_ID_to_row_data(item_ID)
+ row_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)
row_data[column_number] = data
- self._treeview.item(item_ID, values=row_data)
+ self._multicolumn_listbox.interior.item(item_ID, values=row_data)
self._destroy_entry()
def _update_position_of_entry(self):
if self._selected_cell:
- bbox = self._treeview.bbox(self._selected_cell[0], self._selected_cell[1])
+ bbox = self._multicolumn_listbox.interior.bbox(self._selected_cell[0], self._selected_cell[1])
if bbox == "":
self._entry_popup.place_forget()
else:
@@ -559,342 +946,114 @@
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
-
- index += 1
- self._treeview.column('#%s'%index, **kwargs)
+ self._multicolumn_listbox.configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None)
def row_data(self, index):
- try:
- item_ID = self._treeview.get_children()[index]
- except IndexError:
- raise ValueError("Row index out of range: %d"%index)
-
- return self._item_ID_to_row_data(item_ID)
+ return self._multicolumn_listbox.row_data(index)
def update_row(self, index, data):
- try:
- item = self._treeview.get_children()[index]
- except IndexError:
- raise ValueError("Row 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)
+ self._multicolumn_listbox.update_row(index,data)
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)
- self._number_of_rows -= 1
-
- 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])
-
- if self._row_header:
- # The method delete_all_selected_rows() also uses this function.
- # For this reason this part of the code is encapsulated
- self._delete_n_row_headers(1)
-
- def _delete_n_row_headers(self, n):
- list_of_slaves = self._frame_of_row_header.pack_slaves()
-
- for i in range(n):
- list_of_slaves.pop().destroy()
-
- if list_of_slaves:
- width_of_row_header= max(list_of_slaves[-1].winfo_reqwidth(), self._rowlabel_minwidth)
-
- if width_of_row_header < self._width_of_row_header:
- self._width_of_row_header = width_of_row_header
-
- for slave in self._frame_of_row_header.pack_slaves():
- slave.configure(width=width_of_row_header)
-
- self._canvas.config(width=self._frame_of_row_header.winfo_reqwidth())
- self._canvas.config(scrollregion=(0, 0, self._frame_of_row_header.winfo_reqwidth(), len(list_of_slaves)*self._rowheight))
-
+ self._multicolumn_listbox.delete_row(index)
+ if self._row_numbers:
+ self._row_numbers.pop()
+
def insert_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)
-
- self._number_of_rows += 1
-
- number_of_rows = self._number_of_rows
-
- if self._stripped_rows:
- list_of_items = self._treeview.get_children()
-
- 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])
-
- if self._row_header:
- # Creating a new row header
-
- frame_button = Frame(self._frame_of_row_header, height=self._rowheight)
- frame_button.pack()
- frame_button.pack_propagate(False)
-
- row_label = Label(frame_button, text = number_of_rows, **self._labelrow_kwargs)
- row_label.bind("<1>", lambda event, index=number_of_rows-1: self._on_click_row_label(index))
-
- if self._rowlabel_hoverbackground:
- row_label.bind("<Enter>", lambda event, row_label=row_label: row_label.configure(background=self._rowlabel_hoverbackground))
- row_label.bind("<Leave>", lambda event, row_label=row_label: row_label.configure(background=self.cget("background")))
-
- row_label.pack(expand=True, fill=BOTH)
-
- width_of_row_header= max(row_label.winfo_reqwidth(), self._rowlabel_minwidth)
-
- if width_of_row_header > self._width_of_row_header:
- self._width_of_row_header = width_of_row_header
-
- for slave in self._frame_of_row_header.pack_slaves():
- slave.configure(width=width_of_row_header)
- else:
- frame_button.configure(width=width_of_row_header)
-
- frame_button.update_idletasks()
-
- self._canvas.config(width=self._frame_of_row_header.winfo_reqwidth())
- self._canvas.config(scrollregion=(0, 0, self._frame_of_row_header.winfo_reqwidth(), self._number_of_rows*self._rowheight))
-
- def _on_click_row_label(self, index):
-
- if self._selected_cell and self._treeview.get_children()[index] == self._selected_cell[0]:
- self._destroy_entry()
-
- self.toogle_selection(index)
+ self._multicolumn_listbox.insert_row(data, index)
+ if self._row_numbers:
+ self._row_numbers.new_label()
def column_data(self, index):
- return [self._treeview.set(child_ID, index) for child_ID in self._treeview.get_children('')]
+ return self._multicolumn_listbox.column_data(index)
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
+ self._multicolumn_listbox.update_column(index, data)
def clear(self):
- # Another possibility:
- # self._treeview.delete(*self._treeview.get_children())
-
- for row in self._treeview.get_children():
- self._treeview.delete(row)
-
- if self._row_header:
- for slave in self._frame_of_row_header.pack_slaves():
- slave.destroy()
-
- self._number_of_rows = 0
-
+ self._multicolumn_listbox.clear()
+
+ if self._row_numbers:
+ self._row_numbers.delete_labels()
+
def update(self, data):
- self.clear()
-
- for row in data:
- self.insert_row(row)
-
+ self._multicolumn_listbox.update(data)
+ if self._row_numbers:
+ for i in range(len(data)):
+ self._row_numbers.new_label()
+
def focus(self, index=None):
- if index is None:
- return self._treeview.item(self._treeview.focus())
- else:
- item = self._treeview.get_children()[index]
- self._treeview.focus(item)
+ self._multicolumn_listbox.focus(index)
def state(self, state=None):
- if stateSpec is None:
- return self._treeview.state()
- else:
- self._treeview.state(state)
+ self._multicolumn_listbox.state(state)
@property
def number_of_rows(self):
- return len(self._treeview.get_children())
-
+ return self._multicolumn_listbox.number_of_rows
+
@property
def number_of_columns(self):
- return self._number_of_columns
-
+ return self._multicolumn_listbox.number_of_columns
+
def toogle_selection(self, index):
- list_of_items = self._treeview.get_children()
-
- try:
- item_ID = list_of_items[index]
- except IndexError:
- raise ValueError("Row index out of range: %d"%index)
-
- self._treeview.selection_toggle(item_ID)
+ self._multicolumn_listbox.toogle_selection(index)
def select_row(self, index):
- list_of_items = self._treeview.get_children()
-
- try:
- item_ID = list_of_items[index]
- except IndexError:
- raise ValueError("Row index out of range: %d"%index)
-
- self._treeview.selection_add(item_ID)
+ self._multicolumn_listbox.select_row(index)
def deselect_row(self, index):
- list_of_items = self._treeview.get_children()
-
- try:
- item_ID = list_of_items[index]
- except IndexError:
- raise ValueError("Row index out of range: %d"%index)
-
- self._treeview.selection_remove(item_ID)
-
+ self._multicolumn_listbox.deselect_row(index)
+
def deselect_all(self):
- self._treeview.selection_remove(self._treeview.selection())
+ self._multicolumn_listbox.deselect_all()
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))
+ self._multicolumn_listbox.set_selection(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
+ return self._multicolumn_listbox.selected_rows
@property
def indices_of_selected_rows(self):
- list_of_indices = []
- for index, item_ID in enumerate(self._treeview.get_children()):
- if item_ID in self._treeview.selection():
- list_of_indices.append(index)
-
- return list_of_indices
-
+ return self._multicolumn_listbox.indices_of_selected_rows
+
def delete_all_selected_rows(self):
- selected_items = self._treeview.selection()
- for item_ID in selected_items:
- self._treeview.delete(item_ID)
-
- self._delete_n_row_headers(len(selected_items))
-
- 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"]
-
+ number_of_deleted_rows = self._multicolumn_listbox.delete_all_selected_rows()
+
+ if self._row_numbers:
+ self._row_numbers.pop(n_labels=number_of_deleted_rows)
+
@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
-
+ return self._multicolumn_listbox.table_data
+
@table_data.setter
def table_data(self, data):
- self.update(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))
-
- if self._stripped_rows:
- list_of_items = self._treeview.get_children('')
- for i in range(len(list_of_items)):
- self._treeview.tag_configure(list_of_items[i], background=self._stripped_rows[i%2])
-
+ self._multicolumn_listbox.table_data = data
+
+ if self._row_numbers:
+ for i in range(len(data)):
+ self._row_numbers.new_label()
+
def cell_data(self, row, column):
- """Get the value of a table cell"""
- try:
- item = self._treeview.get_children()[row]
- except IndexError:
- raise ValueError("Row index out of range: %d"%row)
-
- return self._treeview.set(item, column)
-
+ return self._multicolumn_listbox.cell_data(row, column)
+
def update_cell(self, row, column, value):
- """Set the value of a table cell"""
-
- 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)
-
+ self._multicolumn_listbox.update_cell(row, column, value)
+
def __getitem__(self, index):
- if isinstance(index, tuple):
- row, column = index
- return self.cell_data(row, column)
- else:
- raise Exception("Row and column indices are required")
-
+ return self._multicolumn_listbox[index]
def __setitem__(self, index, value):
- if isinstance(index, tuple):
- row, column = index
- self.update_cell(row, column, value)
- else:
- raise Exception("Row and column indices are required")
+ self._multicolumn_listbox[index] = value
def bind(self, event, handler):
- self._treeview.bind(event, handler)
-
+ self._multicolumn_listbox.bind(event, handler)
+
+ def sort_by(self, col, descending):
+ self._multicolumn_listbox.sort_by(col, descending)
if __name__ == '__main__':
try:
@@ -914,69 +1073,69 @@
def show_info(msg):
messagebox.showinfo("Table Data", msg)
- table = Multicolumn_Listbox(root, ["column one","column two", "column three"], command=on_select, row_header=True, anchor="center")
- table.pack()
-
- table.insert_row([1,2,3])
- show_info("table.insert_row([1,2,3])")
-
- table.row.insert([4,5,7])
- show_info("table.row.insert([4,5,7])")
-
- table.update_row(0, [7,8,9])
- show_info("table.update_row(0, [4,5,6])")
-
- table.update([[1,2,3], [4,5,6]])
- show_info("table.update([[1,2,3], [4,5,6]])")
-
- table.select_row(0)
- show_info("table.select_row(0)")
-
- print("table.selected_rows")
- print(table.selected_rows)
+ mcListbox = Multicolumn_Listbox(root, ["column one","column two", "column three"], command=on_select, anchor="center")
+ mcListbox.interior.pack()
+
+ mcListbox.insert_row([1,2,3])
+ show_info("mcListbox.insert_row([1,2,3])")
+
+ mcListbox.row.insert([4,5,7])
+ show_info("mcListbox.row.insert([4,5,7])")
+
+ mcListbox.update_row(0, [7,8,9])
+ show_info("mcListbox.update_row(0, [4,5,6])")
+
+ mcListbox.update([[1,2,3], [4,5,6]])
+ show_info("mcListbox.update([[1,2,3], [4,5,6]])")
+
+ mcListbox.select_row(0)
+ show_info("mcListbox.select_row(0)")
+
+ print("mcListbox.selected_rows")
+ print(mcListbox.selected_rows)
print("\n")
- print("table.table_data")
- print(table.table_data)
+ print("mcListbox.table_data")
+ print(mcListbox.table_data)
print("\n")
- print("table.row[0]")
- print(table.row[0])
+ print("mcListbox.row[0]")
+ print(mcListbox.row[0])
print("\n")
- print("table.row_data(0)")
- print(table.row_data(0))
+ print("mcListbox.row_data(0)")
+ print(mcListbox.row_data(0))
print("\n")
- print("table.column[1]")
- print(table.column[1])
+ print("mcListbox.column[1]")
+ print(mcListbox.column[1])
print("\n")
- print("table[0,1]")
- print(table[0,1])
+ print("mcListbox[0,1]")
+ print(mcListbox[0,1])
print("\n")
- 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.delete_row(1)
- show_info("table.delete_row(1)")
-
- row = table.row[0].update([2,4,5])
- show_info("table.row[0].update([2,4,5])")
-
- table.destroy()
+ mcListbox.column[1] = ["item1", "item2"]
+
+ mcListbox.update_column(2, [8,9])
+ show_info("mcListbox.update_column(2, [8,9])")
+
+ mcListbox.clear()
+ show_info("mcListbox.clear()")
+
+ mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]
+ show_info("mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]")
+
+ mcListbox.delete_row(1)
+ show_info("mcListbox.delete_row(1)")
+
+ row = mcListbox.row[0].update([2,4,5])
+ show_info("mcListbox.row[0].update([2,4,5])")
+
+ mcListbox.destroy()
# The next table is editable: Click on the table to edit the cell
- table = Multicolumn_Listbox(root, ["column one","column two", "column three"], stripped_rows = ("white","#f2f2f2"), select_mode="none")
+ table = Tk_Table(root, ["column one","column two", "column three"], row_numbers=True, stripped_rows = ("white","#f2f2f2"), select_mode="none")
table.pack(expand=True, fill=BOTH)
table.table_data = [[0, 1, 2],
@@ -991,6 +1150,7 @@
[27, 28, 29]]
table.insert_row([1,2,3])
+
show_info("We created an editable and zebra style table. Click on a cell to edit.")
root.mainloop()