tkinter: identifying callback button?

Randall Hopper aa8vb at yahoo.com
Thu Sep 9 09:32:06 EDT 1999


Lewis:
 |I have a bunch of (named) buttons which call the same callback.
 |I've been trying to figure out how (or if) I can get the callback
 |function to be able to identify which one.
 |
 |    for x in range(0,5):
 |        b = Button(frame,text='#%s' % x,command=self.xxx, name='b%s' % x)
 |        b.pack()

I asked this several months ago.  Here are the answer highlights:

  1) Use a generic callable object as a wrapper (__call__)
  2) Use a specific callback class object
  3) Use a lambda or function (written in-line or generated by a function)
  4) Write python call code as a string and "exec" it in a lambda or function

They sound tricky, but they're really not.  The attached simple code shows
how to use these techniques for both full and partial resolution of
callback arguments at registration time.

(Note: "partial" means specifying some of the arguments at registration
time, while leaving others until invocation time -- e.g. for cases where
your Tk callback takes arguments).

My personal favorite for full resolution is #1 (callable object).  Clean
appearance, and no custom code to write per-callback to make it fly.

Randall



-------------- next part --------------
#!/usr/bin/env python
#
#  closure.py  -  Demonstrate various ways to generate and use
#                 parameterized callback functions.
#
#                 Examples are shown for both total and partial resolution
#                 of callback arguments at registration time.
#
#                 Techniques:  lambdas, callable objects, deferred execution.
#

class Call:
  """Instances of this class store a function as well as a list of arguments.
     When they are called, the function will be called together with the
     arguments used for creating the instance.
     Slightly different than lambda, but nicer syntax."""
  def __init__ (self, func, *args):
    self.func = func             # save the function (or bound method, or ...)
    self.args = args             # save the arguments to use 
  def __call__ (self):
    apply (self.func, self.args) # call function, using args as arguments.

class CallbackHookFull:
  """
  Demonstrates various ways to do full resolution of callback arguments
  at registration time, and return/invoke the callback.
  """
  
  def ExecCommand(self,command):
    exec command

  def DoIt(self,color):
    """ The callback we eventually want to end up calling, but with no args. """
    print color

  def Invoke(self):
    self.doit_cb()

  #---------------------------------------------------------------------
    
  def Register1( self, color ):
    """ Generate a callable class that'll invoke the callback w/ args """
    self.doit_cb = Call( self.DoIt, color )

  #---------------------------------------------------------------------
    
  class ColorCallback:
    def __init__( self, obj, color ):
        self.obj   = obj
        self.color = color
    def callback( self ):
        self.obj.DoIt( self.color )

  def Register2( self, color ):
    """ Wrap the args in a class, and have a class method with the appropriate
        signature do the calling.  """
    self.doit_cb = CallbackHookFull.ColorCallback( self, color ).callback

  #---------------------------------------------------------------------
    
  def Register3a( self, color ):
    """ Generate a closure which is a wrapper around the invocation w/ args """
    self.doit_cb = lambda callback=self.DoIt, color=color: \
      callback(color)

  #---------------------------------------------------------------------
    
  def Register4a( self, color ):
    self.doit_cb = lambda callback=self.DoIt, color=color: \
      callback(color)

  #---------------------------------------------------------------------
    
  def Register5a( self, color ):
    """ Build a string with the invocation we want, and then build a closure
        that will interpret that string when invoked. """
    self.doit_cb = lambda self=self, command="self.DoIt('"+color+"')": \
      self.ExecCommand(command)

  #---------------------------------------------------------------------

  def BuildCallback3b( self, callback, color ):
    return lambda callback=callback, color=color: callback(color)

  def Register3b(self,color):
    """ Nicer-looking 3a with lambda generated by a function """
    self.doit_cb = self.BuildCallback3b( self.DoIt, color );
  
  #---------------------------------------------------------------------

  def BuildCallback4b( self, callback, color ):
    def _( callback=callback, color=color ): callback(color)
    return _

  def Register4b(self,color):
    """ Same as the 3b, but use def instead of lambda; exactly the same
        because functions are just named lambdas. """
    self.doit_cb = self.BuildCallback3b( self.DoIt, color );
  
  #---------------------------------------------------------------------

  def BuildCallback5b( self, callback, color ):
    return lambda self=self, command=callback+"('"+color+"')" : \
      self.ExecCommand(command)
      
  def Register5b(self,color):
    """ Nicer-looking 5a with lambda generated by a function """
    self.doit_cb = self.BuildCallback5b( "self.DoIt", color )

#===============================================================================

class CallbackHookPartial:
  """
  Demonstrates various ways to do partial resolution of callback arguments
  at registration time (i.e. for cases where the callback takes argument(s) at
  invocation time as well as registration time), and return/invoke the callback.
  """

  def ExecCommand(self,command):
    exec command

  def DoIt(self, color, what_to_paint):
    """ The callback we eventually want to end up calling, but with one arg
        (what_to_paint) instead of two. """
    print what_to_paint + " = " + color

  def Invoke(self):
    self.doit_cb( "walls" )

  #---------------------------------------------------------------------
    
  class ColorCallback:
    def __init__( self, callback, color ):
        self.cb    = callback
        self.color = color
    def callback( self, what_to_paint ):
        self.cb( self.color, what_to_paint )

  def Register2( self, color ):
    """ Wrap the args in a class, and have a class method with the appropriate
        signature be the callback.  """
    self.doit_cb = CallbackHookPartial.ColorCallback(self.DoIt, color).callback


  #---------------------------------------------------------------------
    
  def Register3a( self, color ):
    """ Generate a closure which is a wrapper around the invocation w/ args """
    self.doit_cb = lambda what_to_paint, callback=self.DoIt, color=color: \
      callback(color, what_to_paint)

  #---------------------------------------------------------------------

  def BuildCallback3b( self, callback, color ):
    return lambda what_to_paint, callback=self.DoIt, color=color: \
      callback(color, what_to_paint)

  def Register3b(self,color):
    """ Nicer-looking 3a with lambda generated by a function """
    self.doit_cb = self.BuildCallback3b( self.DoIt, color );
  
  #---------------------------------------------------------------------

  def BuildCallback4b( self, callback, color ):
    def _( what_to_paint, callback=self.DoIt, color=color ): \
      callback(color, what_to_paint)
    return _

  def Register4b(self,color):
    """ Same as the 3b, but use def instead of lambda; exactly the same
        because functions are just named lambdas. """
    self.doit_cb = self.BuildCallback3b( self.DoIt, color );


if __name__ == "__main__":
  cb = CallbackHookFull()
  cb.Register1('orange')
  cb.Invoke()
  cb.Register2('white')
  cb.Invoke()
  cb.Register3a('blue')
  cb.Invoke()
  cb.Register5a('red')
  cb.Invoke()
  cb.Register3b('green')
  cb.Invoke()
  cb.Register4b('purple')
  cb.Invoke()
  cb.Register5b('yellow')
  cb.Invoke()

  print "--------"

  cb = CallbackHookPartial()
  cb.Register2('white')
  cb.Invoke()
  cb.Register3a('blue')
  cb.Invoke()
  cb.Register3b('green')
  cb.Invoke()
  cb.Register4b('purple')
  cb.Invoke()


More information about the Python-list mailing list