CptS 481 - Python Software Construction

Unit 15: tkinter

In [1]:
from IPython.core.display import HTML, Image, display
HTML(open("../include/notes.css", "r").read())
Out[1]:
In [83]:
# make sure the demos and applications directories exist
! [ -d demos ] || mkdir demos
! [ -d applications ] || mkdir applications

# Use this to enable/disable execution of the Tkinter examples.
# (If they are disabled, this notebook will recreate them without
# interaction.)
runTkinter = True

tkinter is a widely-available, widely-used GUI toolkit for Python. It's usually packaged with Python distributions and runs on all popular platforms. Other features:

  • wrapper for Tk, which is written in Tcl

  • other packages (e.g. Python Megawidgets, sometimes matplotlib) are built on top of tkinter

Other GUI toolkits for Python include:

  • PyQt (and PySide)

    part of the (huge) Qt development framework

  • wxPython

    wrapper for wxWidgets

  • kivy

    newest one, includes multi-touch

  • Pygame

    wrapper for Simple Directmedia Layer (SDL) -- targeted at games

  • PyGTK

    wrapper for GTK+, successor is GObject (and PyGObject)

"Hello, world!" in tkinter

In [3]:
%%file demos/d00_first.py
from tkinter import Tk, Label

tk = Tk()
w = Label(tk, text='Hello, world!', font=('TimesRoman', 12, 'bold'))
w.grid()
tk.mainloop()
Overwriting demos/d00_first.py
In [4]:
if runTkinter:
    !python3 demos/d*_first.py # the wildcard * allows for renumbering

Okay, that's hard to see, so let's make it bigger:

In [5]:
%%file demos/d01_firstLarger.py
from tkinter import Tk, Label

tk = Tk()
label = Label(tk, text='Hello, world!', font=('Helvetica', 72, 'bold'))
label.grid()
tk.mainloop()
Overwriting demos/d01_firstLarger.py
In [6]:
if runTkinter:
    !python3 demos/d*_firstLarger.py

First Interactive tkinter Program

But that's not very interactive. Let's add interaction and be a bit more Pythonic by creating an Application class that inherits from a tkinter class.

In [7]:
%%file demos/d02_quitButton.py
from tkinter import Frame, Button, Tk

class Application(Frame):

    def __init__(self, parent=None):
        # always be sure to do this with tkinter child classes...
        super().__init__(parent)
        quitButton = Button(self, text="Goodbye, World!",
                            command=self.quit,
                            font=('times', 24))
        quitButton.grid()

root = Tk()
app = Application(root) # Instantiate the application class
app.grid() # "grid" is a Tkinter geometry manager
root.title("Sample application")
root.mainloop() # Wait for events, until "quit()" method is called
print("done")
Overwriting demos/d02_quitButton.py
In [8]:
if runTkinter:
    !python3 demos/d*_quitButton.py

Note the object orientation. Frames are containers for other widgets and it's comon to inherit Frame. Frames can be placed within other Frames, so basically we're actually creating a new widget.

What's Possible With tkinter?

Here's a little demo with just 50 lines of Python and tkinter. Don't worry about the details for now. (This is from an out-of-print book by John Grayson, modfied by me.)

In [9]:
%%file demos/d03_grayson_calculator.py
from tkinter import *

operators = "+", "-", "*", "/", "//", "=", "%", "**", "(", ")"

class Calculator(Frame):

    def __init__(self):
        super().__init__()
        self.option_add('*Font', 'Verdana 16 bold')
        self.master.title('Simple Calculator')
        self.master.iconname("calculator")

        self.display = StringVar()
        entry = Entry(self, relief=SUNKEN, textvariable=self.display)
        entry.pack(side=TOP, expand=YES, fill=BOTH)

        for key in ("123", "456", "789", "-0."):
            keyFrame = Frame(self)
            keyFrame.pack(side=TOP, expand=YES, fill=BOTH)

            for char in key:
                # Note that this "def" works identically to a lambda
                # expression assigned to the name "updateDisplay".
                # Using a lambda expression just eliminates the name.
                #
                # Note the use of argument defaults to pass arguments
                # to a function that's going to be called without
                # arguments from within "button".
                def updateDisplay(w=self.display, c=char):
                    # Update the widget with `c` appended to its
                    # current contents.
                    return w.set(w.get() + c)
                btn = Button(keyFrame, text=char, command=updateDisplay) 
                btn.pack(side=LEFT, expand=YES, fill=BOTH)

        operatorFrame = Frame(self)
        operatorFrame.pack(side=TOP, expand=YES, fill=BOTH)
        for operator in operators:
            # This "if" statement selects one of two definitions
            # (i.e. assignments) of the function (reference)
            # "onPress".
            if operator == '=':
                # Note that we don't need to define a function (or use
                # a lambda expression) when we have a method that
                # takes no arguments (except for `self`).
                onPress = self.evaluateDisplay
            else:
                def onPress(display=self.display, s = ' {} '.format(
                            operator)):
                    return display.set(display.get() + s)
            # It's often common practice to give use the name of a
            # keyword argument for the argument value. We could have
            # used "command" instead of "onPress". The resulting
            # argument syntax "command=command" is confusing at first,
            # but makes good sense upon reflection.
            btn = Button(operatorFrame, text=operator, command=onPress) 
            btn.pack(side=LEFT, expand=YES, fill=BOTH)

        clearF = Frame(self)
        clearF.pack(side=BOTTOM, expand=YES, fill=BOTH)

        btn = Button(self, text='Clear', command=self.clearDisplay) 
        btn.pack(side=LEFT, expand=YES, fill=BOTH)

    def clearDisplay(self):
        """
        clears out the display readout
        """
        self.display.set('')

    def evaluateDisplay(self):
        """
        evaluates the display readout, replacing it with a string
        representation of its evaluation
        """
        try:
            expression = self.display.get()
            self.display.set(str(eval(expression)))
        except:
            self.display.set("ERROR")


calculator = Calculator()
calculator.pack(expand=YES, fill=BOTH) # Pack geometry manager
calculator.mainloop()
Overwriting demos/d03_grayson_calculator.py
In [10]:
if runTkinter:
    !python3 demos/d*_grayson_calculator.py

Here's an even more impressive demonstration of what's possible with tkinter. This example, from Grayson, makes use of the Pmw ("Python Megawidget") package, which we don't have time to discuss. Also, not all of the buttons work, but it would be easy to make them do so.

In [11]:
%%file applications/tt42_grayson.py
from tkinter   import *
import Pmw, string

class SLabel(Frame):
    """ SLabel defines a 2-sided label within a Frame. The
        left hand label has blue letters the right has white letters """
    def __init__(self, master, leftl, rightl):
        Frame.__init__(self, master, bg='gray40')
        self.pack(side=LEFT, expand=YES, fill=BOTH)
        Label(self, text=leftl, fg='steelblue1',
                     font=("arial", 6, "bold"), width=5, bg='gray40').pack(
                         side=LEFT, expand=YES, fill=BOTH)
        Label(self, text=rightl, fg='white',
                     font=("arial", 6, "bold"), width=1, bg='gray40').pack(
                         side=RIGHT, expand=YES, fill=BOTH)

class Key(Button):
    def __init__(self, master, font=('arial', 8, 'bold'), 
                 fg='white',width=5, borderwidth=5, **kw):
        kw['font'] = font
        kw['fg'] = fg
        kw['width'] = width
        kw['borderwidth'] = borderwidth
        Button.__init__(*(self, master), **kw)
        self.pack(side=LEFT, expand=NO, fill=NONE)
        
class Calculator(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, bg='gray40')
        self.pack(expand=YES, fill=BOTH)
        self.master.title('Tkinter Toolkit TT-42')
        self.master.iconname('Tk-42')
        self.calc = Evaluator()        # This is our evaluator
        self.buildCalculator()         # Build the widgets
        # This is an incomplete dictionary - a good exercise!
        self.actionDict = {
                'second': self.doThis, 'mode':    self.doThis,
                'delete': self.doThis, 'alpha':   self.doThis,
                'stat':   self.doThis, 'math':    self.doThis,
                'matrix': self.doThis, 'program': self.doThis,
                'vars':   self.doThis, 'clear':   self.clearall,
                'sin':    self.doThis, 'cos':     self.doThis,
                'tan':    self.doThis, 'up':      self.doThis,
                'X1':     self.doThis, 'X2':      self.doThis,
                'log':    self.doThis, 'ln':      self.doThis,
                'store':  self.doThis, 'off':     self.turnoff,
                'neg':    self.doThis, 'enter':   self.doEnter,
                           }
        self.current = ""
                           
    def doThis(self,action):
        print(('"%s" has not been implemented' % action))
        
    def turnoff(self, *args):
        self.quit()

    def clearall(self, *args):
        self.current = ""
        self.display.component('text').delete(1.0, END)

    def doEnter(self, *args):
        result = self.calc.runpython(self.current)
        if result:
            self.display.insert(END, '\n')
            self.display.insert(END, '%s\n' % result, 'ans')
        self.current = ""
         
    def doKeypress(self, event):
        key = event.char
        if not key in ['\b']:
            self.current = self.current + event.char
        if key == '\b':
            self.current = self.current[:-1]
              
    def keyAction(self, key):
        self.display.insert(END, key)
        self.current = self.current + key
         
    def evalAction(self, action):
        try:
            self.actionDict[action](action)
        except KeyError:
            pass

    def buildCalculator(self):
        FUN   = 1            # Designates a Function
        KEY   = 0            # Designates a Key
        KC1   = 'gray30'     # Dark Keys
        KC2   = 'gray50'     # Light Keys
        KC3   = 'steelblue1' # Light Blue Key
        KC4   = 'steelblue'  # Dark Blue Key
        keys = [
            [('2nd',  '',     '',  KC3, FUN, 'second'),  # Row 1
             ('Mode', 'Quit', '',  KC1, FUN, 'mode'),
             ('Del',  'Ins',  '',  KC1, FUN, 'delete'),
             ('Alpha','Lock', '',  KC2, FUN, 'alpha'),
             ('Stat', 'List', '',  KC1, FUN, 'stat')],
            [('Math', 'Test', 'A', KC1, FUN, 'math'),    # Row 2
             ('Mtrx', 'Angle','B', KC1, FUN, 'matrix'),
             ('Prgm', 'Draw', 'C', KC1, FUN, 'program'),
             ('Vars', 'YVars','',  KC1, FUN, 'vars'),
             ('Clr',  '',     '',  KC1, FUN, 'clear')],        
            [('X-1',  'Abs',  'D', KC1, FUN, 'X1'),      # Row 3
             ('Sin',  'Sin-1','E', KC1, FUN, 'sin'),
             ('Cos',  'Cos-1','F', KC1, FUN, 'cos'),
             ('Tan',  'Tan-1','G', KC1, FUN, 'tan'),
             ('^',    'PI',   'H', KC1, FUN, 'up')],
            [('X2',   'Root', 'I', KC1, FUN, 'X2'),      # Row 4
             (',',    'EE',   'J', KC1, KEY, ','),
             ('(',    '{',    'K', KC1, KEY, '('),
             (')',    '}',    'L', KC1, KEY, ')'),
             ('/',    '',     'M', KC4, KEY, '/')],
            [('Log',  '10x',  'N', KC1, FUN, 'log'),     # Row 5
             ('7',    'Un-1', 'O', KC2, KEY, '7'),
             ('8',    'Vn-1', 'P', KC2, KEY, '8'),
             ('9',    'n',    'Q', KC2, KEY, '9'),
             ('X',    '[',    'R', KC4, KEY, '*')],
            [('Ln',   'ex',   'S', KC1, FUN, 'ln'),      # Row 6
             ('4',    'L4',   'T', KC2, KEY, '4'),
             ('5',    'L5',   'U', KC2, KEY, '5'),
             ('6',    'L6',   'V', KC2, KEY, '6'),
             ('-',    ']',    'W', KC4, KEY, '-')],
            [('STO',  'RCL',  'X', KC1, FUN, 'store'),   # Row 7
             ('1',    'L1',   'Y', KC2, KEY, '1'),
             ('2',    'L2',   'Z', KC2, KEY, '2'),
             ('3',    'L3',   '',  KC2, KEY, '3'),
             ('+',    'MEM',  '"', KC4, KEY, '+')],
            [('Off',  '',     '',  KC1, FUN, 'off'),     # Row 8
             ('0',    '',     '',  KC2, KEY, '0'),
             ('.',    ':',    '',  KC2, KEY, '.'),
             ('(-)',  'ANS',  '?', KC2, FUN, 'neg'),
             ('Enter','Entry','',  KC4, FUN, 'enter')]]

        self.display = Pmw.ScrolledText(self, hscrollmode='dynamic',
                      vscrollmode='dynamic', hull_relief='sunken',
                      hull_background='gray40', hull_borderwidth=10, 
                      text_background='honeydew4', text_width=16,
                      text_foreground='black', text_height=6,
              text_padx=10, text_pady=10, text_relief='groove',
                      text_font=('arial', 12, 'bold'))
        self.display.pack(side=TOP, expand=YES, fill=BOTH)
        self.display.tag_config('ans', foreground='white')
        self.display.component('text').bind('<Key>', self.doKeypress)
        self.display.component('text').bind('<Return>', self.doEnter)

        for row in keys:
            rowa = Frame(self, bg='gray40')
            rowb = Frame(self, bg='gray40')
            for p1, p2, p3, color, ktype, func in row:
                if ktype == FUN:
                    a = lambda s=self, a=func: s.evalAction(a)
                else:
                    a = lambda s=self, k=func: s.keyAction(k)
                SLabel(rowa, p2, p3)
                Key(rowb, text=p1, bg=color, command=a)
            rowa.pack(side=TOP, expand=YES, fill=BOTH)
            rowb.pack(side=TOP, expand=YES, fill=BOTH)            

class Evaluator:
    def __init__(self):
        self.myNameSpace = {} 
        self.runpython("from math import *")

    def runpython(self, code):
        try:
            return repr(eval(code, self.myNameSpace, self.myNameSpace))
        except SyntaxError:
            try:
                exec(code, self.myNameSpace, self.myNameSpace)
            except:
                return 'Error'

Calculator().mainloop()
Overwriting applications/tt42_grayson.py
In [12]:
if runTkinter:
    !python3 applications/tt42_grayson.py

We'll study this in more detail at the end of the class.

Tkinter Widget Tour

Let's look at the Tkinter widgets. This isn't just an introduction to the widget, though: Take a look at how we use Python's built-in data structures, especially collections, to make compact yet readable code.

Here's a UML diagram of the Tkinter widgets:

Label Widgets

The name Label is a little misleading. Yes, Labels can hold text, even multiline, but they can also hold images. In general, they're for display, not interaction. Here's an example, again from Grayson.

In [13]:
%%file demos/d04_grayson_label.py
from tkinter import *

tk = Tk()
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass
tk.title('tkinter Label demo')

Label(tk, text="""
I mean, it's a little confusing for me when you say 'dog kennel' if \
you want a mattress.  Why not just say 'mattress'?
""",
      wraplength=300, justify=LEFT, background='yellow').pack(pady=10)

f1=Frame(tk)
Label(f1,
      text="It's not working, we need more!",
      relief=RAISED).pack(side=LEFT, padx=5)
Label(f1,
      text="I'm not coming out!",
      relief=SUNKEN).pack(side=LEFT, padx=5)
f1.pack()

f2=Frame(tk)
for bitmap, relief  in (
    ('xlogo64',     RAISED),
    ('mensetmanus', SOLID),
    ('terminal',    SUNKEN),
    ('escherknot',  FLAT),
    ('calculator',  GROOVE),
    ('letters',     RIDGE)):
    Label(f2,
          bitmap='@bitmaps/%s' % bitmap,
          relief=relief).pack(side=LEFT, padx=5)
f2.pack()

tk.mainloop()
Overwriting demos/d04_grayson_label.py
In [14]:
if runTkinter:
    !python3 demos/d*_grayson_label.py

Adding tkinter widgets to a GUI is 2-step process. First, create the widget. Then say where it belongs in its parent widget with (for now) grid(row, column). (Grayson prefers to use pack(), which is an alternative.)

Message Widgets

Message widgets are like Label widgets, except that they're designed to wrap text into multiple lines.

In [15]:
%%file demos/d05_grayson_message.py
from tkinter import *

