Skip to content

Instantly share code, notes, and snippets.

@mikofski
Last active December 19, 2015 05:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikofski/5907350 to your computer and use it in GitHub Desktop.
Save mikofski/5907350 to your computer and use it in GitHub Desktop.
ttk widgets
from Tkinter import Canvas, GROOVE, BOTH, X, Y, YES, RIGHT, LEFT, W
from ttk import Frame, Scrollbar, Checkbutton
import logging
import tkMessageBox
class ScrolledCanvas(Frame):
"""
A scrolling canvas of frames with checkboxes.
"""
def __init__(self, master, name=None, scrollregion=(0, 0, '5i', '5i'),
items=[], window_size=[160, 30], **canvaskw):
Frame.__init__(self, master, name=name)
self.scrollcanvas = Canvas(self, name='scrollcanvas',
scrollregion=scrollregion, **canvaskw)
self.yscroll = Scrollbar(self, name='yscroll',
command=self.scrollcanvas.yview)
self.scrollcanvas['yscrollcommand'] = self.yscroll.set
self.yscroll.pack(side=RIGHT, fill=Y)
self.scrollcanvas.pack(side=LEFT, fill=BOTH, expand=YES)
self.items = dict.fromkeys(items)
for n, i in enumerate(items):
self.items[i] = {'frame': Frame(self, name=(i.lower() + '_frame'))}
self.items[i]['frame'].config(relief=GROOVE, padding=5)
self.items[i]['chbx'] = Checkbutton(self.items[i]['frame'],
name=(i.lower() + '_chbx'))
self.items[i]['chbx']['text'] = i
self.items[i]['chbx'].pack(side=LEFT, fill=X)
y = window_size[1] / 2 + window_size[1] * n
self.items[i]['window'] = self.scrollcanvas.create_window(0, y)
self.scrollcanvas.itemconfigure(self.items[i]['window'],
window=self.items[i]['frame'],
anchor=W, width=window_size[0],
height=window_size[1])
def onLeftClick(item, event):
logging.debug(event.__dict__)
if tkMessageBox.askokcancel('scrolled canvas', item):
return
if __name__ == "__main__":
import tkFont
from Tkinter import Tk
dummy_items = ['holy grail', 'meaning of life', 'life of brian',
'jabberwocky', "monte python's flying circus",
'time bandits', 'brazil', '12 monkeys',
'adventures of baron von munchausen']
root = Tk()
maxstr = max(dummy_items, key=lambda x: len(x))
pixsz = tkFont.Font().measure(maxstr)+25
chrsz = len(maxstr)+5
scrollcanvas = ScrolledCanvas(root, scrollregion=[0, 0, '5i', '3i'],
items=dummy_items, height='2i',
window_size=[pixsz, 30], width=pixsz)
# add bindings?
for item, val in scrollcanvas.items.iteritems():
logging.debug('binding frame %s', val['frame'])
callback = lambda e, i=item: onLeftClick(i, e)
val['frame'].bind('<Button-1>', callback)
scrollcanvas.pack(expand=YES, fill=BOTH)
scrollcanvas.mainloop()
from Tkinter import N, E, W, S, END, YES, BOTH
from ttk import Frame, Treeview, Scrollbar
import tkFont
import logging
class TreeTable(Frame):
"""
A table based on :class:`ttk.Treeview`.
:Parameters:
**master** : Tkinter or ttk widget
The widget in which the :class:`TableTree` will reside.
**headers** : list of str
The column headers for the table.
**data** : list of tuples
Table data. There must be as many elements in each tuple as there \
are headers. Each tuple in the list corresponds to a row in the \
table.
"""
def __init__(self, master, headers, data, name=None):
Frame.__init__(self, master, name=name)
#: column headers
self.headers = headers
#: table data
self.data = data
#: :class:`~ttk.Treeview` that only shows "headings" not "tree columns"
self.tree = Treeview(self, columns=self.headers, show="headings",
name='tabletree')
#: vertical scrollbar
self.yscroll = Scrollbar(self, orient="vertical",
command=self.tree.yview, name='table_yscroll')
#: horizontal scrollbar
self.xscroll = Scrollbar(self, orient="horizontal",
command=self.tree.xview, name='table_xscroll')
self.tree['yscrollcommand'] = self.yscroll.set # bind to scrollbars
self.tree['xscrollcommand'] = self.xscroll.set
# position widgets and set resize behavior
self.tree.grid(column=0, row=0, sticky=(N + E + W + S))
self.yscroll.grid(column=1, row=0, sticky=(N + S))
self.xscroll.grid(column=0, row=1, sticky=(E + W))
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# build tree
for col in self.headers:
# NOTE: Use col as column identifiers, crafty!
# NOTE: Also change col to title case using str.title()
# NOTE: make lambda behave nicely in a loop using default arg!
callback = lambda c=col: self.sortby(c, False)
self.tree.heading(col, text=col.title(), command=callback)
# adjust the column's width to the header string
self.tree.column(col, width=tkFont.Font().measure(col.title()))
# insert a new top-level treeview item by suing an empty string
for item in self.data:
self.tree.insert('', END, values=item)
# adjust column's width if necessary to fit each value
for idx, val in enumerate(item):
col_width = tkFont.Font().measure(val)
# option can be specified at least 3 ways: as (a) width=None,
# (b) option='width' or (c) 'width', where 'width' can be any
# valid column option.
if self.tree.column(self.headers[idx], 'width') < col_width:
self.tree.column(self.headers[idx], width=col_width)
def sortby(self, col, descending):
"""
Sort table contents when a column header is clicked.
:Parameters:
**col**
The column identifier of the column to sort.
**descending**
False if ascending, True if descending, switches each time.
"""
logging.debug('sortby %s, descending: %s', col, descending)
# grab values to sort
data = [(self.tree.set(child, col), child)
for child in self.tree.get_children('')]
# now sort the data in place
data.sort(reverse=descending)
for idx, item in enumerate(data):
self.tree.move(item[1], '', idx)
# switch the heading so it will sort in the opposite direction
callback = lambda: self.sortby(col, not descending)
self.tree.heading(col, command=callback)
if __name__ == "__main__":
from Tkinter import Tk
dummy_data = [('holy grail', 1975), ('meaning of life', 1983),
('life of brian', 1979), ('jabberwocky', 1977),
('time bandits', 1981), ('brazil', 1985),
('12 monkeys', 1995),
('adventures of baron von munchausen', 1988)]
headers = ['movie title', 'year']
root = Tk()
treetable = TreeTable(root, headers=headers, data=dummy_data)
treetable.tree['height'] = 4
treetable.pack(expand=YES, fill=BOTH)
treetable.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment