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

I am trying to figure out how to make a Tk::Button change position with a Tk::Table while dragging. My goal is, when you click a button and drag it the button being dragged will push the button under it to the right or to the next row if it is at the right-most column.

I created a little test (slideButton() subroutine) that puts the dragged button in the cell the mouse is hovering over.

Many thanks to thundergnat for help with the findCell() sub...

...Updated code...

I fixed a couple of things in slideButton(). Now when a button is dragged the button it is over moves to the row/col the dragged button was previsouly in, but I can't seem to get the actual button widget to be displayed.
Note this line in slideButton():

$table->put($displacedButton->{'row'},$displacedButton->'col'},$displa +cedButton->cget(-text));
I can display the button's text, but if I remove "->cget(-text)" nothing is displayed... Any ideas?

use warnings; use strict; use Tk; use Tk::Table; use Data::Dumper; my $buttonsPerRow = 5; $buttonsPerRow -= 1; my $row = 1; my $col = 1; my $buttonNum = 0; my @buttons; my $mw = tkinit(); my $menuFrame = $mw->Frame()->pack(-expand=>1,-fill=>'both'); my $buttonFrame = $mw->Frame()->pack(-expand=>1,-fill=>'both'); my $table = $buttonFrame->Table( -columns => 5, -scrollbars => 'se', -fixedrows => 1, )->pack(-expand=>1,-fill=>'both'); my $addButton = $menuFrame->Button( -text => 'Add Button', -command => sub{ $buttons[$buttonNum] = $table->Button(); print "Button $buttonNum->$row:$col\n"; $buttons[$buttonNum]{'row'} = $row; $buttons[$buttonNum]{'col'} = $col; $table->put($row,$col,$buttons[$buttonNum]); configureButton($buttonNum); $col>$buttonsPerRow?$row++:undef; $col>$buttonsPerRow?$col=1:$col++; $buttonNum++; }, )->pack(-side=>'top'); ################################ my $isDragging = 0; #boolean - left mouse button pressed and dragging my $selectedButton = 0; $mw->bind('<Motion>', \&motion); ################################ $mw->MainLoop(); sub configureButton{ my $btnNum = shift; $buttons[$btnNum]->bind('<Enter>', sub{ $selectedButton = $btnNum; } ); $buttons[$btnNum]->configure( -text => "Button $btnNum", -command => sub{ print "Button $btnNum pressed\n"; } ); $buttons[$btnNum]->bind('<ButtonPress-1>', \&buttonPress); $buttons[$btnNum]->bind('<ButtonRelease-1>', \&buttonRelease); } sub motion { my($widget) = @_; my $e = $widget->XEvent; if ($isDragging){ slideButton(findCell($e->X,$e->Y)); } return unless $isDragging; } sub buttonPress { $isDragging = 1; } sub buttonRelease { $isDragging = 0; } sub findCell{ my $tmpX = shift; my $tmpY = shift; my ( $row, $col ) = $table->Posn($table->containing($tmpX,$tmpY)); return ($row,$col,$tmpX,$tmpY); } sub slideButton{ my ($mRow,$mCol,$tmpX,$tmpY) = @_; return unless $mRow; print $buttons[$selectedButton]{'row'} . ":" . $buttons[$selectedB +utton]{'col'} . "\n"; my $displacedButton = $table->containing($tmpX,$tmpY); $displacedButton->{'row'} = $buttons[$selectedButton]{'row'}; $displacedButton->{'col'} = $buttons[$selectedButton]{'col'}; $table->put($displacedButton->{'row'},$displacedButton->{'col'},$d +isplacedButton->cget(-text)); $table->put($mRow,$mCol,$buttons[$selectedButton]); $buttons[$selectedButton]{'row'} = $mRow; $buttons[$selectedButton]{'col'} = $mCol; print "Row:$mRow : Col: $mCol\n"; }

Replies are listed 'Best First'.
Re: How to make a Tk::Button move/slide within a Tk::Table
by kcott (Archbishop) on Feb 08, 2012 at 20:52 UTC

    In motion(), you're calling slideButton() when $isDragging is TRUE. What you want to do is call that function when you've stopped dragging the button. I haven't looked too closely at your code but suspect moving the call to slideButton() from motion() to buttonRelease() might fix the problem (which possibly means you'll no longer need $isDragging or buttonPress() and its associated bind()).

    -- Ken

      The idea is to have the button move while dragging and stay in that location when the button is released.

        My mental visualisation of what you're trying to achieve was clearly wrong. My apologies.

        You'll need to keep track of each cell the button is dragged through. When it enters a new cell, you'll need to blank out the last cell: probably easiest to use a Label without text, e.g. $table->put($r, $c, '').

        You also mentioned you wanted to move any existing button to a new cell ("... push the button under it to the right or to the next row ..."). I think you'll need to capture the return value of $table->put() to achieve this; although there's probably other ways of doing it.

        I hope that's a bit closer to what you're after.

        -- Ken