root = Tk()
root.title('tkinter Message demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

Message(root,
        text="Exactly.  It's my belief that these sheep are laborin' "
        "under the misapprehension that they're birds.  Observe their "
        "be'avior. Take for a start the sheeps' tendency to 'op about "
        "the field on their 'ind legs.  Now witness their attmpts to "
        "fly from tree to tree.  Notice that they do not so much fly "
        "as...plummet.", bg='royalblue',
        fg='ivory', relief=GROOVE).grid(row=0, column=0)
root.mainloop()
Overwriting demos/d05_grayson_message.py
In [16]:
if runTkinter:
    !python3 demos/d*_grayson_message.py

Aside: The Tkinter Configuration File

tkinter allows a wide amount of user customizability. Users can set styles in an "options" file. Here's one we use for lectures in large lecture halls that increases the font size.

In [17]:
%%file lecture_options.txt
*Font:			Times 24 bold
*Entry.Font:    Courier 24 bold
Overwriting lecture_options.txt

Button Widgets

Button widgets are designed to be pressed. You can also find when they're released. They can be created with lots of attributes controlling appearance.

In [18]:
%%file demos/d06_grayson_button.py
from tkinter import *

root = Tk()
root.title('tkinter Button demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

Label(root, text="You shot him!").pack(pady=10)
Button(root, text="He's dead!", state=DISABLED).pack(side=LEFT)
Button(root, text="He's completely dead!", command=root.quit).pack(side=RIGHT)
root.mainloop()
Overwriting demos/d06_grayson_button.py
In [19]:
if runTkinter:
    !python3 demos/d*_grayson_button.py

Tkinter even allows you to change the layout while the program is running.

In [20]:
%%file demos/d07_grayson_dynamicButtons.py
from tkinter import *

root = Tk()
root.title('tkinter Button demo: dynamic buttons')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass


f0 = Frame(root)
f0.grid(row=0, column=0)

f1 = Frame(root)
f1.grid(row=0, column=1)

buttons = []
column = 0 # within f0

def newButton():
    global column
    button = Button(f0, text="Button {}".format(column), command=newButton)
    button.grid(row=0, column=column)
    column += 1
    buttons.append(button)

newButton()
newButton()
Button(f1, text="Quit", command=root.quit).grid(row=0, column=0)
root.mainloop()
Overwriting demos/d07_grayson_dynamicButtons.py
In [21]:
if runTkinter:
    !python3 demos/d*_grayson_dynamicButtons.py

Here's another Button demo from Grayson showing the effect of borderwidth and relief attributes. Note also the effect of padx and pady in the grid() call.

In [22]:
%%file demos/d08_grayson_button_borders.py
from tkinter import *

class GUI:
    def __init__(self):
        self.root = Tk()
        try:
            self.root.option_readfile('lecture_options.txt')
        except TclError:
            pass
        self.root.title('tkinter Button demo: button styles')

        for bdw in range(5):
            setattr(self, 'of%d' % bdw, Frame(self.root, borderwidth=0))
            Label(getattr(self, 'of%d' % bdw),
                  text='borderwidth = %d  ' % bdw).pack(side=LEFT)
            for relief in [RAISED, SUNKEN, FLAT, RIDGE, GROOVE, SOLID]:
                def callback(s=self, r=relief, b=bdw):
                    s.prt(r,b)
                Button(getattr(self, 'of%d' % bdw), text=relief,
                       borderwidth=bdw, relief=relief, width=10,
                       command=callback).pack(
                                  side=LEFT, padx=7-bdw, pady=7-bdw)
            getattr(self, 'of%d' % bdw).pack()

    def prt(self, relief, border):
        print('%s:%d' % (relief, border))

myGUI = GUI()
myGUI.root.mainloop()
Overwriting demos/d08_grayson_button_borders.py
In [23]:
if runTkinter:
    !python3 demos/d*_grayson_button_borders.py

Entry Widgets

Entry widgets are designed to let the user enter a single line of text.

In [24]:
%%file demos/d09_grayson_entry.py
from tkinter import *

root = Tk()
root.title('tkinter Entry demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

label = Label(root, text="occupation:")
label.grid(row=0, column=0, padx=5, pady=10)
stringVar = StringVar()
entry = Entry(root, width=40, textvariable=stringVar)
stringVar.set("What did you want to be?")
entry.grid(row=0, column=1, padx=5, pady=10)

# Note: Once mainloop exits, you can still retrieve the value of
# the textvariable ...
print('before the mainloop call, these two calls are equivalent:')
print('stringVar.get():', stringVar.get())
print('    entry.get():', entry.get())

root.mainloop()

print()
print('after the mainloop call, this still works:')
print('stringVar.get():', stringVar.get())
print('but this ...')
print('    entry.get():')
try:
    print(entry.get())
except:
    print('... raises an exception')
Overwriting demos/d09_grayson_entry.py
In [25]:
if runTkinter:
    !python3 demos/d*_grayson_entry.py

Note the StringVar. This is a Tkinter textvariable. These are objects associated with the values of Tkinter widgets. There are three kinds of these: StringVar, IntVar, and FloatVar. The values are accessed via get() and set() methods.

Radiobutton Widgets

To fully understand what is meant by "radiobuttons", you need to know how car radios from the '50s used to work. (Obscure knowledge sometimes pays off!)

Radiobuttons work a lot like Buttons. The main differences are

  • The value of the Radiobutton is associated with a textvariable (an IntVar or a StringVar).

  • Only one of all of the Radiobuttons that share a textvariable can be selected at one time.

Here's a short demo of Radiobuttons:

In [26]:
%%file demos/d10_grayson_radiobutton_stringVar.py
from tkinter import *

root = Tk()
root.title('tkinter Radiobutton demo: radiobuttons with StringVars')
try:
    if 0: root.option_readfile('lecture_options.txt')
except TclError:
    pass

entries = (
    'Passionfruit',
    'Loganberries',
    'Mangoes in syrup',
    'Oranges',
    'Apples',
    'Grapefruit')
stringVar = StringVar() # shared by all entries in the radiobutton
for row, text in enumerate(entries):
    radiobutton = Radiobutton(root,
                              text=text, value=text, variable=stringVar)
    if 0: # enable/disable this to see what the indicator does
        radiobutton['indicatoron'] = False
    if 0: # demonstrate setting widget attributes using dictionary syntax
        radiobutton['text'] = radiobutton['text'] + '?'
    radiobutton.grid(row=row, column=0, padx=5, pady=5, sticky=W)
stringVar.set('Apples') # initial setting
root.mainloop()
Overwriting demos/d10_grayson_radiobutton_stringVar.py
In [27]:
if runTkinter:
    !python3 demos/d*_grayson_radiobutton_stringVar.py

In this case, Radiobutton values are strings.

They can also be ints:

In [28]:
%%file demos/d11_grayson_radiobutton_intVar.py
from tkinter import *

root = Tk()
root.title('tkinter Radiobutton demo: radiobuttons with IntVars')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

entries = (
    ('Passionfruit',     1),
    ('Loganberries',     2),
    ('Mangoes in syrup', 3),
    ('Oranges',          4),
    ('Apples',           5),
    ('Grapefruit',       6))
intVar = IntVar() # shared by all entries in the radiobutton
for row, (text, value) in enumerate(entries):
    radiobutton = Radiobutton(root,
                              text=text, value=value, variable=intVar)
    if 0: # enable/disable this to see what the indicator does
        radiobutton['indicatoron'] = False
    radiobutton.grid(row=row, column=0, padx=5, pady=5, sticky=W)
intVar.set(3) # initial setting
root.mainloop()
Overwriting demos/d11_grayson_radiobutton_intVar.py
In [29]:
if runTkinter:
    !python3 demos/d*_grayson_radiobutton_intVar.py

Checkbutton Widgets

Checkbuttons are like Radiobuttons, except that each has its own textvariable and you can select 0 or more of them. Here's a demo:

In [30]:
%%file demos/d12_grayson_checkbutton.py
from tkinter import *

root = Tk()
root.title('tkinter Checkbutton demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

varOfCastmember = {}
for castmember, row, col, status in (
        ('John Cleese',    0, 0, NORMAL),
        ('Eric Idle',      0, 1, NORMAL),
        ('Graham Chapman', 1, 0, DISABLED),
        ('Terry Jones',    1, 1, NORMAL),
        ('Michael Palin',  2, 0, NORMAL),
        ('Terry Gilliam',  2, 1, NORMAL)):
    castmemberVar = IntVar()
    varOfCastmember[castmember] = castmemberVar
    checkbutton = Checkbutton(root,
                              text=castmember, state=status, anchor=W,
                              variable=castmemberVar)
    checkbutton.grid(row=row, column=col, sticky=W)

root.mainloop()
for castmember in varOfCastmember:
    var = varOfCastmember[castmember]
    print("{:>20}: {}".format(castmember, var.get()))
Overwriting demos/d12_grayson_checkbutton.py
In [31]:
if runTkinter:
    !python3 demos/d*_grayson_checkbutton.py

Menus should be pretty familiar. Here's a demo with several examples:

In [32]:
%%file demos/d13_grayson_menu.py
from tkinter import * # This is more convenient.


def new_file():
    print("Create new file")


def open_file():
    print("Open existing file")


def stub_action():
    print("Menu select")


def makeCascadeMenubutton():
    menubutton = Menubutton(menubarFrame, text='Cascading Menus', underline=0)
    menubutton.menu = Menu(menubutton)
    menubutton.menu.choices = Menu(menubutton.menu)

    # create and populate the "weirdOnes" submenu
    menubutton.menu.choices.weirdOnes = Menu(menubutton.menu.choices)
    menubutton.menu.choices.weirdOnes.add_command(label='Stockbroker')
    menubutton.menu.choices.weirdOnes.add_command(label='Quantity Surveyor')
    menubutton.menu.choices.weirdOnes.add_command(label='Church Warden')
    menubutton.menu.choices.weirdOnes.add_command(label='BRM')    

    menubutton.menu.choices.add_command(label='Wooden Leg')
    menubutton.menu.choices.add_command(label='Hire Purchase')
    menubutton.menu.choices.add_command(label='Dead Crab')
    menubutton.menu.choices.add_command(label='Tree Surgeon')
    menubutton.menu.choices.add_command(label='Filing Cabinet')
    menubutton.menu.choices.add_command(label='Goldfish')
    # note the "add_cascade()" rather than the "add_command()"
    menubutton.menu.choices.add_cascade(label='Is it a...', 
            menu=menubutton.menu.choices.weirdOnes)

    menubutton.menu.add_cascade(label='Scripts',
                                   menu=menubutton.menu.choices)
    menubutton['menu'] = menubutton.menu
    return menubutton


def makeCheckbuttonMenubutton():
    menubutton = Menubutton(menubarFrame, text='Checkbutton Menus', underline=0)
    menubutton.menu = Menu(menubutton)

    menubutton.menu.add_checkbutton(label='Doug')
    menubutton.menu.add_checkbutton(label='Dinsdale')
    menubutton.menu.add_checkbutton(label="Stig O'Tracy")
    menubutton.menu.add_checkbutton(label='Vince')
    menubutton.menu.add_checkbutton(label='Gloria Pules')    
    menubutton.menu.invoke(menubutton.menu.index('Dinsdale'))

    menubutton['menu'] = menubutton.menu
    return menubutton


def makeCommandMenubutton():
    menubutton = Menubutton(menubarFrame, text='Button Commands', underline=0)
    menubutton.menu = Menu(menubutton)

    menubutton.menu.add_command(label="Undo")
    menubutton.menu.entryconfig(0, state=DISABLED)

    menubutton.menu.add_command(label='New...', underline=0, command=new_file)
    menubutton.menu.add_command(label='Open...', underline=0, command=open_file)
    menubutton.menu.add_command(label='Wild Font', underline=0,
            font=('Zapfino', 14), command=stub_action)
    menubutton.menu.add_command(bitmap="@bitmaps/RotateLeft")
    menubutton.menu.add('separator')
    menubutton.menu.add_command(label='Quit', underline=0, 
            background='red', activebackground='green', 
            command=menubutton.quit)

    menubutton['menu'] = menubutton.menu
    return menubutton


def makeRadiobuttonMenubutton():
    menubutton = Menubutton(menubarFrame, text='Radiobutton Menus', underline=0)
    menubutton.menu = Menu(menubutton)

    menubutton.menu.add_radiobutton(label='metonymy')
    menubutton.menu.add_radiobutton(label='zeugmatists')
    menubutton.menu.add_radiobutton(label='synechdotists')
    menubutton.menu.add_radiobutton(label='axiomists')
    menubutton.menu.add_radiobutton(label='anagogists')
    menubutton.menu.add_radiobutton(label='catachresis')
    menubutton.menu.add_radiobutton(label='periphrastic')
    menubutton.menu.add_radiobutton(label='litotes')
    menubutton.menu.add_radiobutton(label='circumlocutors')

    menubutton['menu'] = menubutton.menu
    return menubutton


def makeDisabledMenubutton(): 
    menubutton = Menubutton(menubarFrame, text='Disabled Menu', underline=0)
    menubutton["state"] = DISABLED
    return menubutton

root = Tk()
root.title('tkinter Menu demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

menubarFrame = Frame(root, relief=RAISED, borderwidth=2)
menubarFrame.grid(row=0, column=0)

commandMenubutton = makeCommandMenubutton()
commandMenubutton.grid(row=0, column=0)
cascadeMenubutton = makeCascadeMenubutton()
cascadeMenubutton.grid(row=0, column=1)
checkbuttonMenubutton = makeCheckbuttonMenubutton()
checkbuttonMenubutton.grid(row=0, column=2)
radiobuttonMenubutton = makeRadiobuttonMenubutton()
radiobuttonMenubutton.grid(row=0, column=3)
disabledMenubutton = makeDisabledMenubutton()
disabledMenubutton.grid(row=0, column=4)

root.mainloop()
Overwriting demos/d13_grayson_menu.py
In [33]:
if runTkinter:
    !python3 demos/d*_grayson_menu.py

Text Widgets

Text widgets allow for multi-line text output and allow you to define and attach tags to the text as you insert it.

In [34]:
%%file demos/d14_grayson_text.py
from tkinter import *
from random import randint

root = Tk()
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass
root.title('tkinter Text Demo')

tagNamesAndAttrs = (
    # ( description, {
    #     [ attributeKeyword: attributeValue, ]*
    # } ),
    ( "normal text (no special tag attributes)", {
    } ),
    ( "12-point bold italic Verdana", {
        'font': ('Verdana', 12, 'bold', 'italic')
    } ),
    ( "24-point bold Verdana", {
        'font': ('Verdana', 24, 'bold')
    } ),
    ( "16-point Tempus Sans ITC in red", {
        'foreground':  'red',
        'font':        ('Tempus Sans ITC', 16),
    } ),
    ( "14-point Courier in green", {
        'foreground':  'dark green',
        'font':        ('Courier', 14),
    } ),
    ( "18-point Arial in blue", {
        'foreground':  'blue',
        'font':        ('Arial', 18),
    } ),
    ( "grooved with a border width of 4", {
        'relief':      GROOVE,
        'borderwidth': 4,
    } ),
    ( "grooved with a border width of 8 and a dark red background",  {
        'relief':      GROOVE,
        'background':  '#cc0000',
        'borderwidth': 8,
    } ),
)
nTags = len(tagNamesAndAttrs)

text = Text(root, height=nTags) # height is in lines (for Text) of "normal" text, I think
for i, (description, attrOfKwd) in enumerate(tagNamesAndAttrs):
    tagName = "_tag" + str(i)
    text.tag_configure(tagName, **attrOfKwd)
    text.insert(END, description + '\n', tagName)

if 0: # demonstrating another possible text.insert "index" syntax 
    text.insert("3.4", "This is on line 3, column 4")
    
text.grid(row=0, column=0)
root.mainloop()
Overwriting demos/d14_grayson_text.py
In [35]:
if runTkinter:
    !python3 demos/d*_grayson_text.py

We chose a few fonts to demo above, but there are actually quite a few. This program creates a Text that contains all it can find. The output scrolls.

In [36]:
%%file demos/d15_listFonts.py
from math import ceil
from tkinter import *
from tkinter.font import families as fontFamilies
from tkinter import messagebox

tk = Tk()

# read all font families into an unique, sorted list
families = list(set(fontFamilies()))
families.sort()

# Find out the maximum number of rows required.
nColumns = 10
nFamilies = len(families)
maxRowCount = ceil(nFamilies / nColumns)

tk.title(f"all {nFamilies} available font families on this system")

# Empirically determined, this allows all fonts to fit into
# buttons on one window. (The window may not fit on all displays.)
fontSize = 8

sampleText = "".join(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit,"
    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris"
    " nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in"
    " reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla"
    " pariatur."
    " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui"
    " officia deserunt mollit anim id est laborum.")

def showMessagebox(family):
    dialogBox = Toplevel()
    dialogBox.title(f'font: {family!r}')
    message = Message(dialogBox, text=sampleText, font=(family, 12))
    message.grid(row=0, column=0)
    button = Button(dialogBox, text="OK", command=dialogBox.destroy)
    button.grid(row=1, column=0)

for (k, family) in enumerate(families):
    
    row = k % maxRowCount
    column = k // maxRowCount

    button = Button(tk, text=family, anchor='w', padx=1, pady=1,
                    font=('Times', fontSize),
                    command=lambda family=family: showMessagebox(family))
    button.grid(row=row, column=column, sticky='ew')

tk.mainloop()
Overwriting demos/d15_listFonts.py
In [37]:
if runTkinter:
    !python3 demos/d*_listFonts.py

Scrollbar Widgets

You can connect a Scrollbar to another widget. Here's one that controls a Text widget vertically.

In [38]:
%%file demos/d16_grayson_scrollbar.py
from tkinter import *

kantSong = """
Immanuel Kant was a real pissant
    who was very rarely stable,
Heidegger, Heidegger was a boozy beggar
    who could think you under the table,
David Hume could out-consume
    Schopenhauer and Hegel,
And Wittgenstein was a beery swine who was just as schloshed as Schlegel.

There's nothing Nietzsche couldn't teach ya 'bout the turning of the wrist,
Socrates himself was permanently pissed...

John Stuart Mill, of his own free will,
    with half a pint of shandy was particularly ill,
Plato, they say, could stick it away,
    half a crate of whiskey every day,
Aristotle, Aristotle was a beggar for the bottle,
Hobbes was fond of his dram,
And Rene Descartes was a drunken fart,
    "I drink therefore I am."

Yes, Socrates himself is particularly missed;
A lovely little thinker but a bugger when he's pissed.
"""[1:-1]

root = Tk()
root.title('tkinter Text demo: Text with a Scrollbar')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

label = Label(text="Monty Python's Philosophers' Song")
label.grid(row=0, column=0)

maxWidth = max([len(str) for str in kantSong.split('\n')])
text = Text(root, height=10, width=maxWidth)
text.insert(END, kantSong)
text.grid(row=1, column=0)

# Link scrolling the scrollbar in y link to the text
scrollbar = Scrollbar(root, command=text.yview)
text.configure(yscrollcommand=scrollbar.set)
scrollbar.grid(row=1, column=1, sticky=N+S)

root.mainloop()
Overwriting demos/d16_grayson_scrollbar.py
In [39]:
if runTkinter:
    !python3 demos/d*_grayson_scrollbar.py

Here's an alternative way to show the above font information that also uses a Scrollbar:

In [40]:
%%file demos/d17_grayson_showFonts.py
from tkinter import *

root = Tk()
root.option_readfile('lecture_options.txt')
root.title("Lots O' Fonts")
text = Text(root, height=26, width=75)

scroll = Scrollbar(root, command=text.yview)
text.configure(yscrollcommand=scroll.set)

from tkinter.font import families

fmlys = list(families())
fmlys.sort()

text.tag_configure('default', font=('Courier', 12, 'bold'))

for fmly in fmlys:
    text.tag_configure(fmly, font=(fmly, 12))

maxl = max([ len(fmly) for fmly in fmlys ])
for fmly in fmlys:
    text.insert(END, '%*s: ' % (maxl,fmly), 'default')
    text.insert(END, fmly + '\n', fmly)
text.grid(row=0, column=0)
scroll.grid(row=0, column=1, sticky=N+S)

root.mainloop()
Overwriting demos/d17_grayson_showFonts.py
In [41]:
if runTkinter:
    !python3 demos/d*_grayson_showFonts.py

Listbox Widgets

Listboxes display multiple text items, one per line, and allow you to choose one or more of them. If you have a lot of items, you can link a Scrollbar to them.

In [42]:
%%file demos/d18_grayson_listbox.py
from tkinter import *

def demoSelectMode(selectMode, description):
    tk = Tk()
    tk.title(f'tkinter Listbox demo: {selectMode!r} mode')
    try:
        tk.option_readfile('lecture_options.txt')
    except TclError:
        pass

    label = Label(tk, text=f'{selectMode!r}: {description}')
    label.grid(row=0, column=0)

    list_ = Listbox(tk, selectmode=selectMode)
    for item in range(10):
        list_.insert(END, item)
    list_.grid(row=1, column=0)

    tk.mainloop()
    
demoSelectMode(SINGLE,   f"a single choice")
demoSelectMode(BROWSE,   f"like {SINGLE!r}, but mouse-drag-selectable")
demoSelectMode(MULTIPLE, f"multiple items selectable at one time")
demoSelectMode(EXTENDED, f"usually {SINGLE!r}, but ranges possible")
Overwriting demos/d18_grayson_listbox.py
In [43]:
if runTkinter:
    !python3 demos/d*_grayson_listbox.py

Scale Widgets

Scales (or "sliders") allow you to select an int or float value from a range. Click the left mouse button in the "trough" to give fine control.

In [44]:
%%file demos/d19_horizontalScale.py
from tkinter import *

def printValue(valueStr):
    # The '\r' and the 'end=""' allow us to overwrite previous lines
    # when run interactively in a terminal (emulator).
    print(f'\rscale value: {valueStr:>4s}', end="")

root = Tk()
root.title('tkinter Scale demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

scale = Scale(root, orient=HORIZONTAL, length=400, from_=0, to=100,
              tickinterval=20, command=printValue)
scale.grid(row=0, column=0, sticky='NE')
scale.set(50)
root.mainloop()
Overwriting demos/d19_horizontalScale.py
In [45]:
if runTkinter:
    !python3 demos/d*_horizontalScale.py
In [46]:
def f(parg, **kwargs):
    print(parg, **kwargs)
    
f(42, end="hello")
lst = [3, 4, 5]
(a, *_) = lst
42hello

Here's a more elaborate use of a Scale that uses it to control a Canvas (we haven't talked about those yet) drawing.

In [47]:
%%file demos/d20_grayson_verticalScaleWithArrow.py
from tkinter import *

def setHeight(heightStr):
    height = int(heightStr) + 21
    y2 = height - 30 # base of arrow tip
    if y2 < 21:
        y2 = 21
    coords = [
        15, 20, 
        35, 20, 
        35, y2, 
        45, y2, 
        25, height, 
         5, y2, 
        15, y2, 
        15, 20 ]
    canvas.coords('poly', *coords)
    canvas.coords('line', *coords)

root = Tk()
root.title('tkinter Scale demo: controlling arrow drawn on Canvas')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

canvas = Canvas(root, width=50, height=50, bd=0, highlightthickness=0)
canvas.create_polygon(0,0, 1,1, 2,2, fill='cadetblue', tags='poly')
canvas.create_line(0,0, 1,1, 2,2, 0,0, fill='black', tags='line')
scale = Scale(root, orient=VERTICAL, length=284, from_=0, to=250,
              tickinterval=50, command=setHeight)
scale.grid(row=0, column=0, sticky='NE')
canvas.grid(row=0, column=1, sticky='NWSE')
scale.set(100)
root.mainloop()
Overwriting demos/d20_grayson_verticalScaleWithArrow.py
In [48]:
if runTkinter:
    !python3 demos/d*_grayson_verticalScaleWithArrow.py

Spinbox Widgets

Spinboxes display a value like an Entry, but have adjacent arrows to increment and decrement it.

In [49]:
%%file demos/d21_spinbox.py
from tkinter import *

tk = Tk()
tk.title('tkinter Spinbox demo')
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass

w = Spinbox(tk, from_=0, to=10)
w.grid()

mainloop()
Overwriting demos/d21_spinbox.py
In [50]:
if runTkinter:
    !python3 demos/d*_spinbox.py

Optionmenu Widgets

These usually look like buttons, but a horizontal bar on the right indicates that they have a pull-down menu associated with them. Here's a simple example:

In [51]:
%%file demos/d22_basicOptionmenu.py
from tkinter import *

tk = Tk()
tk.title('tkinter OptionMenu demo: basic')
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass

variable = StringVar(tk)
variable.set("one") # default value

w = OptionMenu(tk, variable, "one", "two", "three")
w.grid(row=0, column=0)

mainloop()
Overwriting demos/d22_basicOptionmenu.py
In [52]:
if runTkinter:
    !python3 demos/d*_basicOptionmenu.py

Here's a more elaborate example showing how to access the menu contents with a string text variable.

In [53]:
%%file demos/d23_getOptionmenu.py
from tkinter import *

tk = Tk()
tk.title("tkinter OptionMenu demo: getting the menu value")
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass

stringVar = StringVar(tk)
stringVar.set("one") # initial value

option = OptionMenu(tk, stringVar, "one", "two", "three", "four")
option.grid(row=0, column=0)

def onOkPress():
    print(f"value is {stringVar.get()!r}")
    tk.quit()

button = Button(tk, text="OK", command=onOkPress)
button.grid(row=0, column=1)

mainloop()
Overwriting demos/d23_getOptionmenu.py
In [54]:
if runTkinter:
    !python3 demos/d*_getOptionmenu.py

Canvas Widgets

Canvases are for drawing 2D figures. Being a graphicist, these are my favorite tkinter widgets.

Default units for Canvases are usually pixels. (Font sizes are in points.) You can use other units by putting them in quotes with suffixes to indicate the units, e.g.:

  • "4in" (4 inches)

  • "3.2cm" (3.2 centimeters)

  • "8mm" (8 millimeters)

Notice how Python duck typing allows you to pass either strings or ints to the same tkinter functions or methods to set the sizes of things.

The coordinate system for Canvases puts the origin (0,0) in the upper left (like the rest of tkinter) with x increasing to the right and y increasing down. What you draw is clipped to the visible area of the Canvas.

Drawing Rectangles in Canvases

The create_rectangle(xUL, yUL, xLR, yLR, ...) method adds an axis-aligned rectangle with an upper left corner at (xUL, yUL) and a lower right corner at (xLR, yLR)to a Canvas.

In [55]:
%%file demos/d24_basicCanvas.py
from tkinter import *

tk = Tk()
tk.title('tkinter Canvas demo: basic')
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass

canvas = Canvas(tk, width=400, height=400)
#         (( xUL,   yUL),  (xLR,  yLR)) 
corners = (('10mm','2in'), (200, '4in'))
canvas.create_rectangle(corners, fill="Green")
canvas.grid()
tk.mainloop()
Overwriting demos/d24_basicCanvas.py
In [56]:
if runTkinter:
    !python3 demos/d*_basicCanvas.py
All 2D Graphics Primitives in Canvases

The following methods add 2D graphics primitives to a Canvas:

  • create_arc(xUL, yUL, xLR, yLR, ...) draws a slice out of an ellipse (or a circle).

  • create_bitmap(x, y, ...) inserts a bitmap image. (x, y) is the anchor point.

  • create_image(x, y, ...) inserts a graphic image. (x, y) is the anchor point.

  • create_line(x0, y0, x1, y1, ...) draws one or more line segments.

  • create_oval(xUL, yUL, xLR, yLR, ...) draws an ellipse; use this also for drawing circles, which are a special case of an ellipse. (Mathematical trivia: All ellipses are ovals, but not all ovals are ellipses.)

  • create_polygon(x0, y0, x1, y1, ...) draws a polygon.

  • create_rectangle(xUL, yUL, xLR, yLR, ...) draws a rectangle.

  • create_text(x, y, ...) adds text annotation. (x, y) is the anchor point.

  • create_window(x, y, ...) inserts a rectangular window that can contain any one widget (including a Frame). (x, y) is the anchor point.

Note that none of these entities creates an object or widget in the Python sense. (I find that slightly annoying, but I suppose there are reasons.) They return an id (not to be confused with a result of the Python id() builtin) that can be used to delete or modify the entity later.

We'll show an application that uses many of these primitives below.

Putting Images in Canvases

You can put images in Canvases with create_image().

Aside: If you really want to manipulate images, don't use Tkinter images. OpenCV (the cv2 package) is designed for image manipulation and its images work much better with numpy (later).

If necessary, we need to copy an image for use below.

In [57]:
if runTkinter:
    ![ -d demos/images ] || mkdir demos/images
    !cp images/nitobe.png demos/images
In [58]:
%%file demos/d25_nitobeCanvas.py
from tkinter import *

tk = Tk()
tk.title('tkinter Canvas demo: an image viewer')
img = PhotoImage(file='demos/images/nitobe.png')

cnvs = Canvas(tk, width=img.width(), height=img.height())
cnvs.create_image(0,0, image=img, anchor=NW)
cnvs.grid(row=0, column=0)

button = Button(tk, text='Quit', command=tk.quit)
button.grid(row=1, column=0, sticky=E)

tk.mainloop()
Overwriting demos/d25_nitobeCanvas.py
In [59]:
if runTkinter:
    !python3 demos/d*_nitobeCanvas.py

PhotoImage() understands most popular image file formats.

But I should point out a small annoyance with images. Let's try a program that should work identically to the above:

In [60]:
%%file demos/d26_buggyNitobeCanvas.py
from tkinter import *

root = Tk()
root.title('tkinter Canvas demo: a buggy image viewer')

def func():
    img = PhotoImage(file='demos/images/nitobe.png')

    cnvs = Canvas(root, width=img.width(), height=img.height())
    cnvs.create_image(0,0, image=img, anchor=NW)

    cnvs.grid(row=0, column=0)
    Button(root, text='Quit', command=root.quit).grid(row=1,column=0)

func()
root.mainloop()
Overwriting demos/d26_buggyNitobeCanvas.py
In [61]:
if runTkinter:
    !python3 demos/d*_buggyNitobeCanvas.py

What happened to the image? All we did was put some of the code in a function. What could be wrong with that? Here's a hint: Add one line to func()...

In [62]:
%%file demos/d27_goodNitobeCanvas.py
from tkinter import *

root = Tk()
root.title('tkinter Canvas demo: a repaired image viewer')

def func():
    # Note the use of the "_" prefix to hint to the user
    # "don't mess with this unless you know what you're doing".
    global _img # prevents _img from garbage collection
    
    _img = PhotoImage(file='demos/images/nitobe.png')
    cnvs = Canvas(root, width=_img.width(), height=_img.height())
    cnvs.create_image(0,0, image=_img, anchor=NW)
    cnvs.grid(row=0, column=0)
    Button(root, text='Quit', command=root.quit).grid(row=1,column=0)

func()
root.mainloop()
Overwriting demos/d27_goodNitobeCanvas.py
In [63]:
if runTkinter:
    !python3 demos/d*_goodNitobeCanvas.py

... and everything works fine again. This works, too:

In [64]:
%%file demos/d28_alsoGoodNitobeCanvas.py
from tkinter import *

root = Tk()
root.title('tkinter Canvas demo: an alternatively-repaired image viewer')

# make img global (but somewhat hidden), so it's not garbage-collected
_img = PhotoImage(file='demos/images/nitobe.png')

def func():
    cnvs = Canvas(root, width=_img.width(), height=_img.height())
    cnvs.create_image(0,0, image=_img, anchor=NW)
    cnvs.grid(row=0, column=0)
    button = Button(root, text='Quit', command=root.quit)
    button.grid(row=1, column=0)

func()
root.mainloop()
Overwriting demos/d28_alsoGoodNitobeCanvas.py
In [65]:
if runTkinter:
    !python3 demos/d*_alsoGoodNitobeCanvas.py

LabelFrame Widgets

LabelFrames are Frames that provide a label and a border to group related widgets together more visibly to the user. These are particularly useful when have a large number of widgets.

In [66]:
%%file demos/d29_basicLabelFrame.py
from tkinter import *

root = Tk()
root.title('tkinter LabelFrame demo')
try:
    root.option_readfile('lecture_options.txt')
except TclError:
    pass

def addAttributes(frame, attrNames):
    for (row, attrName) in enumerate(attrNames):
        label = Label(frame, text=attrName + ":")
        label.grid(row=row, column=0, sticky=E)
        entry = Entry(frame, width=4)
        entry.grid(row=row, column=1, sticky=W)

heroFrame = LabelFrame(root, text="Hero Attributes", padx=5, pady=5)
heroFrame.grid(row=0, column=0)

heroAttrNames = ("strength", "wisdom", "intelligence", "dexterity")
addAttributes(heroFrame, heroAttrNames)

sidekickFrame = LabelFrame(root, text="Sidekick Attributes", padx=5, pady=5)
sidekickFrame.grid(row=1, column=0)

sidekickAttrNames = ("wimpiness", "foolishness", "ignorance", "clumsiness")
addAttributes(sidekickFrame, sidekickAttrNames)

root.mainloop()
Overwriting demos/d29_basicLabelFrame.py
In [67]:
if runTkinter:
    !python3 demos/d*_basicLabelFrame.py

Events in tkinter.

Like most GUI frameworks, tkinter is "event-based". Once you call the mainloop() method, tkinter puts you in an event loop where it waits for one or more events to happen. Events can be mouse clicks, key presses, timer timeouts, mouse motion, etc. When one does, it calls an event handler (i.e. a callback). When the event handler has done whatever it needs to do (assuming it doesn't exit the program), a return from it resumes the event loop, which then waits for the next event.

We're already using some built-in event handlers: Users can type stuff into text widgets (e.g. Text, Entry) and many widgets have a "command=" callback, but we can get a lot more different kinds of control by binding events to objects.

Here's a program that prints out some information about events associated with Buttons and Frames.

In [68]:
%%file demos/d30_handleButtonFrameEvents.py
from tkinter import *

tk = Tk()
tk.title('tkinter Event demo: handle Button and Frame Events')
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass

# Watch for these events in the Frame and the Button. (These
# are taken from the New Mexico Tech Tkinter documentation.)
eventTypeNames = (
    "ButtonPress", # "Button" is equivalent
    "ButtonRelease",
    "Enter",
    "KeyPress",
    "KeyRelease",
    "Leave",
    "Motion",
)

# print a header
print("{:15s} {:10s} {:>4s} {:>4s}".format(
      "event type", "widget", "x", "y"))

def handler(event):
    eventTypeName = str(event.type)
    widgetClassName = event.widget.__class__.__name__
    print("\r{:15s} {:10s} {:>4d} {:>4d}".format(
            eventTypeName, widgetClassName, event.x, event.y),
            end="")
    
def bindHandler(widget):
    for eventTypeName in eventTypeNames:
        eventSpec = f'<Any-{eventTypeName}>'
        widget.bind(eventSpec, handler)

frame = Frame(tk, width=150, height=150)
bindHandler(frame)
frame.grid()

btn = Button(frame, text='Press and Release Me')
bindHandler(btn)
btn.grid(padx=20, pady=20)

tk.mainloop()
Overwriting demos/d30_handleButtonFrameEvents.py
In [85]:
if runTkinter:
    !python3 demos/d*_handleButtonFrameEvents.py
event type      widget        x    y
Leave           Frame       346   -1

Events can also be triggered by changes to the window like resizes or deletion.

In [87]:
%%file demos/d31_handleConfigureEvents.py
from tkinter import *


# Watch for these events in the window. (These
# are taken from the New Mexico Tech Tkinter documentation.)
eventTypeNames = (
    "Configure",
    "Destroy",
    "Enter",
    "Expose",
    "FocusIn",
    "FocusOut",
    "Leave",
    "Visibility",
)

# print a header
print("{:15s} {:15s} {:>8s} {:>8s} {:>8s} {:>8s}".format(
      "event type", "widget class", "width", "height", "rootx", "rooty"))

def handler(event):
    eventTypeName = str(event.type)
    widget = event.widget
    widgetClassName = widget.__class__.__name__
    widgetWidth = widget.winfo_width()
    widgetHeight = widget.winfo_height()
    widgetRootX = widget.winfo_rootx()
    widgetRootY = widget.winfo_rooty()
    print("\r{:15s} {:15s} {:>8d} {:>8d} {:>8d} {:>8d}".format(
            eventTypeName, widgetClassName, widgetWidth, widgetHeight,
            widgetRootX, widgetRootY),
            end="")

def bindHandler(widget):
    for eventTypeName in eventTypeNames:
        eventSpec = f'<{eventTypeName}>'
        widget.bind(eventSpec, handler)
    
tk = Tk()
tk.title('tkinter Event demo: handle Configure Events')
try:
    tk.option_readfile('lecture_options.txt')
except TclError:
    pass
bindHandler(tk)
label = Label(tk, text='A Window (i.e., a Tk)')
label.grid()

tk.mainloop()
Overwriting demos/d31_handleConfigureEvents.py
In [88]:
if runTkinter:
    !python3 demos/d31_handleConfigureEvents.py
event type      widget class       width   height    rootx    rooty
Destroy         Tk                   299       37     1997       55

There's an extensive syntax to specify events inherited from Tck/Tk:

http://www.tcl.tk/man/tcl8.5/TkCmd/bind.htm#M7

Some Substantial tkinter Applications

To put all of this together, let's look at some larger Tkinter applications.

hanoi: The "Towers of Hanoi" Visualization

The "Towers of Hanoi" is a classic puzzle. The idea is to move disks of decreasing size from the left of three towers to the right tower, one disk at a time, without ever putting a larger disk on top of a smaller one. Here is a text-based solution that illustrates the algorithm:

In [72]:
%%file demos/d32_hanoiText.py
from sys import argv

if len(argv) == 2:
    height = int(argv[1])
else:
    height = 4

towerLeft = list(range(height))
towerLeft.reverse()
towerMiddle = []
towerRight = []

def printTowers():
    print("%-20s" % str(towerLeft), \
          "%-20s" % str(towerMiddle), \
          "%-20s" % str(towerRight))

def hanoi(n, towerFrom, towerTo, towerTemp):
    if n == 0:
        return
    hanoi(n-1, towerFrom, towerTemp, towerTo)
    towerTo.append(towerFrom[-1])
    del towerFrom[-1]
    printTowers()
    hanoi(n-1, towerTemp, towerTo, towerFrom)

printTowers()
hanoi(len(towerLeft), towerLeft, towerRight, towerMiddle)
Overwriting demos/d32_hanoiText.py
In [89]:
!python3 demos/d*_hanoiText.py
[3, 2, 1, 0]         []                   []                  
[3, 2, 1]            [0]                  []                  
[3, 2]               [0]                  [1]                 
[3, 2]               []                   [1, 0]              
[3]                  [2]                  [1, 0]              
[3, 0]               [2]                  [1]                 
[3, 0]               [2, 1]               []                  
[3]                  [2, 1, 0]            []                  
[]                   [2, 1, 0]            [3]                 
[]                   [2, 1]               [3, 0]              
[1]                  [2]                  [3, 0]              
[1, 0]               [2]                  [3]                 
[1, 0]               []                   [3, 2]              
[1]                  [0]                  [3, 2]              
[]                   [0]                  [3, 2, 1]           
[]                   []                   [3, 2, 1, 0]        

Notice that we can pass an argument to the above to vary the number of disks.

In [92]:
if runTkinter:
    !python3 demos/d*_hanoiText.py 3
[2, 1, 0]            []                   []                  
[2, 1]               []                   [0]                 
[2]                  [1]                  [0]                 
[2]                  [1, 0]               []                  
[]                   [1, 0]               [2]                 
[0]                  [1]                  [2]                 
[0]                  []                   [2, 1]              
[]                   []                   [2, 1, 0]           

We can use tkinter to build a visualization for this algorithm:

In [93]:
%%file applications/hanoi.py
from tkinter import *
from sys import argv


class LastStepReached(Exception):
    pass  # exception to prematurely exit recursive algorithm


class HanoiCanvas(Canvas):

    def __init__(self, parent, nDisks, canvasWidth = 600):
        self.nDisks = nDisks
        self.diskHeight = 20
        self.diskMinWidth = 2 * self.diskHeight
        self.diskMaxWidth = (canvasWidth - 30) / 3
        self.canvasWidth = canvasWidth
        self.canvasHeight = (nDisks + 2) * self.diskHeight
        super().__init__(parent, width=self.canvasWidth, height=self.canvasHeight)
        self.running = 0
        self.restart()

    def drawTower(self, tower, xCtr):
        y = self.canvasHeight - 2.0 * self.diskHeight
        colors = [ 'red', 'orange', 'yellow', 'green', 'blue', 'violet' ]
        for disk in tower:
            wDisc = self.diskMinWidth \
                    + (self.diskMaxWidth - self.diskMinWidth) * (disk + 1) \
                      / self.nDisks
            self.create_oval(xCtr - wDisc / 2, y+2,
                                  xCtr + wDisc / 2, y+self.diskHeight,
                                  tag='tower',
                                  fill=colors[disk % len(colors)],
                                  outline='black')
            y = y - self.diskHeight

    @property
    def interval(self):
        return int(1001.0 / speedIntVar.get())

    def hanoi(self, n, stackFrom, stackTo, stackTemp):
        if n == 0:
            return
        # move n-1 disks from stackFrom to stackTemp
        self.hanoi(n-1, stackFrom, stackTemp, stackTo)
        # transfer the top disk on stackFrom to the top of stackTo
        stackTo.append(stackFrom[-1])
        del stackFrom[-1]
        self.curStep = self.curStep + 1
        if self.curStep == self.lastStep:
            raise LastStepReached
        # move n-1 disks from stackTemp to stackTo
        self.hanoi(n-1, stackTemp, stackTo, stackFrom)
        
    def reset(self):
        self.left = list(range(self.nDisks))
        self.left = list(reversed(self.left))
        self.middle = []
        self.right = []

    def redraw(self):
        self.delete('tower')
        y = self.canvasHeight - self.diskHeight
        # this creates the base
        self.create_rectangle(10,
                                   y,
                                   self.canvasWidth - 10,
                                   y + 0.5 * self.diskHeight,
                                   fill='brown',
                                   outline='black',
                                   tag='tower')
        # this creates the towers
        for (twr, xCtr) in ((self.left, self.canvasWidth / 6),
                            (self.middle, self.canvasWidth / 2),
                            (self.right, 5 * self.canvasWidth / 6)):
            self.create_rectangle(
                    xCtr-5,
                    y,
                    xCtr+5,
                    y - (self.nDisks + 0.5) * self.diskHeight,
                    fill='brown',
                    outline='black',
                    tag='tower')
            self.drawTower(twr, xCtr)
        self.update()

    def restart(self):
        self.lastStep = 0
        self.curStep = 0
        self.reset()
        self.redraw()

    def setSpeed(self, r):
        pass

    def startRun(self):
        tk.after(self.interval, self.stepRun)
        self.running = 1

    def stopRun(self):
        self.running = 0

    def step(self):
        self.reset()
        self.lastStep = self.lastStep + 1
        self.curStep = 0
        try:
            self.hanoi(len(self.left), self.left, self.right, self.middle)
        except LastStepReached:
            self.redraw()

    def stepRun(self):
        self.step()
        if self.running and (self.left or self.middle): # reschedule itself
            tk.after(self.interval, self.stepRun)

if __name__ == '__main__':
    tk = Tk()
    tk.title('The Towers of Hanoi')

    if len(argv) == 2:
        nDisks = int(argv[1])
    else:
        nDisks = 3

    hanoiCanvas = HanoiCanvas(tk, nDisks)
    buttonWidth = 20
    runButton = Button(tk, text='Run', width=buttonWidth, command=hanoiCanvas.startRun)
    stopButton = Button(tk, text='Stop', width=buttonWidth, command=hanoiCanvas.stopRun)
    stepButton = Button(tk, text='Step', width=buttonWidth, command=hanoiCanvas.step)
    restartButton = Button(tk, text='Restart', width=buttonWidth, command=hanoiCanvas.restart)
    speedLabel = Label(tk, text="Speed (Hz): ")

    speedIntVar = IntVar()
    speedIntVar.set(1) # default update interval is 1 sec (1000 msec)

    # tk.after() uses milliseconds, so the maximum speed is 1000 Hz
    scale = Scale(tk, orient=HORIZONTAL, length=300,
                  from_=1, to=1000, variable=speedIntVar)

    quitButton = Button(tk, text='Quit', width=buttonWidth, command=tk.quit)

    row = 1
    column = 0
    runButton.grid(row=row, column=column, sticky='ew')
    column += 1
    stopButton.grid(row=row, column=column, sticky='ew')
    column += 1
    stepButton.grid(row=row, column=column, sticky='ew')
    column += 1
    restartButton.grid(row=row, column=column, sticky='ew')
    column += 1
    mxnColumns = column

    row = 0
    column = 0
    hanoiCanvas.grid(row=row, column=column, columnspan=mxnColumns)

    row = 2
    column = 0
    speedLabel.grid(row=row, column=column, sticky='e')
    column += 1
    scale.grid(row=row, column=column, columnspan=mxnColumns-2, sticky='w')
    column += mxnColumns-2
    quitButton.grid(row=row, column=column, sticky='ew')
    column += 1

    tk.mainloop()
Overwriting applications/hanoi.py
In [95]:
if runTkinter:
    !python3 applications/hanoi.py 15

lmprop: a static illustration of light propagation

Here's a program I created for a research project I was working on in light propagation.

In [96]:
%%file applications/lmprop.py
import PIL.Image as pilImage
import tkinter as tk
import numpy as np
import collections
from pprint import pprint

EPS = 1.0e-8 # (feeble) attempt to account for roundoff

Intersection = collections.namedtuple('Intersection', ('p', 't'))


def createOrientedSquare(centerPoint, wh, u):
    """create a wh x wh square centered on centerPoint w/one edge || to u
    """
    u /= mag(u)
    v = np.array((-u[1], u[0]))
    return (centerPoint - wh/2 * u - wh/2 * v,
            centerPoint + wh/2 * u - wh/2 * v,
            centerPoint + wh/2 * u + wh/2 * v,
            centerPoint - wh/2 * u + wh/2 * v)


def createRegularPolygon(centerPoint, radius, nSides, phaseDeg=0.0):
    dTheta = 2 * np.pi / nSides
    phase = phaseDeg * np.pi / 180
    polygon = []
    for i in range(nSides):
        theta = i * dTheta
        dx = radius * np.cos(theta - phase)
        dy = radius * np.sin(theta - phase)
        p = centerPoint + np.array((dx, -dy))
        polygon.append(p)
    return polygon


def lineThroughPoints(p0, p1):
    """returns the line that passes through points p0 and p1

    it returns (p0, n) where p0 is an arbitrary point on the line and n is
    a normal to the the line, which is defined by n . (p - p0) = 0 for any
    point p on the line.
    """
    dx = p1[0] - p0[0]
    dy = p1[1] - p0[1]
    # If dx & dy are positive, the positive half-plane is SE of the line.
    mag = (dx**2 + dy**2)**0.5
    n = (dy/mag, -dx/mag)
    return (p0, n)


def mag(v):
    return np.linalg.norm(v)


def normalize(v):
    return v / mag(v)


class Ray:

    def __init__(self, o, d):
        """the ray is of the form o+dt
        """
        self.o = o
        self.d = d

    def intersectsLine(self, p0, n):
        """returns the intersection of the ray with a line (or None)

        the line is (p-p0).n = 0
        """
        nDotD = np.dot(n, self.d)
        if np.abs(nDotD) < EPS:
            # If the ray begins on the line, its direction doesn't matter.
            if mag(self.o - p0) < EPS:
                return Intersection(self.o, 0.0)
            else:
                return None
        t = np.dot(n, p0 - self.o) / nDotD
        if t < 0: # t must be positive for the ray to intersect
            return None
        p = self.o + self.d * t
        return Intersection(p, t)

    def intersectsLineSegment(self, p0, p1):
        (pOnLine, n) = lineThroughPoints(p0, p1)
        intersection = self.intersectsLine(pOnLine, n)
        if intersection is None:
            return None

        # At this point, the ray intersects the line defined by the
        # points, but to intersect the line segment, the intersection must
        # lie between them. We'll do this by seeing if the vectors from
        # the endpoints to the intersection are antiparallel.
        v0 = intersection.p - p0
        v1 = intersection.p - p1
        result = intersection if np.dot(v0, v1) <= 0 else None
        return result

    def intersectsPolygon(self, polygon):
        """returns a list of intersections of the ray with a polygon (or [])
        """
        n = len(polygon)
        result = []
        for i in range(n):
            p0 = polygon[i]
            p1 = polygon[(i+1) % n]
            intersection = self.intersectsLineSegment(p0, p1)
            if intersection:
                result.append(intersection)
        def getT(intersection):
            return intersection.t
        return sorted(result, key=getT)


class Application(tk.Frame):

    def __init__(self, parent, nCellRows, nCellColumns, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.canvas = Canvas(self, nCellRows, nCellColumns)
        self.canvas.grid(row=0, column=0)


class Canvas(tk.Canvas):

    CELL_ARRAY_OFFSET = 30 # of cell array within canvas, allowing for labels

    def __init__(self, parent, nCellRows, nCellColumns):
        self.nCellRows = nCellRows
        self.nCellColumns= nCellColumns
        self.width = self.nCellColumns * Cell.WH + Canvas.CELL_ARRAY_OFFSET
        self.height = self.nCellRows * Cell.WH + Canvas.CELL_ARRAY_OFFSET
        super().__init__(parent, 
                         width=self.width, height=self.height,
                         background='white')

        self.corners = (
            (Cell.INSET, Cell.INSET),
            (Cell.INSET, self.height - Cell.INSET),
            (self.width - Cell.INSET, self.height - Cell.INSET),
            (self.width - Cell.INSET, Cell.INSET))

        self.cells = {}
        for i in range(self.nCellRows):
            for j in range(self.nCellColumns):
                self.cells[i,j] = Cell(self, i, j)
        for j in range(self.nCellColumns):
            self.drawText(((j + 0.5) * Cell.WH + Canvas.CELL_ARRAY_OFFSET,
                           0.5 * Canvas.CELL_ARRAY_OFFSET),
                          "{}".format(j), font=("Times", 16))
        for i in range(self.nCellColumns):
            self.drawText((0.5 * Canvas.CELL_ARRAY_OFFSET,
                          (i + 0.5) * Cell.WH + Canvas.CELL_ARRAY_OFFSET),
                          "{}".format(i), font=("Times", 16))
            
                    
    def drawArrow(self, p0, p1, **kwargs):
        d = 15 * normalize(p1 - p0)
        dPerp = np.array((d[1], -d[0]))
        polygon = ( p1,
                    p1 - d - dPerp/4,
                    p1 - 0.75 * d,
                    p1 - d + dPerp/4 )
        if 'fill' not in kwargs:
            kwargs['fill'] = 'black'
        self.drawPolygon(polygon, **kwargs)
        self.create_line(p0[0], p0[1], p1[0], p1[1], **kwargs)

    def drawCircle(self, pCtr, r, **kwargs):
        if 'fill' not in kwargs:
            kwargs['fill'] = 'light green' # default
        return self.create_oval((pCtr[0] - r, pCtr[1] - r),
                                (pCtr[0] + r, pCtr[1] + r), **kwargs)

    def drawImage(self, p, image, **kwargs):
        return self.create_image(p[0], p[1], image=image, **kwargs)

    def drawLine(self, p0, p1, **kwargs):
        return self.create_line(p0[0], p0[1], p1[0], p1[1], **kwargs)

    def drawPolygon(self, polygon, **kwargs):
        # convert arrays, if need be
        return self.create_polygon([ tuple(p) for p in polygon ],
                                   **kwargs)

    def drawRay(self, ray, **kwargs):
        edgePoint = None
        for i in range(4):
            p0 = self.corners[i]
            p1 = self.corners[(i+1) % 4]
            (p0, n) = lineThroughPoints(p0, p1)
            intersection = ray.intersectsLine(p0, n)
            if intersection is not None:
                if edgePoint is None \
                       or mag(intersection.p - ray.o) < mag(edgePoint - ray.o):
                    edgePoint = intersection.p
        if edgePoint is not None:
            self.drawArrow(ray.o, edgePoint)

    def drawRectangle(self, pUL, pLR, **kwargs):
        if 'fill' not in kwargs:
            kwargs['fill'] = 'light green' # default
        return self.create_rectangle(pUL[0], pUL[1], pLR[0], pLR[1], **kwargs)

    def drawRayTrace(self, eyePoint, viewDirection, fovDeg, polygon):
        d = mag(viewDirection)
        pViewCenter = eyePoint + viewDirection
        normalizedViewDirection = normalize(viewDirection)

        # perpendicular to the view direction
        normalizedViewDirectionPerp = normalize(np.array((viewDirection[1], 
                                                          -viewDirection[0])))

        fov = fovDeg * np.pi / 180
        halfW = d * np.tan(fov/2)

        p0 = pViewCenter + halfW * normalizedViewDirectionPerp
        p1 = pViewCenter - halfW * normalizedViewDirectionPerp
        viewPlane = Mesh(self, p0, p1, 7)

        nSamples = len(viewPlane.samplePoints)
        w = 2 * halfW
        pixelWH = w / (nSamples - 1)

        for samplePoint in viewPlane.samplePoints:
            pixelPolygon = createOrientedSquare(samplePoint, pixelWH,
                                                viewDirection)
            ray = Ray(eyePoint, samplePoint - eyePoint)
            intersections = ray.intersectsPolygon(polygon)
            if intersections:
                self.drawArrow(eyePoint, intersections[0].p)
                self.drawPolygon(pixelPolygon, fill='blue',
                                 outline='black', width=1)
            else:
                self.drawRay(ray)
                self.drawPolygon(pixelPolygon, fill='gray80',
                                 outline='black', width=1)

        # need to save image reference so it won't be garbage-collected
        self.image = tk.PhotoImage(file="applications/images/eye_rev.png")
        self.drawImage(eyePoint, self.image)

    def drawText(self, p, text, **kwargs):
        return self.create_text(p, text=text, **kwargs)

    def xyPosition(self, x, y):
        return np.array((x * Cell.WH + Canvas.CELL_ARRAY_OFFSET,
                         y * Cell.WH + Canvas.CELL_ARRAY_OFFSET), dtype=float)

    def xyDirection(self, dx, dy):
        return np.array((dx * Cell.WH, dy * Cell.WH), dtype=float)


class Cell:

    WH = 300 # width and height of (square) cells
    WALL_MESH_RESOLUTION = 9
    INSET = 1  # distinguish cell walls

    def __init__(self, canvas, i, j):
        self.pUL = np.array(
            (j       * Cell.WH + Cell.INSET + Canvas.CELL_ARRAY_OFFSET,
             i       * Cell.WH + Cell.INSET + Canvas.CELL_ARRAY_OFFSET))
        self.pLR = np.array((
            (j + 1) * Cell.WH - Cell.INSET + Canvas.CELL_ARRAY_OFFSET,
            (i + 1) * Cell.WH - Cell.INSET + Canvas.CELL_ARRAY_OFFSET))
        self.canvas = canvas
        self.walls = []
        for (x0, y0, x1, y1) in (
                (self.pUL[0], self.pUL[1], self.pUL[0], self.pLR[1]),
                (self.pUL[0], self.pLR[1], self.pLR[0], self.pLR[1]),
                (self.pLR[0], self.pLR[1], self.pLR[0], self.pUL[1]),
                (self.pLR[0], self.pUL[1], self.pUL[0], self.pUL[1])):
            p0 = np.array((x0, y0))
            p1 = np.array((x1, y1))
            wall = Mesh(self.canvas, p0, p1, Cell.WALL_MESH_RESOLUTION)
            self.walls.append(wall)

        pUL = np.array((self.pUL[0], self.pUL[1]))
        pLR = np.array((self.pLR[0], self.pLR[1]))
        self.backgroundId = self.canvas.drawRectangle(pUL, pLR)
        self.setHighlight(False)

    def drawBlocking(self, pLight, wallIndex, blockingPolygon, wallTo):
        intercedingWall = self.walls[wallIndex]
        for i in range(Cell.WALL_MESH_RESOLUTION):
            pTo = wallTo.samplePoints[i]
            o = pLight
            d = normalize(pTo - o)
            ray = Ray(o, d)

            wallIntersection = intercedingWall.intersectsRay(ray)
            if wallIntersection is not None:
                self.canvas.drawLine(pLight, wallIntersection.p)
                self.canvas.drawArrow(wallIntersection.p,
                                      wallIntersection.p + 20*d)
                blockingIntersections = ray.intersectsPolygon(blockingPolygon)
                if blockingIntersections:
                    self.canvas.drawArrow(wallIntersection.p, 
                                     blockingIntersections[0].p)
                    self.canvas.drawLine(blockingIntersections[1].p,
                                    pTo, fill='gray50')
                else:
                    self.canvas.drawLine(wallIntersection.p, pTo)
                    self.canvas.drawArrow(pTo, pTo + 20*d)
            else:
                self.canvas.drawLine(pLight, pTo, fill='gray50')

    def setHighlight(self, status):
        self.canvas.itemconfigure(self.backgroundId,
                                  fill= 'white' if status else 'gray80')


class Mesh:
    """This is a 1D mesh analogous to a more conventional 2D or 3D mesh.
    """

    def __init__(self, canvas, p0, p1, nSamples):
        self.canvas = canvas
        self.p0 = p0
        self.p1 = p1
        d = 1 / (nSamples - 1) # spacing between vertices
        self.samplePoints = []
        for i in range(nSamples):
            t = i * d
            p = self.p0 + t * (self.p1 - self.p0)
            self.samplePoints.append(p)
        self.canvas.drawLine(self.p0, self.p1)

    def line(self):
        """returns the line the wall intersects

        it returns (p0, n) where p0 is an arbitrary point on the line and n is
        a normal to the the line, which is defined by n . (p - p0) = 0 for any
        point p on the line.
        """
        return lineThroughPoints(self.p0, self.p1)

    def intersectsRay(self, ray):
        """returns true iff ray intersects the wall
        """
        return ray.intersectsLineSegment(self.p0, self.p1)


def drawFrame0(canvas):
    pLight = canvas.xyPosition(0.3, 0.4)

    illuminatedCell = canvas.cells[canvas.nCellRows-1, canvas.nCellColumns-1]
    illuminatedWall = illuminatedCell.walls[0]

    blockingCell = canvas.cells[0, 1]
    blockingCell.setHighlight(True)

    blockingPolygon = createRegularPolygon(canvas.xyPosition(1.2, 0.8), 15, 6)
    canvas.drawPolygon(blockingPolygon, fill='cyan', outline='black', width=1)

    blockingCell.drawBlocking(pLight, 0, blockingPolygon, illuminatedWall)

    canvas.drawCircle(pLight, 10, fill='yellow')

    targetPolygon = createRegularPolygon(canvas.xyPosition(2.4, 1.5), 80, 10,
                                         phaseDeg=10)
    canvas.drawPolygon(targetPolygon, fill='blue', outline='black', width=1)

    canvas.drawRayTrace(eyePoint = canvas.xyPosition(0.3, 1.25),
                        viewDirection = canvas.xyDirection(0.6, 0.03),
                        fovDeg = 30,
                        polygon = targetPolygon)


def onKey(event, application):
    widget = event.widget
    if event.char == 'p':
        application.canvas.postscript(file="lmprop.ps")
    elif event.char == 'q':
        widget.quit()
    elif event.char == '0':
        pass


def main():
    root = tk.Tk()
    application = Application(root,
                              nCellRows = 2,
                              nCellColumns = 3)
    application.grid(row=0, column=0)
    application.bind_all("<Key>",
              lambda event, application=application: onKey(event, application))

    drawFrame0(application.canvas)
    root.mainloop()


if __name__ == '__main__':
    main()
Overwriting applications/lmprop.py

Tkinter allows you to capture the canvas as a PostScript image "lmprop.ps" by pressing the "p" key. PostScript, however, is kind of passe, so we'll convert it to PNG so we can embed it in this notebook.

In [97]:
if runTkinter:
    !python3 applications/lmprop.py
    # "convert" comes from the (widely-used) ImageMagick package
    ![ -r lmprop.ps ] && convert lmprop.ps applications/images/lmprop.png
    !rm -f lmprop.ps # cleanup
convert-im6.q16: attempt to perform an operation not allowed by the security policy `PS' @ error/constitute.c/IsCoderAuthorized/408.
convert-im6.q16: no images defined `applications/images/lmprop.png' @ error/convert.c/ConvertImageCommand/3258.

Here is the result:

pyckett, an Interactive Slide Rule

Another example of Canvas graphics is the interactive pyckett program:

In [98]:
%%file applications/pyckett.py
"""
This program is an interactively emulates much of the functionality
of a Pickett slide rule, hence the name.  The center bar and slider
move with the left mouse button. Remember to keep fingerprints off
the clear plastic!

If you find it useful or amusing, let me know, but no warranty is
expressed or implied.

If you want to contribute to further development, it's on Sourceforge.
"""

from tkinter import *
from math import log10, pi


class Moveable:
    """This is a mixin class to make other classes that inherit from it
    moveable."""
    def __init__(self, rule, xMin=None, xMax=None):
        self.x = 0
        self.rule = rule
        self.wxPrev = None
        self.xMin = xMin
        self.xMax = xMax
        # Moveable objects should have tags bound to them.
        rule.tag_bind(self.tag, '<ButtonPress-1>', self.evtPress)
        rule.tag_bind(self.tag, '<Button1-Motion>', self.evtMotion)

    def evtPress(self, evt):
        self.wxPrev = evt.x

    def evtMotion(self, evt):
        dx = evt.x - self.wxPrev
        if self.xMax is not None:
            xNew = self.x + dx
            if xNew < self.xMin:
                xNew = self.xMin
                dx = xNew - self.x
            elif xNew > self.xMax:
                xNew = self.xMax
                dx = xNew - self.x
            self.x = xNew
        self.rule.move(self.tag, dx, 0)
        self.wxPrev = evt.x


class Bar:
    """One of the labelled bars of the slide rule."""
    def __init__(self, cnvs, x0, y0, w, h, xOff, tag=None):
        self.cnvs = cnvs
        self.x0 = x0
        self.y0 = y0
        self.w = w
        self.xOff = xOff
        self.tag = tag

        self.scaleW = w - 2*xOff
        self.scaleX0 = x0 + xOff
        cnvs.create_rectangle(x0, y0, x0+w, y0+h, fill='yellow', tag=tag)

    def addScale(self, label, dy0, nDec, hTickLabelled):
        if label[0] in 'ABCDK':
            LogScale(self.cnvs, label, self.y0 + dy0, self.scaleX0,
                     self.scaleW, nDec, hTickLabelled, self.tag)
        elif label == 'L':
            LinearScale(self.cnvs, label, self.y0 + dy0, self.scaleX0,
                     self.scaleW, hTickLabelled, self.tag)


class MoveableBar(Bar,Moveable):
    """A labelled bar of the slide rule that can move."""
    def __init__(self, rule, x0, y0, w, h, xOff, xMax):
        Bar.__init__(self, rule, x0, y0, w, h, xOff, 'centerbar')
        Moveable.__init__(self, rule, -xMax, xMax)


class LinearScale:
    """A linear scale."""
    def __init__(self, cnvs, lbl, y0, x0, w, hTickCoarse, tag=None):
        self.x0 = x0
        self.w = w

        fontLbl = ("Helvetica", 10)

        if hTickCoarse < 0:
            anchDgt = N
            anchLblL = NE
            anchLblR = NW
        else:
            anchDgt = S
            anchLblL = SE
            anchLblR = SW

        hTickMedium = hTickCoarse / 2
        b = 15 # x offset of label from start of scale
        cnvs.create_text(x0 - b, y0-hTickCoarse/12, text=lbl,
                         anchor=anchLblL, fill='black', tag=tag, font=fontLbl)
        cnvs.create_text(x0 + w + b, y0-hTickCoarse/12, text=lbl,
                         anchor=anchLblR, fill='black', tag=tag, font=fontLbl)

        fontDgt = ("Helvetica", 8)
        for iTickCoarse in range(11):  # allows for '1' tick at end
            t = iTickCoarse / 10.0
            x = self.xOfVal(t)
            cnvs.create_line(x, y0, x, y0-hTickCoarse/4, fill='black', tag=tag)
            if iTickCoarse == 0:
                sVal = '0'
            elif iTickCoarse == 10:
                sVal = '1'
            else:
                sVal = '.' + str(iTickCoarse)
            cnvs.create_text(x, y0-5*hTickCoarse/16, text=sVal,
                             anchor=anchDgt, fill='black', tag=tag,
                             font=fontDgt)
            if iTickCoarse == 10:  # we only want the '1' tick
                continue
            for iTickMedium in range(9):
                t = 0.1 * iTickCoarse + 0.01 * (iTickMedium + 1)
                x = self.xOfVal(t)
                hTick = hTickMedium
                if iTickMedium == 4: # make the midpoint longer
                    hTick = hTickCoarse
                cnvs.create_line(x, y0, x, y0-hTick, fill='black', tag=tag)

    def xOfVal(self, val):
        return self.x0 + val * self.w


class LogScale:
    """A logarithmic scale with a variable number of decades that may also
    be inverted (go from right-to-left)."""
    def __init__(self, cnvs, lbl, y0, x0, w, nDec, hTickCoarse, tag=None):
        self.x0 = x0
        self.w = w
        self.nDec = nDec

        if 'F' in lbl:
            self.v0 = pi
        else:
            self.v0 = 0
        self.inverse = 'I' in lbl # convention

        nTickMediumPerCoarse = 10
        nTickCoarse = 10 * nDec
        nTick = (10**nDec - 1) * nTickMediumPerCoarse + 1

        fontLbl = ("Helvetica", 10)
        fontSym = ("Symbol", 10)
        if nDec < 3:  # use a larger font for two or fewer decades
            fontDgtNormal = fontLbl
            fontDgtSmall = ("Helvetica", 8)
        else:
            fontDgtNormal = ("Helvetica", 8)
            fontDgtSmall = ("Helvetica", 6)

        if hTickCoarse < 0:
            anchDgt = N
            anchLblL = NE
            anchLblR = NW
        else:
            anchDgt = S
            anchLblL = SE
            anchLblR = SW

        if self.inverse:
            fllDgt = fllLbl = 'red'
        else:
            fllDgt = fllLbl = 'black'
            
        b = 15 # x offset of label from start of scale
        cnvs.create_text(x0 - b, y0-hTickCoarse/12, text=lbl,
                         anchor=anchLblL, fill=fllLbl, tag=tag, font=fontLbl)
        cnvs.create_text(x0 + w + b, y0-hTickCoarse/12, text=lbl,
                         anchor=anchLblR, fill=fllLbl, tag=tag, font=fontLbl)

        tick = 0
        # we span one more decade than nDec to get the major tick at the end
        for dec in range(nDec+1):
            dTick = 10.0**(dec-1)
            for iTickCoarse in range(9):
                x = self.xOfVal(tick+1)
                cnvs.create_line(x, y0, x, y0-hTickCoarse/4, fill='black',
                                 tag=tag)
                txt = self.lbl(iTickCoarse+1)
                cnvs.create_text(x, y0-hTickCoarse/4, text=txt,
                                 anchor=anchDgt, fill=fllDgt, tag=tag,
                                 font=fontDgtNormal)
                if dec == nDec:  # draws just the major tick of the next decade
                    break
                tick = tick + dTick
                for iTickMedium in range(9):
                    x = self.xOfVal(tick+1)

                    # Make the "5" tick longer than the rest (but see below).
                    if iTickMedium == 4:
                        hTickMedium = hTickCoarse
                    else:
                        hTickMedium = hTickCoarse/2

                    # If there's only one decade, show more detail in the
                    # [1..2] interval.
                    if nDec == 1 and iTickCoarse == 0:
                        txt = self.lbl(iTickMedium+1)
                        cnvs.create_text(x, y0-hTickCoarse/4, text=txt,
                                         anchor=NW, fill=fllDgt, tag=tag,
                                         font=fontDgtSmall)
                        # override tick labelled '5' for this decade
                        hTickMedium = hTickCoarse/2

                    cnvs.create_line(x, y0, x, y0-hTickMedium,
                                     fill='black', tag=tag)

                    tick = tick + dTick

        # If there are two or fewer decades, draw a tick at pi (on all
        # decades).
        if nDec <= 2:
            for dec in range(nDec):
                x = self.xOfVal(pi * 10**dec)
                cnvs.create_line(x, y0, x, y0-hTickCoarse, fill='black',
                                 tag=tag)
                cnvs.create_text(x, y0-hTickMedium/2, text='p',
                                 font=fontSym, anchor=anchDgt, fill=fllDgt,
                                 tag=tag)

        # If there's only one decade and this is not an 'F' scale,
        # draw a tick labelled 'R' at 180/pi (actually, 18/pi).
        if nDec == 1 and lbl[-1] != 'F':
            x = self.xOfVal(18.0/pi)
            cnvs.create_line(x, y0, x, y0-hTickCoarse, fill='black', tag=tag)
            cnvs.create_text(x, y0-hTickMedium/2, text='R',
                             font=fontDgtSmall, anchor=anchDgt, fill=fllDgt,
                             tag=tag)
            
    def lbl(self, val):
        txt = str(val)
        if self.inverse:  # another convention
            txt = '<' + txt
        return txt

    def xOfVal(self, val):
        t = log10(val) / self.nDec
        if self.v0 != 0:
            t0 = log10(self.v0) / self.nDec
            t = ((t-t0) % 1)
        if self.inverse:
            t = 1 - t
        return self.x0 + t * self.w


class Cursor(Moveable):

    def __init__(self, rule, xCtr, w, xMax):
        self.tag = 'cursor'
        self.rule = rule

        x0 = xCtr-w/2

        Moveable.__init__(self, rule, 0, xMax)

        hSlider = rule.h / 8.0

        # amount by which slider extends beyond cursor (on three sides)
        protrude = hSlider/6.0

        # draw the sliders themselves
        y0 = rule.y - hSlider
        rule.draw_beveled_rect(x0-protrude, y0, w + 2*protrude, hSlider, 2,
                               tag=self.tag)
        y1 = rule.y + rule.h
        rule.draw_beveled_rect(x0-protrude, y1, w + 2*protrude, hSlider, 2,
                               tag=self.tag)

        plastY0 = rule.y - hSlider + protrude
        plastY1 = y1 + hSlider - protrude

        rule.draw_beveled_rect(x0, plastY0, w, plastY1 - plastY0,
                               3, 1, tag=self.tag)

        rule.create_line(xCtr, y0+hSlider+protrude, xCtr, y1-protrude,
                         fill='red', tag=self.tag)  # cursor line itself

        # draw screws on the slider
        r = hSlider/8.0 # radius of screw

        # horizontal and vertical offsets of screw from edge
        d = hSlider/2.0 - r

        rule.draw_screw_head(x0 + d,     plastY0 + d, r, tag=self.tag)
        rule.draw_screw_head(x0 + w - d, plastY0 + d, r, tag=self.tag)
        rule.draw_screw_head(x0 + d,     plastY1 - d, r, tag=self.tag)
        rule.draw_screw_head(x0 + w - d, plastY1 - d, r, tag=self.tag)

class SlideRule(Canvas):

    def __init__(self, parent, xBorder, yBorder, w, h):
        Canvas.__init__(self, parent, width=w+2*xBorder, height=h+2*yBorder,
                        bg='navy')
        self.x = x = xBorder
        self.y = y = yBorder
        self.w = w
        self.h = h
        barXOff = 80 # horizontal distance from edge of bar to scale

        barH = h / 3

        self.brTop = Bar(self, x, y, w, barH, barXOff)
        self.brTop.addScale('DF', barH / 4, 1, -10)
        self.brTop.addScale('A', barH, 2, 10)

        self.brMid = MoveableBar(self, x, y+barH, w, barH, barXOff,
                                 self.brTop.scaleW)
        self.brMid.addScale('B',   0*barH/32, 2, -10)
        self.brMid.addScale('L',  15*barH/32, 1, 10)
        self.brMid.addScale('CI', 23*barH/32, 1, 10)
        self.brMid.addScale('C',  32*barH/32, 1, 10)

        self.brBot = Bar(self, x, y+2*barH, w, barH, barXOff)
        self.brBot.addScale('D', 0, 1, -10)
        self.brBot.addScale('DI', barH / 3, 1, -10)
        self.brBot.addScale('K', 2*barH / 3, 3, -10)

        capW = barXOff/3

        # draw the left cap
        self.draw_beveled_rect(x, y, capW, h, 4)
        self.draw_screw_head(x + capW/2, y + capW/2, 5)
        self.draw_screw_head(x + capW/2, y + h - capW/2, 5)

        # draw the right cap
        self.draw_beveled_rect(x+w-capW, y, capW, h, 4)
        self.draw_screw_head(x + w - capW/2, y + capW/2, 5)
        self.draw_screw_head(x + w - capW/2, y + h - capW/2, 5)

        Cursor(self, xBorder + self.brBot.xOff, w/10, self.brBot.scaleW)

    def draw_beveled_rect(cnvs, x, y, w, h, bvl, isTransp=0, tag=None):
        xIn = x + bvl
        yIn = y + bvl
        wIn = w - 2*bvl
        hIn = h - 2*bvl
        if isTransp:
            fll = { 'center': '', 'top': '', 'left': '',
                    'bottom': '', 'right': '' }
            outl = 'black'
        else:
            fll = { 'center': 'gray50', 'top': 'gray70',
                    'left': 'gray40', 'bottom': 'gray30',
                    'right': 'gray50' }
            outl = ''
        
        cnvs.create_rectangle(xIn, yIn, xIn+wIn, yIn+hIn,
                              fill=fll['center'], outline=outl, tag=tag)
        cnvs.create_polygon(x, y, x+w, y, xIn+wIn, yIn, xIn, yIn,
                            fill=fll['top'], outline=outl, tag=tag)
        cnvs.create_polygon(x, y, xIn, yIn, xIn, yIn+hIn, x, y+h,
                            fill=fll['left'], outline=outl, tag=tag)
        cnvs.create_polygon(xIn, yIn+hIn, xIn+wIn, yIn+hIn, x+w, y+h, x, y+h,
                            fill=fll['bottom'], outline=outl, tag=tag)
        cnvs.create_polygon(xIn+wIn, yIn, x+w, y, x+w, y+h, xIn+wIn, yIn+hIn,
                            fill=fll['right'], outline=outl, tag=tag)

    def draw_screw_head(cnvs, x, y, r, tag=None):
        cnvs.create_oval(x-r, y-r, x+r, y+r, fill='gray40', tag=tag)
        cnvs.create_line(x-r, y, x+r, y, fill='black', tag=tag)

if __name__ == '__main__':
    root = Tk()
    root.title('pyckett -- a Python slide rule')
    slideRule = SlideRule(root, xBorder=30, yBorder=30, w=1000, h=200)
    slideRule.grid()
    root.mainloop()
Overwriting applications/pyckett.py
In [99]:
if runTkinter:
    !python3 applications/pyckett.py

(Not working) Aside: This also works, but adds to the Jupyter Notebook namespace, so I prefer the above.

In [102]:
if runTkinter:
    import applications.pyckett

and, just as a reminder:

In [103]:
%%sh
# using "%%sh", as inline "!" includes some non-printable characters
python3 -m pydoc applications.pyckett
Help on module applications.pyckett in applications:

NAME
    applications.pyckett

DESCRIPTION
    This program is an interactively emulates much of the functionality
    of a Pickett slide rule, hence the name.  The center bar and slider
    move with the left mouse button. Remember to keep fingerprints off
    the clear plastic!
    
    If you find it useful or amusing, let me know, but no warranty is
    expressed or implied.
    
    If you want to contribute to further development, it's on Sourceforge.

CLASSES
    builtins.object
        Bar
            MoveableBar(Bar, Moveable)
        LinearScale
        LogScale
        Moveable
            Cursor
    tkinter.Canvas(tkinter.Widget, tkinter.XView, tkinter.YView)
        SlideRule
    
    class Bar(builtins.object)
     |  Bar(cnvs, x0, y0, w, h, xOff, tag=None)
     |  
     |  One of the labelled bars of the slide rule.
     |  
     |  Methods defined here:
     |  
     |  __init__(self, cnvs, x0, y0, w, h, xOff, tag=None)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  addScale(self, label, dy0, nDec, hTickLabelled)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class Cursor(Moveable)
     |  Cursor(rule, xCtr, w, xMax)
     |  
     |  This is a mixin class to make other classes that inherit from it
     |  moveable.
     |  
     |  Method resolution order:
     |      Cursor
     |      Moveable
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __init__(self, rule, xCtr, w, xMax)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from Moveable:
     |  
     |  evtMotion(self, evt)
     |  
     |  evtPress(self, evt)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from Moveable:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class LinearScale(builtins.object)
     |  LinearScale(cnvs, lbl, y0, x0, w, hTickCoarse, tag=None)
     |  
     |  A linear scale.
     |  
     |  Methods defined here:
     |  
     |  __init__(self, cnvs, lbl, y0, x0, w, hTickCoarse, tag=None)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  xOfVal(self, val)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class LogScale(builtins.object)
     |  LogScale(cnvs, lbl, y0, x0, w, nDec, hTickCoarse, tag=None)
     |  
     |  A logarithmic scale with a variable number of decades that may also
     |  be inverted (go from right-to-left).
     |  
     |  Methods defined here:
     |  
     |  __init__(self, cnvs, lbl, y0, x0, w, nDec, hTickCoarse, tag=None)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  lbl(self, val)
     |  
     |  xOfVal(self, val)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class Moveable(builtins.object)
     |  Moveable(rule, xMin=None, xMax=None)
     |  
     |  This is a mixin class to make other classes that inherit from it
     |  moveable.
     |  
     |  Methods defined here:
     |  
     |  __init__(self, rule, xMin=None, xMax=None)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  evtMotion(self, evt)
     |  
     |  evtPress(self, evt)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class MoveableBar(Bar, Moveable)
     |  MoveableBar(rule, x0, y0, w, h, xOff, xMax)
     |  
     |  A labelled bar of the slide rule that can move.
     |  
     |  Method resolution order:
     |      MoveableBar
     |      Bar
     |      Moveable
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __init__(self, rule, x0, y0, w, h, xOff, xMax)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from Bar:
     |  
     |  addScale(self, label, dy0, nDec, hTickLabelled)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from Bar:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from Moveable:
     |  
     |  evtMotion(self, evt)
     |  
     |  evtPress(self, evt)
    
    class SlideRule(tkinter.Canvas)
     |  SlideRule(parent, xBorder, yBorder, w, h)
     |  
     |  Canvas widget to display graphical elements like lines or text.
     |  
     |  Method resolution order:
     |      SlideRule
     |      tkinter.Canvas
     |      tkinter.Widget
     |      tkinter.BaseWidget
     |      tkinter.Misc
     |      tkinter.Pack
     |      tkinter.Place
     |      tkinter.Grid
     |      tkinter.XView
     |      tkinter.YView
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __init__(self, parent, xBorder, yBorder, w, h)
     |      Construct a canvas widget with the parent MASTER.
     |      
     |      Valid resource names: background, bd, bg, borderwidth, closeenough,
     |      confine, cursor, height, highlightbackground, highlightcolor,
     |      highlightthickness, insertbackground, insertborderwidth,
     |      insertofftime, insertontime, insertwidth, offset, relief,
     |      scrollregion, selectbackground, selectborderwidth, selectforeground,
     |      state, takefocus, width, xscrollcommand, xscrollincrement,
     |      yscrollcommand, yscrollincrement.
     |  
     |  draw_beveled_rect(cnvs, x, y, w, h, bvl, isTransp=0, tag=None)
     |  
     |  draw_screw_head(cnvs, x, y, r, tag=None)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.Canvas:
     |  
     |  addtag(self, *args)
     |      Internal function.
     |  
     |  addtag_above(self, newtag, tagOrId)
     |      Add tag NEWTAG to all items above TAGORID.
     |  
     |  addtag_all(self, newtag)
     |      Add tag NEWTAG to all items.
     |  
     |  addtag_below(self, newtag, tagOrId)
     |      Add tag NEWTAG to all items below TAGORID.
     |  
     |  addtag_closest(self, newtag, x, y, halo=None, start=None)
     |      Add tag NEWTAG to item which is closest to pixel at X, Y.
     |      If several match take the top-most.
     |      All items closer than HALO are considered overlapping (all are
     |      closests). If START is specified the next below this tag is taken.
     |  
     |  addtag_enclosed(self, newtag, x1, y1, x2, y2)
     |      Add tag NEWTAG to all items in the rectangle defined
     |      by X1,Y1,X2,Y2.
     |  
     |  addtag_overlapping(self, newtag, x1, y1, x2, y2)
     |      Add tag NEWTAG to all items which overlap the rectangle
     |      defined by X1,Y1,X2,Y2.
     |  
     |  addtag_withtag(self, newtag, tagOrId)
     |      Add tag NEWTAG to all items with TAGORID.
     |  
     |  bbox(self, *args)
     |      Return a tuple of X1,Y1,X2,Y2 coordinates for a rectangle
     |      which encloses all items with tags specified as arguments.
     |  
     |  canvasx(self, screenx, gridspacing=None)
     |      Return the canvas x coordinate of pixel position SCREENX rounded
     |      to nearest multiple of GRIDSPACING units.
     |  
     |  canvasy(self, screeny, gridspacing=None)
     |      Return the canvas y coordinate of pixel position SCREENY rounded
     |      to nearest multiple of GRIDSPACING units.
     |  
     |  coords(self, *args)
     |      Return a list of coordinates for the item given in ARGS.
     |  
     |  create_arc(self, *args, **kw)
     |      Create arc shaped region with coordinates x1,y1,x2,y2.
     |  
     |  create_bitmap(self, *args, **kw)
     |      Create bitmap with coordinates x1,y1.
     |  
     |  create_image(self, *args, **kw)
     |      Create image item with coordinates x1,y1.
     |  
     |  create_line(self, *args, **kw)
     |      Create line with coordinates x1,y1,...,xn,yn.
     |  
     |  create_oval(self, *args, **kw)
     |      Create oval with coordinates x1,y1,x2,y2.
     |  
     |  create_polygon(self, *args, **kw)
     |      Create polygon with coordinates x1,y1,...,xn,yn.
     |  
     |  create_rectangle(self, *args, **kw)
     |      Create rectangle with coordinates x1,y1,x2,y2.
     |  
     |  create_text(self, *args, **kw)
     |      Create text with coordinates x1,y1.
     |  
     |  create_window(self, *args, **kw)
     |      Create window with coordinates x1,y1,x2,y2.
     |  
     |  dchars(self, *args)
     |      Delete characters of text items identified by tag or id in ARGS (possibly
     |      several times) from FIRST to LAST character (including).
     |  
     |  delete(self, *args)
     |      Delete items identified by all tag or ids contained in ARGS.
     |  
     |  dtag(self, *args)
     |      Delete tag or id given as last arguments in ARGS from items
     |      identified by first argument in ARGS.
     |  
     |  find(self, *args)
     |      Internal function.
     |  
     |  find_above(self, tagOrId)
     |      Return items above TAGORID.
     |  
     |  find_all(self)
     |      Return all items.
     |  
     |  find_below(self, tagOrId)
     |      Return all items below TAGORID.
     |  
     |  find_closest(self, x, y, halo=None, start=None)
     |      Return item which is closest to pixel at X, Y.
     |      If several match take the top-most.
     |      All items closer than HALO are considered overlapping (all are
     |      closest). If START is specified the next below this tag is taken.
     |  
     |  find_enclosed(self, x1, y1, x2, y2)
     |      Return all items in rectangle defined
     |      by X1,Y1,X2,Y2.
     |  
     |  find_overlapping(self, x1, y1, x2, y2)
     |      Return all items which overlap the rectangle
     |      defined by X1,Y1,X2,Y2.
     |  
     |  find_withtag(self, tagOrId)
     |      Return all items with TAGORID.
     |  
     |  focus(self, *args)
     |      Set focus to the first item specified in ARGS.
     |  
     |  gettags(self, *args)
     |      Return tags associated with the first item specified in ARGS.
     |  
     |  icursor(self, *args)
     |      Set cursor at position POS in the item identified by TAGORID.
     |      In ARGS TAGORID must be first.
     |  
     |  index(self, *args)
     |      Return position of cursor as integer in item specified in ARGS.
     |  
     |  insert(self, *args)
     |      Insert TEXT in item TAGORID at position POS. ARGS must
     |      be TAGORID POS TEXT.
     |  
     |  itemcget(self, tagOrId, option)
     |      Return the resource value for an OPTION for item TAGORID.
     |  
     |  itemconfig = itemconfigure(self, tagOrId, cnf=None, **kw)
     |  
     |  itemconfigure(self, tagOrId, cnf=None, **kw)
     |      Configure resources of an item TAGORID.
     |      
     |      The values for resources are specified as keyword
     |      arguments. To get an overview about
     |      the allowed keyword arguments call the method without arguments.
     |  
     |  lift = tag_raise(self, *args)
     |  
     |  lower = tag_lower(self, *args)
     |  
     |  move(self, *args)
     |      Move an item TAGORID given in ARGS.
     |  
     |  moveto(self, tagOrId, x='', y='')
     |      Move the items given by TAGORID in the canvas coordinate
     |      space so that the first coordinate pair of the bottommost
     |      item with tag TAGORID is located at position (X,Y).
     |      X and Y may be the empty string, in which case the
     |      corresponding coordinate will be unchanged. All items matching
     |      TAGORID remain in the same positions relative to each other.
     |  
     |  postscript(self, cnf={}, **kw)
     |      Print the contents of the canvas to a postscript
     |      file. Valid options: colormap, colormode, file, fontmap,
     |      height, pageanchor, pageheight, pagewidth, pagex, pagey,
     |      rotate, width, x, y.
     |  
     |  scale(self, *args)
     |      Scale item TAGORID with XORIGIN, YORIGIN, XSCALE, YSCALE.
     |  
     |  scan_dragto(self, x, y, gain=10)
     |      Adjust the view of the canvas to GAIN times the
     |      difference between X and Y and the coordinates given in
     |      scan_mark.
     |  
     |  scan_mark(self, x, y)
     |      Remember the current X, Y coordinates.
     |  
     |  select_adjust(self, tagOrId, index)
     |      Adjust the end of the selection near the cursor of an item TAGORID to index.
     |  
     |  select_clear(self)
     |      Clear the selection if it is in this widget.
     |  
     |  select_from(self, tagOrId, index)
     |      Set the fixed end of a selection in item TAGORID to INDEX.
     |  
     |  select_item(self)
     |      Return the item which has the selection.
     |  
     |  select_to(self, tagOrId, index)
     |      Set the variable end of a selection in item TAGORID to INDEX.
     |  
     |  tag_bind(self, tagOrId, sequence=None, func=None, add=None)
     |      Bind to all items with TAGORID at event SEQUENCE a call to function FUNC.
     |      
     |      An additional boolean parameter ADD specifies whether FUNC will be
     |      called additionally to the other bound function or whether it will
     |      replace the previous function. See bind for the return value.
     |  
     |  tag_lower(self, *args)
     |      Lower an item TAGORID given in ARGS
     |      (optional below another item).
     |  
     |  tag_raise(self, *args)
     |      Raise an item TAGORID given in ARGS
     |      (optional above another item).
     |  
     |  tag_unbind(self, tagOrId, sequence, funcid=None)
     |      Unbind for all items with TAGORID for event SEQUENCE  the
     |      function identified with FUNCID.
     |  
     |  tkraise = tag_raise(self, *args)
     |  
     |  type(self, tagOrId)
     |      Return the type of the item TAGORID.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.BaseWidget:
     |  
     |  destroy(self)
     |      Destroy this and all descendants widgets.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.Misc:
     |  
     |  __getitem__ = cget(self, key)
     |  
     |  __repr__(self)
     |      Return repr(self).
     |  
     |  __setitem__(self, key, value)
     |  
     |  __str__(self)
     |      Return the window path name of this widget.
     |  
     |  after(self, ms, func=None, *args)
     |      Call function once after given time.
     |      
     |      MS specifies the time in milliseconds. FUNC gives the
     |      function which shall be called. Additional parameters
     |      are given as parameters to the function call.  Return
     |      identifier to cancel scheduling with after_cancel.
     |  
     |  after_cancel(self, id)
     |      Cancel scheduling of function identified with ID.
     |      
     |      Identifier returned by after or after_idle must be
     |      given as first parameter.
     |  
     |  after_idle(self, func, *args)
     |      Call FUNC once if the Tcl main loop has no event to
     |      process.
     |      
     |      Return an identifier to cancel the scheduling with
     |      after_cancel.
     |  
     |  anchor = grid_anchor(self, anchor=None)
     |  
     |  bell(self, displayof=0)
     |      Ring a display's bell.
     |  
     |  bind(self, sequence=None, func=None, add=None)
     |      Bind to this widget at event SEQUENCE a call to function FUNC.
     |      
     |      SEQUENCE is a string of concatenated event
     |      patterns. An event pattern is of the form
     |      <MODIFIER-MODIFIER-TYPE-DETAIL> where MODIFIER is one
     |      of Control, Mod2, M2, Shift, Mod3, M3, Lock, Mod4, M4,
     |      Button1, B1, Mod5, M5 Button2, B2, Meta, M, Button3,
     |      B3, Alt, Button4, B4, Double, Button5, B5 Triple,
     |      Mod1, M1. TYPE is one of Activate, Enter, Map,
     |      ButtonPress, Button, Expose, Motion, ButtonRelease
     |      FocusIn, MouseWheel, Circulate, FocusOut, Property,
     |      Colormap, Gravity Reparent, Configure, KeyPress, Key,
     |      Unmap, Deactivate, KeyRelease Visibility, Destroy,
     |      Leave and DETAIL is the button number for ButtonPress,
     |      ButtonRelease and DETAIL is the Keysym for KeyPress and
     |      KeyRelease. Examples are
     |      <Control-Button-1> for pressing Control and mouse button 1 or
     |      <Alt-A> for pressing A and the Alt key (KeyPress can be omitted).
     |      An event pattern can also be a virtual event of the form
     |      <<AString>> where AString can be arbitrary. This
     |      event can be generated by event_generate.
     |      If events are concatenated they must appear shortly
     |      after each other.
     |      
     |      FUNC will be called if the event sequence occurs with an
     |      instance of Event as argument. If the return value of FUNC is
     |      "break" no further bound function is invoked.
     |      
     |      An additional boolean parameter ADD specifies whether FUNC will
     |      be called additionally to the other bound function or whether
     |      it will replace the previous function.
     |      
     |      Bind will return an identifier to allow deletion of the bound function with
     |      unbind without memory leak.
     |      
     |      If FUNC or SEQUENCE is omitted the bound function or list
     |      of bound events are returned.
     |  
     |  bind_all(self, sequence=None, func=None, add=None)
     |      Bind to all widgets at an event SEQUENCE a call to function FUNC.
     |      An additional boolean parameter ADD specifies whether FUNC will
     |      be called additionally to the other bound function or whether
     |      it will replace the previous function. See bind for the return value.
     |  
     |  bind_class(self, className, sequence=None, func=None, add=None)
     |      Bind to widgets with bindtag CLASSNAME at event
     |      SEQUENCE a call of function FUNC. An additional
     |      boolean parameter ADD specifies whether FUNC will be
     |      called additionally to the other bound function or
     |      whether it will replace the previous function. See bind for
     |      the return value.
     |  
     |  bindtags(self, tagList=None)
     |      Set or get the list of bindtags for this widget.
     |      
     |      With no argument return the list of all bindtags associated with
     |      this widget. With a list of strings as argument the bindtags are
     |      set to this list. The bindtags determine in which order events are
     |      processed (see bind).
     |  
     |  cget(self, key)
     |      Return the resource value for a KEY given as string.
     |  
     |  clipboard_append(self, string, **kw)
     |      Append STRING to the Tk clipboard.
     |      
     |      A widget specified at the optional displayof keyword
     |      argument specifies the target display. The clipboard
     |      can be retrieved with selection_get.
     |  
     |  clipboard_clear(self, **kw)
     |      Clear the data in the Tk clipboard.
     |      
     |      A widget specified for the optional displayof keyword
     |      argument specifies the target display.
     |  
     |  clipboard_get(self, **kw)
     |      Retrieve data from the clipboard on window's display.
     |      
     |      The window keyword defaults to the root window of the Tkinter
     |      application.
     |      
     |      The type keyword specifies the form in which the data is
     |      to be returned and should be an atom name such as STRING
     |      or FILE_NAME.  Type defaults to STRING, except on X11, where the default
     |      is to try UTF8_STRING and fall back to STRING.
     |      
     |      This command is equivalent to:
     |      
     |      selection_get(CLIPBOARD)
     |  
     |  columnconfigure = grid_columnconfigure(self, index, cnf={}, **kw)
     |  
     |  config = configure(self, cnf=None, **kw)
     |  
     |  configure(self, cnf=None, **kw)
     |      Configure resources of a widget.
     |      
     |      The values for resources are specified as keyword
     |      arguments. To get an overview about
     |      the allowed keyword arguments call the method keys.
     |  
     |  deletecommand(self, name)
     |      Internal function.
     |      
     |      Delete the Tcl command provided in NAME.
     |  
     |  event_add(self, virtual, *sequences)
     |      Bind a virtual event VIRTUAL (of the form <<Name>>)
     |      to an event SEQUENCE such that the virtual event is triggered
     |      whenever SEQUENCE occurs.
     |  
     |  event_delete(self, virtual, *sequences)
     |      Unbind a virtual event VIRTUAL from SEQUENCE.
     |  
     |  event_generate(self, sequence, **kw)
     |      Generate an event SEQUENCE. Additional
     |      keyword arguments specify parameter of the event
     |      (e.g. x, y, rootx, rooty).
     |  
     |  event_info(self, virtual=None)
     |      Return a list of all virtual events or the information
     |      about the SEQUENCE bound to the virtual event VIRTUAL.
     |  
     |  focus_displayof(self)
     |      Return the widget which has currently the focus on the
     |      display where this widget is located.
     |      
     |      Return None if the application does not have the focus.
     |  
     |  focus_force(self)
     |      Direct input focus to this widget even if the
     |      application does not have the focus. Use with
     |      caution!
     |  
     |  focus_get(self)
     |      Return the widget which has currently the focus in the
     |      application.
     |      
     |      Use focus_displayof to allow working with several
     |      displays. Return None if application does not have
     |      the focus.
     |  
     |  focus_lastfor(self)
     |      Return the widget which would have the focus if top level
     |      for this widget gets the focus from the window manager.
     |  
     |  focus_set(self)
     |      Direct input focus to this widget.
     |      
     |      If the application currently does not have the focus
     |      this widget will get the focus if the application gets
     |      the focus through the window manager.
     |  
     |  getboolean(self, s)
     |      Return a boolean value for Tcl boolean values true and false given as parameter.
     |  
     |  getdouble(self, s)
     |  
     |  getint(self, s)
     |  
     |  getvar(self, name='PY_VAR')
     |      Return value of Tcl variable NAME.
     |  
     |  grab_current(self)
     |      Return widget which has currently the grab in this application
     |      or None.
     |  
     |  grab_release(self)
     |      Release grab for this widget if currently set.
     |  
     |  grab_set(self)
     |      Set grab for this widget.
     |      
     |      A grab directs all events to this and descendant
     |      widgets in the application.
     |  
     |  grab_set_global(self)
     |      Set global grab for this widget.
     |      
     |      A global grab directs all events to this and
     |      descendant widgets on the display. Use with caution -
     |      other applications do not get events anymore.
     |  
     |  grab_status(self)
     |      Return None, "local" or "global" if this widget has
     |      no, a local or a global grab.
     |  
     |  grid_anchor(self, anchor=None)
     |      The anchor value controls how to place the grid within the
     |      master when no row/column has any weight.
     |      
     |      The default anchor is nw.
     |  
     |  grid_bbox(self, column=None, row=None, col2=None, row2=None)
     |      Return a tuple of integer coordinates for the bounding
     |      box of this widget controlled by the geometry manager grid.
     |      
     |      If COLUMN, ROW is given the bounding box applies from
     |      the cell with row and column 0 to the specified
     |      cell. If COL2 and ROW2 are given the bounding box
     |      starts at that cell.
     |      
     |      The returned integers specify the offset of the upper left
     |      corner in the master widget and the width and height.
     |  
     |  grid_columnconfigure(self, index, cnf={}, **kw)
     |      Configure column INDEX of a grid.
     |      
     |      Valid resources are minsize (minimum size of the column),
     |      weight (how much does additional space propagate to this column)
     |      and pad (how much space to let additionally).
     |  
     |  grid_location(self, x, y)
     |      Return a tuple of column and row which identify the cell
     |      at which the pixel at position X and Y inside the master
     |      widget is located.
     |  
     |  grid_propagate(self, flag=['_noarg_'])
     |      Set or get the status for propagation of geometry information.
     |      
     |      A boolean argument specifies whether the geometry information
     |      of the slaves will determine the size of this widget. If no argument
     |      is given, the current setting will be returned.
     |  
     |  grid_rowconfigure(self, index, cnf={}, **kw)
     |      Configure row INDEX of a grid.
     |      
     |      Valid resources are minsize (minimum size of the row),
     |      weight (how much does additional space propagate to this row)
     |      and pad (how much space to let additionally).
     |  
     |  grid_size(self)
     |      Return a tuple of the number of column and rows in the grid.
     |  
     |  grid_slaves(self, row=None, column=None)
     |      Return a list of all slaves of this widget
     |      in its packing order.
     |  
     |  image_names(self)
     |      Return a list of all existing image names.
     |  
     |  image_types(self)
     |      Return a list of all available image types (e.g. photo bitmap).
     |  
     |  keys(self)
     |      Return a list of all resource names of this widget.
     |  
     |  mainloop(self, n=0)
     |      Call the mainloop of Tk.
     |  
     |  nametowidget(self, name)
     |      Return the Tkinter instance of a widget identified by
     |      its Tcl name NAME.
     |  
     |  option_add(self, pattern, value, priority=None)
     |      Set a VALUE (second parameter) for an option
     |      PATTERN (first parameter).
     |      
     |      An optional third parameter gives the numeric priority
     |      (defaults to 80).
     |  
     |  option_clear(self)
     |      Clear the option database.
     |      
     |      It will be reloaded if option_add is called.
     |  
     |  option_get(self, name, className)
     |      Return the value for an option NAME for this widget
     |      with CLASSNAME.
     |      
     |      Values with higher priority override lower values.
     |  
     |  option_readfile(self, fileName, priority=None)
     |      Read file FILENAME into the option database.
     |      
     |      An optional second parameter gives the numeric
     |      priority.
     |  
     |  pack_propagate(self, flag=['_noarg_'])
     |      Set or get the status for propagation of geometry information.
     |      
     |      A boolean argument specifies whether the geometry information
     |      of the slaves will determine the size of this widget. If no argument
     |      is given the current setting will be returned.
     |  
     |  pack_slaves(self)
     |      Return a list of all slaves of this widget
     |      in its packing order.
     |  
     |  place_slaves(self)
     |      Return a list of all slaves of this widget
     |      in its packing order.
     |  
     |  propagate = pack_propagate(self, flag=['_noarg_'])
     |  
     |  quit(self)
     |      Quit the Tcl interpreter. All widgets will be destroyed.
     |  
     |  register = _register(self, func, subst=None, needcleanup=1)
     |  
     |  rowconfigure = grid_rowconfigure(self, index, cnf={}, **kw)
     |  
     |  selection_clear(self, **kw)
     |      Clear the current X selection.
     |  
     |  selection_get(self, **kw)
     |      Return the contents of the current X selection.
     |      
     |      A keyword parameter selection specifies the name of
     |      the selection and defaults to PRIMARY.  A keyword
     |      parameter displayof specifies a widget on the display
     |      to use. A keyword parameter type specifies the form of data to be
     |      fetched, defaulting to STRING except on X11, where UTF8_STRING is tried
     |      before STRING.
     |  
     |  selection_handle(self, command, **kw)
     |      Specify a function COMMAND to call if the X
     |      selection owned by this widget is queried by another
     |      application.
     |      
     |      This function must return the contents of the
     |      selection. The function will be called with the
     |      arguments OFFSET and LENGTH which allows the chunking
     |      of very long selections. The following keyword
     |      parameters can be provided:
     |      selection - name of the selection (default PRIMARY),
     |      type - type of the selection (e.g. STRING, FILE_NAME).
     |  
     |  selection_own(self, **kw)
     |      Become owner of X selection.
     |      
     |      A keyword parameter selection specifies the name of
     |      the selection (default PRIMARY).
     |  
     |  selection_own_get(self, **kw)
     |      Return owner of X selection.
     |      
     |      The following keyword parameter can
     |      be provided:
     |      selection - name of the selection (default PRIMARY),
     |      type - type of the selection (e.g. STRING, FILE_NAME).
     |  
     |  send(self, interp, cmd, *args)
     |      Send Tcl command CMD to different interpreter INTERP to be executed.
     |  
     |  setvar(self, name='PY_VAR', value='1')
     |      Set Tcl variable NAME to VALUE.
     |  
     |  size = grid_size(self)
     |  
     |  slaves = pack_slaves(self)
     |  
     |  tk_bisque(self)
     |      Change the color scheme to light brown as used in Tk 3.6 and before.
     |  
     |  tk_focusFollowsMouse(self)
     |      The widget under mouse will get automatically focus. Can not
     |      be disabled easily.
     |  
     |  tk_focusNext(self)
     |      Return the next widget in the focus order which follows
     |      widget which has currently the focus.
     |      
     |      The focus order first goes to the next child, then to
     |      the children of the child recursively and then to the
     |      next sibling which is higher in the stacking order.  A
     |      widget is omitted if it has the takefocus resource set
     |      to 0.
     |  
     |  tk_focusPrev(self)
     |      Return previous widget in the focus order. See tk_focusNext for details.
     |  
     |  tk_setPalette(self, *args, **kw)
     |      Set a new color scheme for all widget elements.
     |      
     |      A single color as argument will cause that all colors of Tk
     |      widget elements are derived from this.
     |      Alternatively several keyword parameters and its associated
     |      colors can be given. The following keywords are valid:
     |      activeBackground, foreground, selectColor,
     |      activeForeground, highlightBackground, selectBackground,
     |      background, highlightColor, selectForeground,
     |      disabledForeground, insertBackground, troughColor.
     |  
     |  tk_strictMotif(self, boolean=None)
     |      Set Tcl internal variable, whether the look and feel
     |      should adhere to Motif.
     |      
     |      A parameter of 1 means adhere to Motif (e.g. no color
     |      change if mouse passes over slider).
     |      Returns the set value.
     |  
     |  unbind(self, sequence, funcid=None)
     |      Unbind for this widget for event SEQUENCE  the
     |      function identified with FUNCID.
     |  
     |  unbind_all(self, sequence)
     |      Unbind for all widgets for event SEQUENCE all functions.
     |  
     |  unbind_class(self, className, sequence)
     |      Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE
     |      all functions.
     |  
     |  update(self)
     |      Enter event loop until all pending events have been processed by Tcl.
     |  
     |  update_idletasks(self)
     |      Enter event loop until all idle callbacks have been called. This
     |      will update the display of windows but not process events caused by
     |      the user.
     |  
     |  wait_variable(self, name='PY_VAR')
     |      Wait until the variable is modified.
     |      
     |      A parameter of type IntVar, StringVar, DoubleVar or
     |      BooleanVar must be given.
     |  
     |  wait_visibility(self, window=None)
     |      Wait until the visibility of a WIDGET changes
     |      (e.g. it appears).
     |      
     |      If no parameter is given self is used.
     |  
     |  wait_window(self, window=None)
     |      Wait until a WIDGET is destroyed.
     |      
     |      If no parameter is given self is used.
     |  
     |  waitvar = wait_variable(self, name='PY_VAR')
     |  
     |  winfo_atom(self, name, displayof=0)
     |      Return integer which represents atom NAME.
     |  
     |  winfo_atomname(self, id, displayof=0)
     |      Return name of atom with identifier ID.
     |  
     |  winfo_cells(self)
     |      Return number of cells in the colormap for this widget.
     |  
     |  winfo_children(self)
     |      Return a list of all widgets which are children of this widget.
     |  
     |  winfo_class(self)
     |      Return window class name of this widget.
     |  
     |  winfo_colormapfull(self)
     |      Return True if at the last color request the colormap was full.
     |  
     |  winfo_containing(self, rootX, rootY, displayof=0)
     |      Return the widget which is at the root coordinates ROOTX, ROOTY.
     |  
     |  winfo_depth(self)
     |      Return the number of bits per pixel.
     |  
     |  winfo_exists(self)
     |      Return true if this widget exists.
     |  
     |  winfo_fpixels(self, number)
     |      Return the number of pixels for the given distance NUMBER
     |      (e.g. "3c") as float.
     |  
     |  winfo_geometry(self)
     |      Return geometry string for this widget in the form "widthxheight+X+Y".
     |  
     |  winfo_height(self)
     |      Return height of this widget.
     |  
     |  winfo_id(self)
     |      Return identifier ID for this widget.
     |  
     |  winfo_interps(self, displayof=0)
     |      Return the name of all Tcl interpreters for this display.
     |  
     |  winfo_ismapped(self)
     |      Return true if this widget is mapped.
     |  
     |  winfo_manager(self)
     |      Return the window manager name for this widget.
     |  
     |  winfo_name(self)
     |      Return the name of this widget.
     |  
     |  winfo_parent(self)
     |      Return the name of the parent of this widget.
     |  
     |  winfo_pathname(self, id, displayof=0)
     |      Return the pathname of the widget given by ID.
     |  
     |  winfo_pixels(self, number)
     |      Rounded integer value of winfo_fpixels.
     |  
     |  winfo_pointerx(self)
     |      Return the x coordinate of the pointer on the root window.
     |  
     |  winfo_pointerxy(self)
     |      Return a tuple of x and y coordinates of the pointer on the root window.
     |  
     |  winfo_pointery(self)
     |      Return the y coordinate of the pointer on the root window.
     |  
     |  winfo_reqheight(self)
     |      Return requested height of this widget.
     |  
     |  winfo_reqwidth(self)
     |      Return requested width of this widget.
     |  
     |  winfo_rgb(self, color)
     |      Return tuple of decimal values for red, green, blue for
     |      COLOR in this widget.
     |  
     |  winfo_rootx(self)
     |      Return x coordinate of upper left corner of this widget on the
     |      root window.
     |  
     |  winfo_rooty(self)
     |      Return y coordinate of upper left corner of this widget on the
     |      root window.
     |  
     |  winfo_screen(self)
     |      Return the screen name of this widget.
     |  
     |  winfo_screencells(self)
     |      Return the number of the cells in the colormap of the screen
     |      of this widget.
     |  
     |  winfo_screendepth(self)
     |      Return the number of bits per pixel of the root window of the
     |      screen of this widget.
     |  
     |  winfo_screenheight(self)
     |      Return the number of pixels of the height of the screen of this widget
     |      in pixel.
     |  
     |  winfo_screenmmheight(self)
     |      Return the number of pixels of the height of the screen of
     |      this widget in mm.
     |  
     |  winfo_screenmmwidth(self)
     |      Return the number of pixels of the width of the screen of
     |      this widget in mm.
     |  
     |  winfo_screenvisual(self)
     |      Return one of the strings directcolor, grayscale, pseudocolor,
     |      staticcolor, staticgray, or truecolor for the default
     |      colormodel of this screen.
     |  
     |  winfo_screenwidth(self)
     |      Return the number of pixels of the width of the screen of
     |      this widget in pixel.
     |  
     |  winfo_server(self)
     |      Return information of the X-Server of the screen of this widget in
     |      the form "XmajorRminor vendor vendorVersion".
     |  
     |  winfo_toplevel(self)
     |      Return the toplevel widget of this widget.
     |  
     |  winfo_viewable(self)
     |      Return true if the widget and all its higher ancestors are mapped.
     |  
     |  winfo_visual(self)
     |      Return one of the strings directcolor, grayscale, pseudocolor,
     |      staticcolor, staticgray, or truecolor for the
     |      colormodel of this widget.
     |  
     |  winfo_visualid(self)
     |      Return the X identifier for the visual for this widget.
     |  
     |  winfo_visualsavailable(self, includeids=False)
     |      Return a list of all visuals available for the screen
     |      of this widget.
     |      
     |      Each item in the list consists of a visual name (see winfo_visual), a
     |      depth and if includeids is true is given also the X identifier.
     |  
     |  winfo_vrootheight(self)
     |      Return the height of the virtual root window associated with this
     |      widget in pixels. If there is no virtual root window return the
     |      height of the screen.
     |  
     |  winfo_vrootwidth(self)
     |      Return the width of the virtual root window associated with this
     |      widget in pixel. If there is no virtual root window return the
     |      width of the screen.
     |  
     |  winfo_vrootx(self)
     |      Return the x offset of the virtual root relative to the root
     |      window of the screen of this widget.
     |  
     |  winfo_vrooty(self)
     |      Return the y offset of the virtual root relative to the root
     |      window of the screen of this widget.
     |  
     |  winfo_width(self)
     |      Return the width of this widget.
     |  
     |  winfo_x(self)
     |      Return the x coordinate of the upper left corner of this widget
     |      in the parent.
     |  
     |  winfo_y(self)
     |      Return the y coordinate of the upper left corner of this widget
     |      in the parent.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from tkinter.Misc:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.Pack:
     |  
     |  forget = pack_forget(self)
     |  
     |  info = pack_info(self)
     |  
     |  pack = pack_configure(self, cnf={}, **kw)
     |  
     |  pack_configure(self, cnf={}, **kw)
     |      Pack a widget in the parent widget. Use as options:
     |      after=widget - pack it after you have packed widget
     |      anchor=NSEW (or subset) - position widget according to
     |                                given direction
     |      before=widget - pack it before you will pack widget
     |      expand=bool - expand widget if parent size grows
     |      fill=NONE or X or Y or BOTH - fill widget if widget grows
     |      in=master - use master to contain this widget
     |      in_=master - see 'in' option description
     |      ipadx=amount - add internal padding in x direction
     |      ipady=amount - add internal padding in y direction
     |      padx=amount - add padding in x direction
     |      pady=amount - add padding in y direction
     |      side=TOP or BOTTOM or LEFT or RIGHT -  where to add this widget.
     |  
     |  pack_forget(self)
     |      Unmap this widget and do not use it for the packing order.
     |  
     |  pack_info(self)
     |      Return information about the packing options
     |      for this widget.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.Place:
     |  
     |  place = place_configure(self, cnf={}, **kw)
     |  
     |  place_configure(self, cnf={}, **kw)
     |      Place a widget in the parent widget. Use as options:
     |      in=master - master relative to which the widget is placed
     |      in_=master - see 'in' option description
     |      x=amount - locate anchor of this widget at position x of master
     |      y=amount - locate anchor of this widget at position y of master
     |      relx=amount - locate anchor of this widget between 0.0 and 1.0
     |                    relative to width of master (1.0 is right edge)
     |      rely=amount - locate anchor of this widget between 0.0 and 1.0
     |                    relative to height of master (1.0 is bottom edge)
     |      anchor=NSEW (or subset) - position anchor according to given direction
     |      width=amount - width of this widget in pixel
     |      height=amount - height of this widget in pixel
     |      relwidth=amount - width of this widget between 0.0 and 1.0
     |                        relative to width of master (1.0 is the same width
     |                        as the master)
     |      relheight=amount - height of this widget between 0.0 and 1.0
     |                         relative to height of master (1.0 is the same
     |                         height as the master)
     |      bordermode="inside" or "outside" - whether to take border width of
     |                                         master widget into account
     |  
     |  place_forget(self)
     |      Unmap this widget.
     |  
     |  place_info(self)
     |      Return information about the placing options
     |      for this widget.
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.Grid:
     |  
     |  grid = grid_configure(self, cnf={}, **kw)
     |  
     |  grid_configure(self, cnf={}, **kw)
     |      Position a widget in the parent widget in a grid. Use as options:
     |      column=number - use cell identified with given column (starting with 0)
     |      columnspan=number - this widget will span several columns
     |      in=master - use master to contain this widget
     |      in_=master - see 'in' option description
     |      ipadx=amount - add internal padding in x direction
     |      ipady=amount - add internal padding in y direction
     |      padx=amount - add padding in x direction
     |      pady=amount - add padding in y direction
     |      row=number - use cell identified with given row (starting with 0)
     |      rowspan=number - this widget will span several rows
     |      sticky=NSEW - if cell is larger on which sides will this
     |                    widget stick to the cell boundary
     |  
     |  grid_forget(self)
     |      Unmap this widget.
     |  
     |  grid_info(self)
     |      Return information about the options
     |      for positioning this widget in a grid.
     |  
     |  grid_remove(self)
     |      Unmap this widget but remember the grid options.
     |  
     |  location = grid_location(self, x, y)
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.XView:
     |  
     |  xview(self, *args)
     |      Query and change the horizontal position of the view.
     |  
     |  xview_moveto(self, fraction)
     |      Adjusts the view in the window so that FRACTION of the
     |      total width of the canvas is off-screen to the left.
     |  
     |  xview_scroll(self, number, what)
     |      Shift the x-view according to NUMBER which is measured in "units"
     |      or "pages" (WHAT).
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from tkinter.YView:
     |  
     |  yview(self, *args)
     |      Query and change the vertical position of the view.
     |  
     |  yview_moveto(self, fraction)
     |      Adjusts the view in the window so that FRACTION of the
     |      total height of the canvas is off-screen to the top.
     |  
     |  yview_scroll(self, number, what)
     |      Shift the y-view according to NUMBER which is measured in
     |      "units" or "pages" (WHAT).

FUNCTIONS
    log10(x, /)
        Return the base 10 logarithm of x.

DATA
    ACTIVE = 'active'
    ALL = 'all'
    ANCHOR = 'anchor'
    ARC = 'arc'
    BASELINE = 'baseline'
    BEVEL = 'bevel'
    BOTH = 'both'
    BOTTOM = 'bottom'
    BROWSE = 'browse'
    BUTT = 'butt'
    CASCADE = 'cascade'
    CENTER = 'center'
    CHAR = 'char'
    CHECKBUTTON = 'checkbutton'
    CHORD = 'chord'
    COMMAND = 'command'
    CURRENT = 'current'
    DISABLED = 'disabled'
    DOTBOX = 'dotbox'
    E = 'e'
    END = 'end'
    EW = 'ew'
    EXCEPTION = 8
    EXTENDED = 'extended'
    FALSE = 0
    FIRST = 'first'
    FLAT = 'flat'
    GROOVE = 'groove'
    HIDDEN = 'hidden'
    HORIZONTAL = 'horizontal'
    INSERT = 'insert'
    INSIDE = 'inside'
    LAST = 'last'
    LEFT = 'left'
    MITER = 'miter'
    MOVETO = 'moveto'
    MULTIPLE = 'multiple'
    N = 'n'
    NE = 'ne'
    NO = 0
    NONE = 'none'
    NORMAL = 'normal'
    NS = 'ns'
    NSEW = 'nsew'
    NUMERIC = 'numeric'
    NW = 'nw'
    OFF = 0
    ON = 1
    OUTSIDE = 'outside'
    PAGES = 'pages'
    PIESLICE = 'pieslice'
    PROJECTING = 'projecting'
    RADIOBUTTON = 'radiobutton'
    RAISED = 'raised'
    READABLE = 2
    RIDGE = 'ridge'
    RIGHT = 'right'
    ROUND = 'round'
    S = 's'
    SCROLL = 'scroll'
    SE = 'se'
    SEL = 'sel'
    SEL_FIRST = 'sel.first'
    SEL_LAST = 'sel.last'
    SEPARATOR = 'separator'
    SINGLE = 'single'
    SOLID = 'solid'
    SUNKEN = 'sunken'
    SW = 'sw'
    TOP = 'top'
    TRUE = 1
    TclVersion = 8.6
    TkVersion = 8.6
    UNDERLINE = 'underline'
    UNITS = 'units'
    VERTICAL = 'vertical'
    W = 'w'
    WORD = 'word'
    WRITABLE = 4
    X = 'x'
    Y = 'y'
    YES = 1
    pi = 3.141592653589793
    wantobjects = 1

FILE
    /home/bobl/Dropbox/cpts481/u15_tkinter/applications/pyckett.py


In [ ]: