The New - Tk::Gripes: Bindings
The New  
Tuesday, 02 September 2014
Main Menu
Perl/Tk Widgets
Useful Tips
Contact Us
Login Form


Remember me
Forgotten your password?
No account yet? Create one
Tk::Gripes: Bindings PDF Print E-mail
Written by Ala Qumsieh   

Tk::Gripes is a series of articles that discuss things I don't like about Tk in general and pTk in particular.

Today's article will discuss Tk's binding mechanism via the Tk::bind method. I will mention what I think is wrong with Tk::bind, and will offer an alternative that addresses my concerns.

In my opinion, there is a major drawback to Tk::bind that often results in unnecessary complications in the code, and that is:

It is not possible to assign multiple callbacks to any single event. 

This might not seem like a big deal, but it opens up a can of worms by requiring that the callback be strongly coupled to potentially many widgets that otherwise don't need to know about each other. This can lead to major (and unnecessary) complications in your code, especially if your application is rather large and complex.

To make things clearer, let me illustrate with a simple example.


Suppose that you have a Tk::List object that contains a list of all the jpeg file names in the current directory. When a user clicks on a file name, the application displays the image with the following statistics below it:

  • file name
  • file size (in bytes)
  • file creation time

This is easy to implement by using two labels:

  • $imageL: used to display the image.
  • $statsL: used to show the statistics.

The callback function might look something like this:

$list->bind('<1>' => \&updateImageAndStatsLabels);  

sub updateImageAndStatsLabels {
   # get file name.
   my $file = $list->get($list->curselection);
   # create a photo object and update the $imageL label
   $mw->Photo($file, -file => $file);
   $imageL->configure(-image => $file);

   # get the file stats, and update the $statsL label
   my ($size, $mtime) = (stat $file)[7, 9];
   my $age = localtime($mtime);
   $statsL->configure(-text => "$file\n$size bytes\n$age");
Admittedly, this is a simple example, but I hope that it will get my point across.

Here, we have a single subroutine that needs to do two things that are logically detached from each other, which mixes our program's logic in unnecessary ways. If, for example, we now need the click on our Tk::List to update some other widget, then we would need to hack our subroutine to do it.

The problem is compounded if our Tk::List is part of another mega widget, or is a subclass of Tk::List. Then, depending on how well the module is written, you might need to resort to ugly tricks like saving off a reference to the currently binded subroutine, and then modifying it:

my $oldBind = $list->bind('<1>');
$list->bind('<1>' => sub {
    $oldBind->() if $oldBind && ref($oldBind) eq 'CODE';

It would really be much easier if Tk used a signal/slot mechanism (like Qt and other GUI toolkits) that simply connects subroutines to events, such that they are called when the event occurs. Something like this:

  my $id1 = $list->connect('<1>' => \&updateImageLabel);
  my $id2 = $list->connect('<1>' => \&updateStatsLabel);
This nicely decouples our program's logic in a more modular way. We don't really care whether some other part of the program (or even perhaps another module) has another callback connected to the mouse click event. When a mouse button is clicked, all the connected callbacks are executed in the order they were connected. Furthermore, the connect() method can be made to return a unique ID that can be used to disconnect a callback:
Subsequent mouse clicks will not call updateImageLabel(). This is much simpler to code, and offers a more modular design paradigm.

To facilitate this, I created the Tk::SignalSlot module that exports these methods into the Tk:: namespace, so they can be used exactly as described above. Complete documentation can be found here.

Feel free to contact me for any comments or issues with the module.


Passing Ev() params to Tk::SignalSlot::c
Written by capacollo on 2006-03-29 11:13:49
Hi Ala, 
I really found your package quite useful as it makes my code much cleaner. I just started using this package and Ill be using it for all future work. 
I currently have a problem passing Ev() params as it passes the blessed Ev() object rather than the value it represents. What would be the best way to resolve this? I guess one could simply get all possible Ev() return values and before calling the code replace the necessary parameters to the code but I havent tried it. 
As well I found it might be useful to add a new function 'reconnect' that resinserts the callback code into the event array. Admittingly I havent implemented it yet but looking at the code it should be simple enough to save the callback in the id array and reinsert back into the event array when requested. I wondered if something like that would be useful in general.  
Re: Passing Ev() params to Tk::SignalSlo
Written by ala on 2006-03-30 07:01:31
I haven't tried using Ev() with Tk::SignalSlot. But, you can use $Tk::event inside your callback instead. For example, instead of passing Ev('x') as an argument, you can directly use $Tk::event->x, which will give you the same value. 
As for the reconnect() feature, it should be easy to implement. I'll add it in the next version of the module.
Written by capacollo on 2006-03-30 07:41:57
Thanks for the help. :grin  
As well, an added feature that may be useful may be to associate callbacks with tags along so callbacks can be disconnected/reconnected in groups or with their id. Im currently doing something like this external to the module to enable/disable button events depending on which selection the user chooses.  
Use redirection
Written by chrislee35 on 2006-05-05 19:22:06
You can make your own callback redirection architecture if you wanted. So, make all widget callbacks point to a central callback and then install redirections from there. This adds a non-standard API layer, but if it's done flexible enough, it might catch on. But it seems like overkill to fix a simple problem. 
rough concept example (not usable code) 
sub masterCallback { 
foreach my $cb (@{$cbs{$widget}->{$event}}) { 

sub installCB { 
$cbs{$widget}->{$event} = [] unless defined $cbs{$widget}->{$event}; 

Only registered users can write comments.
Please login or register.

Powered by AkoComment 2.0!

Next >
Top! Top!