Software Design Spring 2007 Exam next Monday: 1) open book, open notes, open laptop, closed Python. 2) one goal is to exercise your facility with the documentation. make bookmarks, bring your EABB, print some FUPs Gather/scatter dictionaries --------------------------- Last time we saw gather/scatter tuples. In a parameter list, the ** operator gathers the keyword arguments: def foo(**kwds): print kwds foo(a=1, b=2, name='allen') In an argument list, the ** operator scatters a dictionary: d = dict(a=1, b=2, name='allen') foo(d) # error... foo(**d) # works! I use this frequently to pass options to Tkinter: In pack_demo.py: options = dict(side=LEFT, padx=10, pady=10) b1 = g.bu(text='OK', **options) b2 = g.bu(text='Cancel', **options) b3 = g.bu(text='Help', **options) Override/underride ------------------ The widget wrapper functions in Gui.py use ** to pass around options: # canvas def ca(self, *args, **options): underride(options, fill=BOTH, expand=1) return self.widget(GuiCanvas, *args, **options) What is underride all about? Here is the definition: def underride(d, **kwds): """Add kwds to the dictionary only if they are not already set""" for key, val in kwds.iteritems(): if key not in d: d[key] = val def override(d, **kwds): """Add kwds to the dictionary even if they are already set""" d.update(kwds) The factory pattern ------------------- While we are in the neighborhood, you might notice that all of the widget wrapper classes call Gui.widget and pass a constructor as an argument: def widget(self, constructor, *args, **options): """this is the mother of all widget constructors. the constructor argument is the function that will be called to build the new widget. args is rolled into options, and then options is split into widget option, pack options and grid options """ options.update(dict(zip(Gui.argnames,args))) widopt, packopt, gridopt = split_options(options) # make the widget and either pack or grid it widget = constructor(self.frame, **widopt) if hasattr(self.frame, 'gridding'): self.grid(widget, **gridopt) else: widget.pack(**packopt) return widget According to the Gang of Four, the Factory pattern "provides an interface for creating families of related or dependent objects without specifying their concrete classes (at compile time)" Lab Exercise 9 Solutions (sd_hw09_soln.py) ------------------------ 1) IntVar and StringVar If you are working with checkbuttons and radio buttons, you have two choices: a) associate a Tkinter Var object with the widget(s) Tk handles all the interactions, both ways! b) use another kind of variable and handle the interactions yourself Here's an example using IntVar and StringVar: class MyTurtle(Turtle): def __init__(self, world, delay=0.5): Animal.__init__(self, world) self.r = 5 self.heading = 0 self.pen = IntVar() self.pen.set(1) self.color = StringVar() self.color.set('red') self.delay = delay self.draw() world.register(self) And then in the TurtleControl panel: w.fr(TOP, fill=X, expand=1) w.cb(LEFT, text='Pen', variable=self.turtle.pen) colors = 'red', 'orange', 'yellow', 'green', 'blue', 'violet' for color in colors: w.rb(anchor=W, text=color, command=self.turtle.redraw, variable=self.turtle.color, value=color) I don't know why these variables don't take an initial value as a parameter. 2) One more example of a Callable in the TurtleControl panel: w.gr(2, side=LEFT) w.la(TOP, text='Line Width') b1 = w.bu(TOP, text='up') w.en_w = w.en(TOP, fill=X, expand=1, width=5, text='1') b2 = w.bu(TOP, text='down') b1.configure(command=Callable(incr_entry, w.en_w, 1)) b2.configure(command=Callable(incr_entry, w.en_w, -1)) This example also demonstrates grid layout, which can be easier to use and neater looking that a pack layout. When widgets refer to each other, the order of creation can be tricky. In this case I had to a) create the buttons b) create the entry c) configure the buttons to refer to the entry But at least I could use the same function for both buttons: def incr_entry(entry, val): old = int(entry.get()) new = old + val entry.delete(0, END) entry.insert(END, str(new)) Binding and event-handling -------------------------- A binding is a relationship between a widget, an event and an event-handler. When an event (mouse-click, key press, etc.) occurs, Tk decides which widget gets the event, and which of the widget's event-handlers should be invoked. Example: def setup(self): self.canvas = self.ca(...) self.canvas.bind('', canvas_click) The event handler can be the name of a function, a bound method, or a Callback object. You can also bind items within a canvas. Here's an implementation of drag and drop: class Item: """an Item object represents a canvas item. When you create a canvas item, Tkinter returns an integer 'tag' that identifies the new item. To perform an operation on the item, you invoke a method on the canvas and pass the tag as a parameter. The Item class makes this interface more object-oriented: each Item object contains a canvas and a tag. When you invoke methods on the Item, it invokes methods on its canvas. """ def __init__(self, canvas, tag): self.canvas = canvas self.tag = tag self.bind('', self.select) self.bind('', self.drag) self.bind('', self.release) def bind(self, event, callback): """this method applies bindings to canvas items (not the whole canvas)""" self.canvas.tag_bind(self.tag, event, callback) def move(self, dx, dy): """move this item by (dx, dy) in PIXEL coordinates""" self.canvas.move(self.tag, dx, dy) # the following event handlers take an event object as a parameter def select(self, event): print 'Item.select', self self.config(fill='red') self.dragx = event.x self.dragy = event.y def drag(self, event): """move this item using the pixel coordinates in the event object.""" # see how far we have moved dx = event.x - self.dragx dy = event.y - self.dragy # save the current drag coordinates self.dragx = event.x self.dragy = event.y # move the item self.move(dx, dy) def release(self, event): print 'Item.release', self self.config(fill='blue')