The New - Tk::Gripes: Bindings
The New  
Friday, 22 August 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.

--AlaWrite Comment (4 Comments)
Next >
Top! Top!