Created
December 27, 2017 14:43
-
-
Save kogorman/7c6a621fcaaf3ac1e53de821de5a8f52 to your computer and use it in GitHub Desktop.
My scrolled table has some glitches I can't figure out
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This is the beginning of an app I want to make, just testing how it's going to look. It's useable, but there are some glitches. | |
tables3.py is based on a file I took from another project. | |
asker5.py is my app that uses tables3 to support a scrollable table. | |
You can run either one of them, but I'm mostly interested in asker. | |
1 I specify column_minwidths as a list of widths in characters, but sometimes I need to express widths in pixels. I'm not sure how to convert based on the current font of a widget. | |
2. There is some strange movement of the table when it first gets enough rows to enable the vertical scrollbar. This is most easily seen if you maximize the window to start with. | |
a. why does the table start out centered? I'd like it flush left in the canvas | |
b. why does it become flush left as soon as vertical scrolling starts? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Find a matching record in the database | |
Last Modified: Wed Dec 27 05:00:23 PST 2017 | |
""" | |
import argparse # https://docs.python.org/3.5/library/argparse.html | |
import tkinter as tk # https://docs.python.org/3.5/library/tkinter.html | |
import tkinter.ttk as ttk # https://docs.python.org/3.5/library/tkinter.ttk.html | |
import tables3 as tb # local | |
class Asker(ttk.Frame): | |
def __init__(self, root=None, center=False, verbosity=0): | |
super().__init__(root) | |
self.root = root | |
self.verbosity = verbosity | |
root.title("Asker") | |
self.pack() | |
self.results_owner = None | |
self.results_frame = None | |
self.iteration = 0 | |
self._create_widgets() | |
if center: | |
center_window(root) | |
_pairs = ( | |
('Your ID', 'user_id'), | |
('Sheet #', 'sheet_id'), | |
('Last Name', 'name_last'), | |
('First Name', 'name_first'), | |
('Number', 'house_number'), | |
('Street','street'), | |
('City', 'city') | |
) | |
def _create_widgets(self): | |
msgtx = "NOTE: Navigate with mouse or with TAB and Shift-TAB" | |
tx1 = ttk.Label(root, width=len(msgtx), text=msgtx, anchor='w') | |
tx1.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2) | |
self.entries = [] | |
for pair in self._pairs: | |
field = pair[0] | |
row = tk.Frame(root) | |
lab = ttk.Label(row, width=10, text=field, anchor='w') | |
ent = ttk.Entry(row, width=32) | |
row.pack(side=tk.TOP, anchor='w', padx=2, pady=1) | |
lab.pack(side=tk.LEFT) | |
ent.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.X) | |
self.entries.append((field, ent)) | |
noterow2 = tk.Frame(root) | |
msgtx = "NOTE: Use SPACE or mouse click on buttons" | |
tx2 = ttk.Label(noterow2, width=len(msgtx), text=msgtx, anchor='w') | |
noterow2.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2) | |
tx2.pack(side=tk.LEFT, padx=5, pady=2) | |
buttonrow = tk.Frame(root) | |
buttonrow.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2) | |
b1 = ttk.Button(buttonrow, text='Find', command=(lambda e=self.entries: self.match(e))) | |
b1.pack(side=tk.LEFT, padx=5, pady=2) | |
b2 = ttk.Button(buttonrow, text='Quit', command=root.quit) | |
b2.pack(side=tk.LEFT, padx=5, pady=2) | |
test1 = ttk.Button(buttonrow, text='Line+', command= self.addline) | |
test1.pack(side=tk.LEFT, padx=5, pady=2) | |
test2 = ttk.Button(buttonrow, text='New Search', command=self.new_results) | |
test2.pack(side=tk.LEFT, padx=5, pady=2) | |
self.results=None | |
self.new_results() | |
self.root.update() | |
self.root.geometry("%sx%s" % (1160, 740)) | |
def new_results(self): | |
if isinstance(self.results, tb.Table): | |
self.results.destroy() | |
self.results = tb.Table(self.root, ["Choose", "Name" + str(self.iteration+1), "#", "Street", "City"], | |
padx=1, pady=1, | |
button_column=0, button_text="Click", button_command=self.tattler, | |
cell_anchor=tk.W, | |
header_anchor=tk.W, | |
column_minwidths=[6, 44, 10, 33, 20], | |
scroll_horizontally=True) | |
self.results.pack(side=tk.TOP, fill=tk.X, pady=2) | |
self.iteration += 1 | |
def addline(self): | |
self.results.insert_row( | |
["Row "+str(self.iteration), | |
"name from the resulting record though it should never be as long as this excruciatingly prolix one is", | |
str(self.iteration), | |
"some street or avenue", | |
"city of residence" | |
] | |
) | |
self.root.update() | |
self.iteration += 1 | |
def tattler(self, who): | |
print("And the winner is",who) | |
def match(self,entries): | |
criteria = 0 | |
compare = '' | |
for index in range(len(entries)): | |
crit = self._pairs[index][1] | |
entry = entries[index] | |
field = entry[0] | |
text = entry[1].get() | |
if text is not None and text != '': | |
criteria += 1 | |
if criteria == 0: | |
print(" *** ERROR: no fields entered") | |
return | |
self._clear_entries(entries) | |
def _clear_entries(self, entries): | |
for index in range(len(entries)): | |
entry = entries[index] | |
entry[1].delete(0,tk.END) | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description="A program find a voter record to match a petition signature") | |
parser.add_argument("--dbname", default=None, | |
help="name of the database to work on (overrides qubic.ini file)") | |
parser.add_argument("--center", "-c", action="store_true", | |
help="center the window when starting up") | |
parser.add_argument("--verbosity","--verbose","-v",action="count",default=0, | |
help="increase output verbosity") | |
args=parser.parse_args() | |
root = tk.Tk() | |
s=ttk.Style() | |
s.configure('me.TButton',background='lawn green') # https://wiki.tcl.tk/37701 | |
s.configure('name.TButton',background='peru') # https://wiki.tcl.tk/37701 | |
s.configure('number.TButton',background='pink') # https://wiki.tcl.tk/37701 | |
s.configure('street.TButton',background='PeachPuff') # https://wiki.tcl.tk/37701 | |
s.configure('city.TButton',background='sky blue') # https://wiki.tcl.tk/37701 | |
anti = Asker(root=root, center=args.center, verbosity=args.verbosity) | |
anti.mainloop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Retrieved from https://github.com/ActiveState/code/blob/master/recipes/Python/580793_Tkinter_table_with_scrollbars/recipe-580793.py | |
# Author: Miguel Martinez Lopez | |
# Version: 0.20 | |
# Modifications: Kevin O'Gorman | |
# V 0.20.ko3 | |
# Last Modified: Wed Dec 27 06:26:40 PST 2017 | |
# License: MIT | |
try: | |
from Tkinter import Frame, Label, Message, StringVar, Canvas, Button | |
from ttk import Scrollbar | |
from Tkconstants import * | |
except ImportError: | |
from tkinter import Frame, Label, Message, StringVar, Canvas, Button | |
from tkinter.ttk import Scrollbar | |
from tkinter.constants import * | |
import platform | |
OS = platform.system() | |
class Mousewheel_Support(object): | |
# implemetation of singleton pattern | |
_instance = None | |
def __new__(cls, *args, **kwargs): | |
if not cls._instance: | |
cls._instance = object.__new__(cls) | |
return cls._instance | |
def __init__(self, root, horizontal_factor = 2, vertical_factor=2): | |
self._active_area = None | |
if isinstance(horizontal_factor, int): | |
self.horizontal_factor = horizontal_factor | |
else: | |
raise Exception("Vertical factor must be an integer.") | |
if isinstance(vertical_factor, int): | |
self.vertical_factor = vertical_factor | |
else: | |
raise Exception("Horizontal factor must be an integer.") | |
if OS == "Linux" : | |
root.bind_all('<4>', self._on_mousewheel, add='+') | |
root.bind_all('<5>', self._on_mousewheel, add='+') | |
else: | |
# Windows and MacOS | |
root.bind_all("<MouseWheel>", self._on_mousewheel, add='+') | |
def _on_mousewheel(self,event): | |
if self._active_area: | |
self._active_area.onMouseWheel(event) | |
def _mousewheel_bind(self, widget): | |
self._active_area = widget | |
def _mousewheel_unbind(self): | |
self._active_area = None | |
def add_support_to(self, widget=None, xscrollbar=None, yscrollbar=None, what="units", horizontal_factor=None, vertical_factor=None): | |
if xscrollbar is None and yscrollbar is None: | |
return | |
if xscrollbar is not None: | |
horizontal_factor = horizontal_factor or self.horizontal_factor | |
xscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget,'x', self.horizontal_factor, what) | |
xscrollbar.bind('<Enter>', lambda event, scrollbar=xscrollbar: self._mousewheel_bind(scrollbar) ) | |
xscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind()) | |
if yscrollbar is not None: | |
vertical_factor = vertical_factor or self.vertical_factor | |
yscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget,'y', self.vertical_factor, what) | |
yscrollbar.bind('<Enter>', lambda event, scrollbar=yscrollbar: self._mousewheel_bind(scrollbar) ) | |
yscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind()) | |
main_scrollbar = yscrollbar if yscrollbar is not None else xscrollbar | |
if widget is not None: | |
if isinstance(widget, list) or isinstance(widget, tuple): | |
list_of_widgets = widget | |
for widget in list_of_widgets: | |
widget.bind('<Enter>',lambda event: self._mousewheel_bind(widget)) | |
widget.bind('<Leave>', lambda event: self._mousewheel_unbind()) | |
widget.onMouseWheel = main_scrollbar.onMouseWheel | |
else: | |
widget.bind('<Enter>',lambda event: self._mousewheel_bind(widget)) | |
widget.bind('<Leave>', lambda event: self._mousewheel_unbind()) | |
widget.onMouseWheel = main_scrollbar.onMouseWheel | |
@staticmethod | |
def _make_mouse_wheel_handler(widget, orient, factor = 1, what="units"): | |
view_command = getattr(widget, orient+'view') | |
if OS == 'Linux': | |
def onMouseWheel(event): | |
if event.num == 4: | |
view_command("scroll",(-1)*factor, what) | |
elif event.num == 5: | |
view_command("scroll",factor, what) | |
elif OS == 'Windows': | |
def onMouseWheel(event): | |
view_command("scroll",(-1)*int((event.delta/120)*factor), what) | |
elif OS == 'Darwin': | |
def onMouseWheel(event): | |
view_command("scroll",event.delta, what) | |
return onMouseWheel | |
class Scrolling_Area(Frame, object): | |
def __init__(self, master, width=None, anchor=None, height=None, mousewheel_speed = 2, scroll_horizontally=True, | |
xscrollbar=None, scroll_vertically=True, yscrollbar=None, outer_background=None, inner_frame=Frame, **kw): | |
Frame.__init__(self, master, class_=self.__class__) | |
self.anchor = N if anchor is None else anchor | |
if outer_background: | |
self.configure(background=outer_background) | |
self.grid_columnconfigure(0, weight=1) | |
self.grid_rowconfigure(0, weight=1) | |
self._width = width | |
self._height = height | |
self.canvas = Canvas(self, background=outer_background, highlightthickness=0, width=width, height=height) | |
self.canvas.grid(row=0, column=0, sticky=N+E+W+S) | |
if scroll_vertically: | |
if yscrollbar is not None: | |
self.yscrollbar = yscrollbar | |
else: | |
self.yscrollbar = Scrollbar(self, orient=VERTICAL) | |
self.yscrollbar.grid(row=0, column=1,sticky=N+S) | |
self.canvas.configure(yscrollcommand=self.yscrollbar.set) | |
self.yscrollbar['command']=self.canvas.yview | |
else: | |
self.yscrollbar = None | |
if scroll_horizontally: | |
if xscrollbar is not None: | |
self.xscrollbar = xscrollbar | |
else: | |
self.xscrollbar = Scrollbar(self, orient=HORIZONTAL) | |
self.xscrollbar.grid(row=1, column=0, sticky=E+W) | |
self.canvas.configure(xscrollcommand=self.xscrollbar.set) | |
self.xscrollbar['command']=self.canvas.xview | |
else: | |
self.xscrollbar = None | |
self.rowconfigure(0, weight=1) | |
self.columnconfigure(0, weight=1) | |
self.innerframe = inner_frame(self.canvas, **kw) | |
self.innerframe.pack(anchor=self.anchor) | |
self.canvas.create_window(0, 0, window=self.innerframe, anchor='nw', tags="inner_frame") | |
self.canvas.bind('<Configure>', self._on_canvas_configure) | |
Mousewheel_Support(self).add_support_to(self.canvas, xscrollbar=self.xscrollbar, yscrollbar=self.yscrollbar) | |
@property | |
def width(self): | |
return self.canvas.winfo_width() | |
@width.setter | |
def width(self, width): | |
self.canvas.configure(width= width) | |
@property | |
def height(self): | |
return self.canvas.winfo_height() | |
@height.setter | |
def height(self, height): | |
self.canvas.configure(height = height) | |
def set_size(self, width, height): | |
self.canvas.configure(width=width, height = height) | |
def _on_canvas_configure(self, event): | |
width = max(self.innerframe.winfo_reqwidth(), event.width) | |
height = max(self.innerframe.winfo_reqheight(), event.height) | |
self.canvas.configure(scrollregion="0 0 %s %s" % (width, height)) | |
self.canvas.itemconfigure("inner_frame", width=width, height=height) | |
def update_viewport(self): | |
self.update() | |
window_width = self.innerframe.winfo_reqwidth() | |
window_height = self.innerframe.winfo_reqheight() | |
if self._width is None: | |
canvas_width = window_width | |
else: | |
canvas_width = min(self._width, window_width) | |
if self._height is None: | |
canvas_height = window_height | |
else: | |
canvas_height = min(self._height, window_height) | |
self.canvas.configure(scrollregion="0 0 %s %s" % (window_width, window_height), width=canvas_width, height=canvas_height) | |
self.canvas.itemconfigure("inner_frame", width=window_width, height=window_height) | |
class Cell(Frame): | |
"""Base class for cells""" | |
class Data_Cell(Cell): | |
def __init__(self, master, variable, minwidth=None, anchor=W, bordercolor=None, borderwidth=1, padx=0, pady=0, | |
background=None, foreground=None, font=None): | |
Cell.__init__(self, master, background=background, highlightbackground=bordercolor, highlightcolor=bordercolor, | |
highlightthickness=borderwidth, bd= 0) | |
self._message_widget = Message(self, textvariable=variable, font=font, background=background, foreground=foreground, | |
justify=LEFT) | |
if minwidth is not None: | |
# FIXME: should use font size, not constant 10 | |
self._message_widget.configure(width=minwidth*10 + 2*padx) | |
self._message_widget.pack(expand=True, padx=padx, pady=pady, anchor=anchor) | |
class Button_Cell(Cell): | |
def __init__(self, master, variable, text, minwidth=None, anchor=W, bordercolor=None, borderwidth=1, padx=0, pady=0, | |
background=None, foreground=None, font=None, command=None): | |
if command is None: | |
raise Exception("No command given to Button_Cell") | |
Cell.__init__(self, master, background=background, | |
highlightbackground=bordercolor, highlightcolor=bordercolor, | |
highlightthickness=borderwidth, bd= 0) | |
self._message_widget = Button(self, text=text, font=font, background=background, foreground=foreground, | |
highlightcolor="blue", highlightbackground="lawn green", highlightthickness=4, | |
justify=LEFT, command=(lambda :command(self.gettext(variable)))) | |
if minwidth is not None: | |
# NOTE: unlike Message, the width of a button is in small units (points?) | |
self._message_widget.configure(width=minwidth) | |
self._message_widget.pack(expand=True, padx=padx, pady=pady, anchor=anchor) | |
def gettext(self,buttonvar): | |
return buttonvar.get() | |
class Header_Cell(Cell): | |
def __init__(self, master, text, minwidth=0, minheight=0, anchor=CENTER, bordercolor=None, borderwidth=1, padx=0, pady=0, | |
background=None, foreground=None, font=None, separator=True): | |
Cell.__init__(self, master, background=background, highlightbackground=bordercolor, highlightcolor=bordercolor, | |
highlightthickness=borderwidth, bd= 0) | |
self.pack_propagate(False) | |
self._header_label = Label(self, text=text, background=background, foreground=foreground, font=font) | |
self._header_label.pack(padx=padx, pady=pady, expand=True) | |
if separator and bordercolor is not None: | |
separator = Frame(self, height=2, background=bordercolor, bd=0, highlightthickness=0, class_="Separator") | |
separator.pack(fill=X, anchor=anchor) | |
self.update() | |
height = max(minheight, self._header_label.winfo_reqheight()) + 2*padx | |
width = max(minwidth, self._header_label.winfo_reqwidth()) + 2*pady | |
self.configure(height=height, width=width) | |
class Table(Frame): | |
def __init__(self, master, columns, column_weights=None, column_minwidths=None, height=500, minwidth=20, minheight=20, padx=5, | |
pady=5, cell_font=None, cell_foreground="black", cell_background="white", cell_anchor=W, header_font=None, | |
header_background="white", header_foreground="black", header_anchor=W, bordercolor = "#999999", | |
innerborder=True, outerborder=True, striped_rows=("#EEEEEE", "white"), on_change_data=None, | |
mousewheel_speed = 2, scroll_horizontally=False, scroll_vertically=True, | |
button_column=None, button_text=None, button_foreground="red", button_command=None): | |
if button_column is None: | |
if button_text is not None: | |
raise Exception("Button text given but not button column") | |
if button_command is not None: | |
raise Exception("Button command given but not button column") | |
else: | |
if button_column < 0 or button_column >= len(columns): | |
raise Exception("button_column given is not an existing column") | |
if button_text is None: | |
raise Exception("No button text supplied for the button column") | |
if button_command is None: | |
raise Exception("No button command supplied for the button column") | |
outerborder_width = 1 if outerborder else 0 | |
Frame.__init__(self,master, bd= 0) | |
self._column_minwidths=column_minwidths | |
self._cell_background = cell_background | |
self._cell_foreground = cell_foreground | |
self._cell_font = cell_font | |
self._cell_anchor = cell_anchor | |
self._button_column = button_column | |
self._button_text = button_text | |
self._button_foreground = button_foreground | |
self._button_command=button_command | |
self._striped_rows = striped_rows | |
self._padx = padx | |
self._pady = pady | |
self._bordercolor = bordercolor | |
self._innerborder_width = 1 if innerborder else 0 | |
self._data_vars = [] | |
self._columns = columns | |
self._number_of_rows = 0 | |
self._number_of_columns = len(columns) | |
self.grid_columnconfigure(0, weight=1) | |
self.grid_rowconfigure(1, weight=1) | |
self._head = Frame(self, highlightbackground=bordercolor, highlightcolor=bordercolor, highlightthickness=outerborder_width, bd= 0) | |
self._head.grid(row=0, column=0, sticky=E+W) | |
header_separator = False if outerborder else True | |
for j in range(len(columns)): | |
column_name = columns[j] | |
if column_minwidths is not None and column_minwidths[j] is not None: | |
minwidth = column_minwidths[j]* 10 | |
else: | |
minwidth = 0 | |
header_cell = Header_Cell(self._head, text=column_name, minwidth=minwidth, borderwidth=self._innerborder_width, | |
font=header_font, background=header_background, foreground=header_foreground, padx=padx, pady=pady, | |
bordercolor=bordercolor, anchor=header_anchor, separator=header_separator) | |
header_cell.grid(row=0, column=j, sticky=N+E+W+S) | |
add_scrollbars = scroll_horizontally or scroll_vertically | |
if add_scrollbars: | |
if scroll_horizontally: | |
xscrollbar = Scrollbar(self, orient=HORIZONTAL) | |
xscrollbar.grid(row=2, column=0, sticky=E+W) | |
else: | |
xscrollbar = None | |
if scroll_vertically: | |
yscrollbar = Scrollbar(self, orient=VERTICAL) | |
yscrollbar.grid(row=1, column=1, sticky=N+S) | |
else: | |
yscrollbar = None | |
scrolling_area = Scrolling_Area(self, width=self._head.winfo_reqwidth(), height=height, scroll_horizontally=scroll_horizontally, xscrollbar=xscrollbar, scroll_vertically=scroll_vertically, yscrollbar=yscrollbar) | |
scrolling_area.grid(row=1, column=0, sticky=E+W) | |
self._body = Frame(scrolling_area.innerframe, highlightbackground=bordercolor, highlightcolor=bordercolor, highlightthickness=outerborder_width, bd= 0) | |
self._body.pack() | |
def on_change_data(): | |
scrolling_area.update_viewport() | |
else: | |
self._body = Frame(self, height=height, highlightbackground=bordercolor, highlightcolor=bordercolor, highlightthickness=outerborder_width, bd= 0) | |
self._body.grid(row=1, column=0, sticky=N+E+W+S) | |
if column_weights is None: | |
for j in range(len(columns)): | |
self._body.grid_columnconfigure(j, weight=1) | |
else: | |
for j, weight in enumerate(column_weights): | |
self._body.grid_columnconfigure(j, weight=weight) | |
if column_minwidths is not None: | |
for j, minwidth in enumerate(column_minwidths): | |
if minwidth is None: | |
header_cell = self._head.grid_slaves(row=0, column=j)[0] | |
minwidth = header_cell.winfo_reqwidth() | |
else: | |
# FIXME: should use font geometry, not constant 10 | |
minwidth *= 10 | |
self._body.grid_columnconfigure(j, minsize=minwidth) | |
else: | |
for j in range(len(columns)): | |
header_cell = self._head.grid_slaves(row=0, column=j)[0] | |
minwidth = header_cell.winfo_reqwidth() | |
self._body.grid_columnconfigure(j, minsize=minwidth) | |
self._on_change_data = on_change_data | |
def _append_n_rows(self, n): | |
"""Appends a number of empty rows to the end of the table. | |
The entries in the row are bound to Tkinter StringVar | |
Args: | |
self: the Table | |
n: the number of rows to append. | |
""" | |
number_of_rows = self._number_of_rows | |
number_of_columns = self._number_of_columns | |
stripelen = len(self._striped_rows) | |
for i in range(number_of_rows, number_of_rows+n): | |
list_of_vars = [] | |
for j in range(number_of_columns): | |
mywidth = None if self._column_minwidths is None else self._column_minwidths[j] | |
var = StringVar() | |
list_of_vars.append(var) | |
if j == self._button_column: | |
cell = Button_Cell(self._body, var, self._button_text, minwidth=mywidth, borderwidth=self._innerborder_width, | |
bordercolor=self._bordercolor, padx=self._padx, pady=self._pady, | |
background=self._striped_rows[i%stripelen], foreground=self._button_foreground, | |
font=self._cell_font, anchor=self._cell_anchor, command=self._button_command) | |
cell | |
else: | |
cell = Data_Cell(self._body, minwidth=mywidth, borderwidth=self._innerborder_width, variable=var, | |
bordercolor=self._bordercolor, padx=self._padx, pady=self._pady, | |
background=self._striped_rows[i%stripelen], foreground=self._cell_foreground, | |
font=self._cell_font, anchor=self._cell_anchor) | |
cell.grid(row=i, column=j, sticky=N+E+W+S) | |
self._data_vars.append(list_of_vars) | |
if number_of_rows == 0: | |
for j in range(self.number_of_columns): | |
header_cell = self._head.grid_slaves(row=0, column=j)[0] | |
data_cell = self._body.grid_slaves(row=0, column=j)[0] | |
data_cell.bind("<Configure>", | |
lambda event, header_cell=header_cell: header_cell.configure(width=event.width), add="+") | |
self._number_of_rows += n | |
def _pop_n_rows(self, n): | |
"""Removes a number of rows from the end of the table. | |
""" | |
number_of_rows = self._number_of_rows | |
number_of_columns = self._number_of_columns | |
for i in range(number_of_rows-n, number_of_rows): | |
for j in range(number_of_columns): | |
self._body.grid_slaves(row=i, column=j)[0].destroy() | |
self._data_vars.pop() | |
self._number_of_rows -= n | |
def set_data(self, data): | |
"""Sets or replaces the entire data set. | |
""" | |
n = len(data) | |
m = len(data[0]) | |
number_of_rows = self._number_of_rows | |
if number_of_rows > n: | |
self._pop_n_rows(number_of_rows-n) | |
elif number_of_rows < n: | |
self._append_n_rows(n-number_of_rows) | |
for i in range(n): | |
for j in range(m): | |
self._data_vars[i][j].set(data[i][j]) | |
if self._on_change_data is not None: self._on_change_data() | |
def get_data(self): | |
"""Returns the data from the table. | |
""" | |
number_of_rows = self._number_of_rows | |
number_of_columns = self.number_of_columns | |
data = [] | |
for i in range(number_of_rows): | |
row = [] | |
row_of_vars = self._data_vars[i] | |
for j in range(number_of_columns): | |
cell_data = row_of_vars[j].get() | |
row.append(cell_data) | |
data.append(row) | |
return data | |
@property | |
def number_of_rows(self): | |
return self._number_of_rows | |
@property | |
def number_of_columns(self): | |
return self._number_of_columns | |
def row(self, index, data=None): | |
"""Get or set a row (depending on the presence of the data parameter) | |
""" | |
if data is None: | |
row = [] | |
row_of_vars = self._data_vars[index] | |
for j in range(self.number_of_columns): | |
row.append(row_of_vars[j].get()) | |
return row | |
else: | |
number_of_columns = self.number_of_columns | |
if len(data) != number_of_columns: | |
raise ValueError("Row data has %d elements but should be %d: %s"%(len(data), number_of_columns, data)) | |
row_of_vars = self._data_vars[index] | |
for j in range(number_of_columns): | |
row_of_vars[index][j].set(data[j]) | |
if self._on_change_data is not None: self._on_change_data() | |
def column(self, index, data=None): | |
"""Get or set the contents of an entire column (depeinding on the presence of the data parameter) | |
""" | |
number_of_rows = self._number_of_rows | |
if data is None: | |
column= [] | |
for i in range(number_of_rows): | |
column.append(self._data_vars[i][index].get()) | |
return column | |
else: | |
if len(data) != number_of_rows: | |
raise ValueError("Row data has %d elements but should be %d: %s"%(len(data), number_of_columns, data)) | |
for i in range(number_of_columns): | |
self._data_vars[i][index].set(data[i]) | |
if self._on_change_data is not None: self._on_change_data() | |
def clear(self): | |
"""Clear the contents of all data cells | |
""" | |
number_of_rows = self._number_of_rows | |
number_of_columns = self._number_of_columns | |
for i in range(number_of_rows): | |
for j in range(number_of_columns): | |
self._data_vars[i][j].set("") | |
if self._on_change_data is not None: self._on_change_data() | |
def delete_row(self, index): | |
"""Delete the row at a given index. | |
""" | |
i = index | |
while i < self._number_of_rows: | |
row_of_vars_1 = self._data_vars[i] | |
row_of_vars_2 = self._data_vars[i+1] | |
j = 0 | |
while j <self.number_of_columns: | |
row_of_vars_1[j].set(row_of_vars_2[j]) | |
i += 1 | |
self._pop_n_rows(1) | |
if self._on_change_data is not None: self._on_change_data() | |
def insert_row(self, data, index=END): | |
"""Insert a row at a given index. | |
Adds one row. If placed internally, moves subesquent rows towards the | |
new end of the table. | |
Args: | |
self: the table | |
data: a row of values | |
index: the position for the row (default = END) | |
command: to be executed when the button on this row is clicked. | |
Must be omitted or None when there is no button. | |
""" | |
self._append_n_rows(1) | |
if index == END: | |
index = self._number_of_rows - 1 | |
i = self._number_of_rows-1 | |
while i > index: | |
row_of_vars_1 = self._data_vars[i-1] | |
row_of_vars_2 = self._data_vars[i] | |
j = 0 | |
while j < self.number_of_columns: | |
row_of_vars_2[j].set(row_of_vars_1[j]) | |
j += 1 | |
i -= 1 | |
list_of_cell_vars = self._data_vars[index] | |
for cell_var, cell_data in zip(list_of_cell_vars, data): | |
cell_var.set(cell_data) | |
if self._on_change_data is not None: self._on_change_data() | |
def cell(self, row, column, data=None): | |
"""Set or get the value of a table cell; call _on_change_data (if) | |
""" | |
if data is None: | |
return self._data_vars[row][column].get() | |
else: | |
self._data_vars[row][column].set(data) | |
if self._on_change_data is not None: self._on_change_data() | |
def __getitem__(self, index): | |
"""Get the value of a cell, given a tuple of its coordinates. | |
""" | |
if isinstance(index, tuple): | |
row, column = index | |
return self.cell(row, column) | |
else: | |
raise Exception("Row and column indices are required") | |
def __setitem__(self, index, value): | |
"""Set a value at given spot specified as a tuple | |
""" | |
if isinstance(index, tuple): | |
row, column = index | |
self.cell(row, column, value) | |
else: | |
raise Exception("Row and column indices are required") | |
def on_change_data(self, callback): | |
"""Set the _on_change_data for this Table | |
""" | |
self._on_change_data = callback | |
def tattler(message): | |
print(message) | |
if __name__ == "__main__": | |
try: | |
from Tkinter import Tk | |
except ImportError: | |
from tkinter import Tk | |
root = Tk() | |
table = Table(root, ["Click me","column A", "column B", "column C"], column_minwidths=[None, None, None, None], | |
button_column=0, button_text="click me", button_command=tattler, | |
button_foreground="beige", bordercolor="navy", | |
striped_rows=("green","orange","RoyalBlue1")) | |
table.pack() | |
table.set_data([["row 0",1,2,3],["row 1",4,5,6], ["row 2",7,8,9], ["row 3",10,11,12], ["row 4",13,14,15], | |
["row 5", 15,16,18], ["row 6",19,20,21]]) | |
table.cell(0,1, " a fdas fasd fasdf asdf asdfasdf asdf asdfa sdfas asd sadf ") | |
table.insert_row(["row 7",22,23,24]) | |
table.insert_row(["row 8", 25,26,27]) | |
table.cell(0,0,"Jackpot!") | |
root.update() | |
root.geometry("%sx%s"%(root.winfo_reqwidth()+50,250)) | |
root.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is the beginning of an app I want to make, just testing how it's going to look. It's useable, but there are some glitches.
tables3.py is based on a file I took from another project.
asker5.py is my app that uses tables3 to support a scrollable table.
You can run either one of them, but I'm mostly interested in asker.