Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear all,

I would like to bind KeyPress events to a Canvas widget; the documentation clearly states that this is possible, but I cannot manage to have it working.
#!/usr/bin/perl -w use strict; use Tk; my $mw = 'MainWindow'->new(); my $cv = $mw->Canvas()->pack(); $cv->Tk::bind( '<KeyPress-a>', sub { print "Ounch!\n" } ); MainLoop();
Note that for ButtonPress events, everything works fine. I have tried the Tk::bind method directly too.

Any ideas ?

Thanks,

Philippe

Replies are listed 'Best First'.
Re: Perl/Tk CanvasBind question
by liverpole (Monsignor) on Oct 20, 2006 at 13:38 UTC
    Let me ask the obvious question:  Why not just bind the keypress to the MainWindow?
    #!/usr/bin/perl -w + use strict; use Tk; + my $mw = 'MainWindow'->new(); + my $cv = $mw->Canvas()->pack(); + $mw->bind('<KeyPress-a>', sub { print "Ounch!\n" } ); + MainLoop();

    Is there really ever a situation where you don't want to invoke the subroutine unless the Canvas is selected?

    Although I understand why you'd want to bind, for example, a mouse event to a Canvas, I've never even thought about binding a keypress to a Canvas, as a keypress feels like a more "global" event.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

      Updated: Minor content edit for grammar - thanks, liverpole

      Is there really ever a situation where you don't want to invoke the subroutine unless the Canvas is selected?

      This is really the key question that you have to answer when doing something like this, and it is important to answer it in terms of the application you are working on. because the answer will vary. Binding to MainWindow can be a great timesaver and make the code less verbose, but it can also have unintended consequences that will bite you if you aren't careful.

      For small applications such as all of the examples in this thread so far, there is no danger. The potential for problems grows with the number of widgets that make up the application. You have to be sure that the binding won't be disruptive if you apply it globally to all the widgets in your MainWindow. Take the following example:

      use strict; use Tk; my $mw = MainWindow->new; my $frame = $mw->Frame->pack; $frame->Entry->pack(-side => 'left'); $frame->Button(-text => "Button")->pack(-side => 'left'); my $cv = $mw->Canvas()->pack(); $mw->bind( '<KeyPress-a>', sub { my $widget = shift; print "$widget: ouch!\n"; }); MainLoop;

      When you apply a binding to a widget, the first argument that the bound sub receives is the widget being bound. If your sub depends on this, it can be a problem. Try using the tab key to traverse from one widget to the next using my example. Each widget, including the MainWindow should all trigger the binding. If the binding is global in nature (perhaps a sequence to exit the application) then maybe it's something you want

      Consider another case, where your Canvas represents a game board of some kind, and you have other widgets outside that board that can accept inputs, such as Entry or Text widgets. You probably want to ensure that a key press in one of these input widgets doesn't inadvertently trigger an action within the game board.

      Another common use is to create a form and then allow for the form to be submitted by pressing the Return key. Something like this, perhaps:

      This form will now trigger a Submit if someone presses the <Return> key or presses the Submit button, except in cases where the Button has focus. In those cases, Submit will be triggered twice. What about if your form has a Text widget in it. A widget in which users could conceivably type the Return key as part of the content. In this case, a submit could be triggered inadvertently.

      The main point in all of this so far, is make sure that binding all the widgets is really what you want to do. Be sure to test to make sure the behavior is what you expect. Any of the pitfalls can be avoided without much difficulty, so long as you are aware of them.

      One case where you might not want global bindings is when Canvas is being used as another widget. A Tree, Listbox, Group of checkbuttons, Notebook, are all possibilities. If you are simulating one of the other widget types you'd probably want to ensure that all the various bindings applied only to the Canvas.

Re: Perl/Tk CanvasBind question
by zentara (Cardinal) on Oct 20, 2006 at 12:16 UTC
    Yeah, it's a tricky problem where the $mw has focus, and you need to set it up so the canvas has focus. Uncomment the various lines below, and play with it to see what is happening.
    #!/usr/bin/perl -w use strict; use Tk; my $mw = 'MainWindow'->new(); my $cv = $mw->Canvas()->pack(); $mw->bind('<Any-Enter>' => sub { $cv->Tk::focus }); #$cv->Tk::focus; $cv->Tk::bind( '<KeyPress-a>', sub { print "Ounch!\n" } ); #$mw->bind( '<KeyPress-a>', sub { print "Ounch!\n" } ); MainLoop();

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: Perl/Tk CanvasBind question
by rcseege (Pilgrim) on Oct 20, 2006 at 10:01 UTC

    Yes, in order for key events to be sent to any widget, that widget must have the current focus. The way this is usually done is by pressing Tab to traverse your application moving from one widget to the next. Once it has focus, your binding should work as expected. You can also force the initial focus by using:

    $can->focusForce;
    Rob