This section extends the material on lambda functions and thepartialclass in Chapter 11.2.1. Our current project is to make the first step towards a fancy list widget. We want to embed text in a text widget, and when the mouse is over a part of the text, the text’s background changes color, and the text itself is modified. Figure 11.5 shows an example. Every time the mouse is over “You have hit me ...” the background color of this text changes to red and the counter is increased by 1. The “Hello, World!” text changes its background to blue when the mouse cursor is over the text. The complete code is found intext_tag1.pyandtext_tags2.pyin thesrc/py/guidirectory (the two versions employfunctools.partial objects and lambda functions in event bindings).
Fig. 11.5.Example on binding mouse events to modifications of a text in a text widget.
The realization of such a GUI is performed by – a text widget,
– marking parts of the text with text widget tags,
– binding mouse movements over a tag with a function call.
We first create a plain Tk text widget, with width 20 characters and height 5 lines. Too long lines should be broken between words (wrap=’word’):
self.hwtext = Text(parent, width=20, height=5, wrap=’word’) We can insert text in this widget and mark the text with a tag:
self.hwtext.insert(’end’, # insert text after end of text
’Hello, World!’, # text
’tag1’) # name of tag bound to this text self.hwtext.insert(’end’,’\n’)
self.hwtext.insert(’end’,"You haven’t hit me yet!", ’tag2’)
Let nowtag1 get a blue background when the mouse is over the text. This means that we bind the mouse event<Enter>of this tag to a call to the text widget’stag_configurefunction. This can be accomplished using thepartial class from page 543:
from functools import partial
self.hwtext.tag_bind(’tag1’,’<Enter>’,
partial(self.configure, ’tag1’, ’blue’)) def configure(self, tag, bg, event):
self.hwtext.tag_configure(tag, background=bg)
In the calls toconfigure, the partialobject will first use the two positional arguments given at construction time (’tag1’and’blue’) and then add posi- tional arguments provided in the call (hereevent). Therefore,configuremust take theeventargument afer the tag and color arguments.
We could also use a straight lambda function. That would actually save us from writing theconfigure method since we could let the lambda function’s first argument be anEvent object and then just make the proper call to the desiredself.hwtext.tag_configurefunction:
self.hwtext.tag_bind(’tag1’,’<Enter>’,
lambda event=None, x=self.hwtext:
x.tag_configure(’tag1’,background=’blue’)) This set of keyword arguments may look a bit complicated at first sight.
The event can be bound to a function taking one argument, an event object.
Hence, the event makes a call to some anonymous lambda function (say its name isfunc) likefunc(event). However, inside this stand-alone anonymous function we want to make the call
self.hwtext.tag_configure(’tag1’,background=’blue’)
A problem is that this function has no access to the variable self.hwtext (selfhas no meaning in a global lambda function). The remedy is to declare our anonymous function with an extra keyword argument x, where we can make the right reference to the function to be called as a default value:
546 11. More Advanced GUI Programming def func(event=None, x=self.hwtext):
x.tag_configure(’tag1’,background=’blue’)
In other words, we use the keyword argument x to set a constant needed inside the function. We see that the functools.partial tool is significantly simpler to understand and use.
When the mouse leaves tag1, we reset the color of the background to white, either with
self.hwtext.tag_bind(’tag1’,’<Leave>’,
partial(self.configure, ’tag1’,’white’)) or with
self.hwtext.tag_bind(’tag1’,’<Leave>’,
lambda event=None, x=self.hwtext:
x.tag_configure(’tag1’,background=’white’)) The update oftag2 according to mouse movements is more complicated as we need to call tag_configure, increase the counter, and change the text.
We place the three statements in a method in the GUI class and use a functools.partial instance or a lambda function to call the method. Writ- ing the details of the code is left as an exercise for the reader. Here we shall demonstrate a more advanced solution, namely binding mouse events to functionsdefined at run time.
Consider the following function:
def genfunc(self, tag, bg, optional_code=’’):
funcname = ’%(tag)s_%(bg)s_update’ % vars()
# note: funcname can be as simple as "temp", no unique
# name is needed
code = "def %(funcname)s(event=None):\n"\
" self.hwtext.tag_configure("\
"’%(tag)s’, background=’%(bg)s’)\n"\
" %(optional_code)s\n" % vars()
# run function definition as a python script:
exec code in vars()
# return function from funcname string:
return eval(funcname)
This function builds the code for a new function on the fly, having the name contained in the stringfuncname. The Python code for the function definition is stored in a stringcode. To run this code, i.e., to bring the function definition into play in the script, we run exec code. The thein vars() arguments are required for the code incodeto see theselfobject and other class attributes.
Finally, we return the new function as a function object by letting Python evaluate the stringfuncname.
We can now bind mouse events to a tailored function defined at run time.
This function should change the background color of tag2 as the generated function ingenfunc always do, but in addition we should remove the text of tag2,
i=self.hwtext.index(’tag2’+’.first’) # start index of tag2 self.hwtext.delete(i,’tag2’+’.last’) # delete from i to
# last index of tag2 and then insert new text at the start indexi:
self.hwtext.insert(i, ’You have hit me %d times’ % \ self.nhits_tag2, ’tag2’)
self.nhits_tag2 = self.nhits_tag2 + 1 # "hit me" counter
We include this additional code as a (raw) string and send it as the argument optional_code togenfunc:
self.nhits_tag2 = 0 # count the no of mouse hits on tag2
# (must appear before the func def below)
# define a function self.tag2_enter on the fly:
self.tag2_enter = self.genfunc(’tag2’,
’yellow’, # background color
# add a raw string containing optional Python code:
r"i=self.hwtext.index(’tag2’+’.first’); "\
"self.hwtext.delete(i,’tag2’+’.last’); "\
"self.hwtext.insert(i,’You have hit me "\
"%d times’ % self.nhits_tag2, ’tag2’); "\
"self.nhits_tag2 =self.nhits_tag2 + 1")
self.hwtext.tag_bind(’tag2’, ’<Enter>’, self.tag2_enter)
In a similar way we construct a function to be called when the mouse leaves tag2:
self.tag2_leave = self.genfunc(’tag2’, ’white’)
self.hwtext.tag_bind(’tag2’, ’<Leave>’, self.tag2_leave)