Re: How to make a Tk::Button move/slide within a Tk::Table
by Anonymous Monk on Feb 08, 2012 at 22:30 UTC

    In general, you can't, they're not designed that way. Once you pack/grid/table your widget, then pack/grid/table is responsible for positioning the widget (managing geometry), just look inside http://cpansearch.perl.org/src/SREZIC/Tk-804.030/Tk/Table.pm

    To be able to visually drag widgets about, directly, you need to use Tk::Canvas or Tk::Zinc (see zinc-demos) or Tk::place ( 15-puzzlegame in the widget demo) or Tk::form ( in widget demo, under Tix Widgets, 'form_mgr' => 'The form geometry manager'),

    Another option, is to create a ghost-image, a faded copy of the widget, one that isn't managed (pack/grid/table) and drag that around, until you find the final resting place

    Hmm, I suppose you could remove the button from the table (like packForget) when you start dragging ... I'll get back to you on this

Re: How to make a Tk::Button move/slide within a Tk::Table
by thundergnat (Deacon) on Feb 09, 2012 at 17:01 UTC

    I wasn't sure of the desired behavior, (should each button be replaced as you drag over it, or should only the beginning and ending button be swapped?) so each is in here. Uncomment the appropriate line(s) in the slideTo{} sub.

    Also, I changed the drag binding to the right mouse button so it wouldn't collide with left button presses. Change it back if that isn't what you want.

    Your addButton command logic seems over-complex too, but I didn't mess with it as I wasn't sure what else you might be using it for.

    use warnings; use strict; use Tk; use Tk::Table; use Data::Dumper; my $buttonsPerRow = 5; $buttonsPerRow -= 1; my $row = 0; my $col = 1; my $buttonNum = 0; my @buttons; my $mw = tkinit(); my $menuFrame = $mw->Frame()->pack( -expand => 1, -fill => 'both' ); my $buttonFrame = $mw->Frame()->pack( -expand => 1, -fill => 'both' ); my $table = $buttonFrame->Table( -columns => 5, -scrollbars => 'se', -fixedrows => 1, )->pack( -expand => 1, -fill => 'both' ); my $addButton = $menuFrame->Button( -text => 'Add Button', -command => sub { $buttons[$buttonNum] = $table->Button(); print "Button $buttonNum->$row:$col\n"; $buttons[$buttonNum]{'row'} = $row; $buttons[$buttonNum]{'col'} = $col; $table->put( $row, $col, $buttons[$buttonNum] ); configureButton($buttonNum); $col > $buttonsPerRow ? $row++ : undef; $col > $buttonsPerRow ? $col = 1 : $col++; $buttonNum++; }, )->pack( -side => 'top' ); ################################ my $isDragging = 0; #boolean - mouse button pressed and draggi +ng my $selectedButton = 0; my ( @original, @previous ); $mw->bind( '<Motion>', \&motion ); ################################ $mw->MainLoop(); sub configureButton { my $btnNum = shift; $buttons[$btnNum]->bind( '<Enter>', sub { $selectedButton = $btnNum; } ); $buttons[$btnNum]->configure( -text => "Button $btnNum", -command => sub { print "Button $btnNum pressed\n"; } ); $buttons[$btnNum]->bind( '<ButtonPress-3>', \&buttonPress ); $buttons[$btnNum]->bind( '<ButtonRelease-3>', \&buttonRelease ); } sub motion { slideTo( findCell( $_[0] ) ) if $isDragging; } sub buttonPress { $isDragging = 1; @original = @previous = findCell( $_[0] ); } sub buttonRelease { $isDragging = 0; } sub findCell { my $e = $_[0]->XEvent; my ( $row, $col ) = $table->Posn( $table->containing( $e->X, $e->Y + ) ); return ( $row, $col ); } sub slideTo { my @next = @_; if ( $previous[0] == $next[0] and $previous[1] == $next[1] ) { return; # still in same cell } else { #swap( \@previous, \@next ); # depending on desired behaviour, uncomment either the previou +s or the next two lines swap( \@previous, \@original ); swap( \@original, \@next ); @previous = @next; } } sub swap { my ( $this, $that ) = @_; my $s1 = $table->get(@$this); my $s2 = $table->get(@$that); $s2->UnmanageGeometry; $table->LostSlave($s2); $table->put( @$this, $s2 ); $table->put( @$that, $s1 ); }
      Thanks thundergnat!

      That (second option) is exactly what I was looking for, though I think it looks/works better using the first option (swap start and end cells).

      I think I will stick with dragging with the right mouse button, as it seems to be the better method...

      By the way... before I reinvent the wheel, is there an easy way to save the $table layout and @buttons to a file so i can import/recreate the layout? I read something about Data::Dumper being able to do that sort of thing, but i have never used it for that purpose.