Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

How to destroy and re-create bound widgets

by Phinix (Acolyte)
on Nov 03, 2012 at 06:10 UTC ( #1002083=perlquestion: print w/ replies, xml ) Need Help??
Phinix has asked for the wisdom of the Perl Monks concerning the following question:

I am having difficulty attempting to destroy widgets I have bound to mouse click events. All the documentation I read seems to indicate the problem is likely related to internal tables maintained for destroyed widgets, however none seem to elaborate on how to clear them, or how to "detach" or "unbind" events from widgets so they may be safely destroyed and re-created from scratch.

The program I am writing reaches a point where it needs to delete all dynamically created "buttons" (widgets with mouse binds on them) and then re-create the first of them by invoking a sub-routine to basically start over. Below is a sample of the code, to show the basic concept

Basically what it does is set up the container structure, then invoke &scan1start; which is a subroutine to build the first fake widget button, and bind it to right and left mouse (not the reset button, leave that alone for now!) I am using canvas as the bind widget because I want to give it a background image without inheriting the functionality of a regular button.

Clicking this first fake widget button button runs the next two subroutines to create two more buttons (and also changes the first button's color). Clicking either of these new buttons will change their color, and right-clicking will change it back. Right-clicking the first button hides the two higher buttons, but only if they are "off" (black in this example.)

You can test all of this and it works fine, until you click the reset button. This button is supposed to destroy all the buttons, then run the initial subroutine to re-create the first button and set up it's initial bindings again, basically starting all over. However, as you can see perl complains about a bound event and the button is never re-created.
#!/usr/bin/perl use warnings; use Tk; use strict; my $mw = new MainWindow; $mw-> geometry('640x480'); $mw-> resizable( 0, 0 ); my $numscan1 = 0; my $numscan2 = 0; my $numscan3 = 0; my $leftframe = $mw-> Frame(); $leftframe->form(-left => '%0', -right => '%25', -bottom => '%99', -to +p => '%1'); my $resetb = $leftframe-> Button(-text => 'Reset', -command => \&reset + )->form(-left => '%50', -top => '%50'); my $rightframe = $mw-> Frame(-relief => 'groove', -borderwidth => 1); $rightframe-> form(-left => $leftframe, -right => '%99', -bottom => '% +99', -top => '%1'); my $maincanvas = $rightframe-> Canvas(-background => 'blue', -highligh +tthickness => 0); $maincanvas-> form(-left => '%1', -right => '%99', -bottom => '%99', - +top => '%1'); #Here are some fake buttons using sub-canvas: my $scan1 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); my $scan2 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); my $scan3 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); &scan1start; MainLoop; sub reset { my @buttons = ($scan1,$scan2,$scan3); foreach (@buttons) { $_-> destroy; } &scan1start; } sub scan1start { if ($numscan1 == 0) { $scan1-> form(-left => '187', -top => '319'); $scan1-> configure(-background => 'black'); $scan1-> Tk::bind('<Button-1>' => sub { $numscan1 = 1; &scan2start; &scan3start; &scan1start; } ); $scan1-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan1 == 1) { $scan1-> form(-left => '187', -top => '319'); $scan1-> configure(-background => 'white'); $scan1-> Tk::bind('<Button-3>' => sub { if (($numscan2 == 0) && ($numscan3 == 0)) { $scan2->formForget; $scan3->formForget; $numscan1 = 0; &scan1start; } } ); $scan1-> Tk::bind('<Button-1>' => sub { }); } } sub scan2start { if ($numscan2 == 0) { $scan2-> form(-left => '102', -top => '211'); $scan2-> configure(-background => 'black'); $scan2-> Tk::bind('<Button-1>' => sub { $numscan2 = 1; &scan2start; } ); $scan2-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan2 == 1) { $scan2-> form(-left => '102', -top => '211'); $scan2-> configure(-background => 'white'); $scan2-> Tk::bind('<Button-3>' => sub { $numscan2 = 0; &scan2start; } ); $scan2-> Tk::bind('<Button-1>' => sub { }); } } sub scan3start { if ($numscan3 == 0) { $scan3-> form(-left => '202', -top => '211'); $scan3-> configure(-background => 'black'); $scan3-> Tk::bind('<Button-1>' => sub { $numscan3 = 1; &scan3start; } ); $scan3-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan3 == 1) { $scan3-> form(-left => '202', -top => '211'); $scan3-> configure(-background => 'white'); $scan3-> Tk::bind('<Button-3>' => sub { $numscan3 = 0; &scan3start; } ); $scan3-> Tk::bind('<Button-1>' => sub { }); } }

Sorry if this seems similar to another question I asked recently about canvas items, however that example was somewhat unclear and presented in a misleading context.

This is specifically an issue with unbinding/destroying bound widgets. I have attempted it with numerous other widget types (buttons, images), inside numerous container types and had the same result with all of them.

I know I am not the most well-versed in the language but this particular snag is really starting to recede my hairline!

EDIT: Just thought I would add, this same "command bound to event" error crops up when I attempt to use ->delete('all') and formForget instead of destroy. Very very frustrating.

Comment on How to destroy and re-create bound widgets
Download Code
Re: How to destroy and re-create bound widgets
by zentara (Archbishop) on Nov 03, 2012 at 09:30 UTC
    I ran your code, and saw one problem immediately.
    #Here are some fake buttons using sub-canvas: my $scan1 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); my $scan2 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); my $scan3 = $rightframe-> Canvas(-width => 64, -height => 54, -highlig +htthickness => 0); &scan1start; MainLoop; sub reset { my @buttons = ($scan1,$scan2,$scan3); foreach (@buttons) { $_-> destroy; } #&scan1start; } sub scan1start { if ($numscan1 == 0) { $scan1-> form(-left => '187', -top => '319'); $scan1-> configure(-background => 'black'); $scan1-> Tk::bind('<Button-1>' => sub { .... etc .... etc .... etc }
    You define $scan1, $scan2, $scan3 globally, then destroy them in your reset sub. Then without recreating them, you try to access them in &scan1start.

    Although it dosn't address all your issues, you might start by tring something like this:

    #!/usr/bin/perl use warnings; use Tk; use strict; my $mw = new MainWindow; $mw-> geometry('640x480'); $mw-> resizable( 0, 0 ); my $numscan1 = 0; my $numscan2 = 0; my $numscan3 = 0; my $leftframe = $mw-> Frame(); $leftframe->form(-left => '%0', -right => '%25', -bottom => '%99', -to +p => '%1'); my $resetb = $leftframe-> Button(-text => 'Reset', -command => \&reset + )->form(-left => '%50', -top => '%50'); my $rightframe = $mw-> Frame(-relief => 'groove', -borderwidth => 1); $rightframe-> form(-left => $leftframe, -right => '%99', -bottom => '% +99', -top => '%1'); my $maincanvas = $rightframe-> Canvas(-background => 'blue', -highligh +tthickness => 0); $maincanvas-> form(-left => '%1', -right => '%99', -bottom => '%99', - +top => '%1'); #Here are some fake buttons using sub-canvas: #my $scan1 = $rightframe-> Canvas(-width => 64, -height => 54, -highli +ghtthickness => 0); #my $scan2 = $rightframe-> Canvas(-width => 64, -height => 54, -highli +ghtthickness => 0); #my $scan3 = $rightframe-> Canvas(-width => 64, -height => 54, -highli +ghtthickness => 0); my $scan1; my $scan2; my $scan3; &scan1start; MainLoop; sub reset { my @buttons = ($scan1,$scan2,$scan3); foreach (@buttons) { $_-> destroy; } &scan1start; } sub scan1start { $scan1 = $rightframe-> Canvas(-width => 64, -height => 54, -highlight +thickness => 0); $scan2 = $rightframe-> Canvas(-width => 64, -height => 54, -highlight +thickness => 0); $scan3 = $rightframe-> Canvas(-width => 64, -height => 54, -highlight +thickness => 0); if ($numscan1 == 0) { $scan1-> form(-left => '187', -top => '319'); $scan1-> configure(-background => 'black'); $scan1-> Tk::bind('<Button-1>' => sub { $numscan1 = 1; &scan2start; &scan3start; &scan1start; } ); $scan1-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan1 == 1) { $scan1-> form(-left => '187', -top => '319'); $scan1-> configure(-background => 'white'); $scan1-> Tk::bind('<Button-3>' => sub { if (($numscan2 == 0) && ($numscan3 == 0)) { $scan2->formForget; $scan3->formForget; $numscan1 = 0; &scan1start; } } ); $scan1-> Tk::bind('<Button-1>' => sub { }); } } sub scan2start { if ($numscan2 == 0) { $scan2-> form(-left => '102', -top => '211'); $scan2-> configure(-background => 'black'); $scan2-> Tk::bind('<Button-1>' => sub { $numscan2 = 1; &scan2start; } ); $scan2-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan2 == 1) { $scan2-> form(-left => '102', -top => '211'); $scan2-> configure(-background => 'white'); $scan2-> Tk::bind('<Button-3>' => sub { $numscan2 = 0; &scan2start; } ); $scan2-> Tk::bind('<Button-1>' => sub { }); } } sub scan3start { if ($numscan3 == 0) { $scan3-> form(-left => '202', -top => '211'); $scan3-> configure(-background => 'black'); $scan3-> Tk::bind('<Button-1>' => sub { $numscan3 = 1; &scan3start; } ); $scan3-> Tk::bind('<Button-3>' => sub { }); } elsif ($numscan3 == 1) { $scan3-> form(-left => '202', -top => '211'); $scan3-> configure(-background => 'white'); $scan3-> Tk::bind('<Button-3>' => sub { $numscan3 = 0; &scan3start; } ); $scan3-> Tk::bind('<Button-1>' => sub { }); } }
    P. S. I also notice that you have recursive subroutine called in scan1start(). The way I set it up in the example, or in any Tk script, a recursive widget-creating sub is bound to cause havoc. By the way, why are you not using the createWindow method to embed those sub canvases?

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh

      I did try createWindow... I'm still learning so I've been just experimenting and doing research trying to get the syntax right. Perhaps though you could explain why doing this:

      my $canvaswindow = $canvas->createWindow(0,0, -window => $widget); $canvaswindow->Tk::bind('<Button-1>' => sub { &dostuff0 });

      ...gives me an error saying: "Global symbol "$canvaswindow" requires explicit package name at line 3."

      I have created $widget before attempting this, of various types. Is it not possible to create custom event binds on canvas objects? If so, that is why I do not use createWindow. If it is possible I would love to know how.

      EDIT: I was able to get things working after I realized that the createWindow method isn't so much creating a new object that gets a reference but rather more like saying "put me here" to a previously created object template.

      So, the above instead becomes:

      $canvas->createWindow(0,0, -window => $widget); $widget->Tk::bind('<Button-1>' => sub { &dostuff0 });
      I'll post a full working example in a bit once I finish the translation. Thanks for all the helpful pointers and syntax examples though!
        It sounds like you, as a beginner, my want to reconsider your design. The first thing I have to say, is why do you need the overlapping secondary sub-canvases? If you can make something on your sub-canvas, you can do the same thing on the underlying canvas. You can still put a background image on the main canvas, then instead of overlaying other canvases, why not create rectangular regions on the base canvas? In any event, look at this example:
        #!/usr/bin/perl use warnings; use strict; use Tk; my $mw = new MainWindow; my $main = $mw -> Scrolled("Canvas",-scrollbars=>'e')-> pack(-side =>'left', -fill => 'both', -expand => 1); my $canvas = $main->Subwidget("canvas"); Tk::bind($canvas, '<MouseWheel>', [ sub {$canvas->yview('scroll', -($_[1] / 120) * 3,'units')}, Ev('D')]); my $see = "\nHaaa, See that and scroll!"; my $info = $main->createText(180, 15, -text => "$see", -font => 'Arial 11 bold' , -fill => 'red'); $main->configure(-scrollregion => [0,0,2000 , 2000]); my $canvas_cube = $main->Canvas(-width => 45, -height => 45, -background => 'green'); my $canvasWindow = $main->createWindow(75,75, -window => $canvas_cube) +; Tk::bind( $canvas_cube, '<Enter>' => sub { print "Enter\n"; }); Tk::bind( $canvas_cube, '<Leave>' => sub { print "Leave\n"; }); Tk::bind( $canvas_cube, '<Button-1>' => sub { print "1\n"; }); $canvas->focus; MainLoop;

        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku ................... flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1002083]
Approved by Athanasius
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2014-12-27 12:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (177 votes), past polls