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

Hey,

I've been working my a-s off trying to make this work as I want with no luck. So, now I turn myself towards your wisdom. I have been working on this Tk::ColumnView module, and I just can't get the Drag&Drop to work as I want. I've broken it down to the relevant parts:
use Tk; use Tk::HList; use Tk::Pane; use Tk::DragDrop; use strict; use warnings; my $mw = MainWindow->new; my $pane = $mw->Scrolled('Pane', -width => 800, -height => 400, -sticky => 'nsew', -scrollbars => 'os', )->pack(-fill => 'both', -expand => 1); addCol(1); addCol(2); addCol(3); sub addCol { my $id = shift; my $width = 200; my $frame = $pane->Frame( -relief => "ridge", -bd => 1, -width => $width, )->pack(-side => 'left', -fill => 'both', -expand => 1, ); # DnD SUPPORT! #my $frame1 = $frame->Frame()->pack(); #my $frame2 = $frame->Frame()->pack(); my $drag_source = $frame->DragDrop( -event => '<B1-Motion>', -sitetypes => [qw/Local/], ); my $adjuster = $pane->Adjuster(); $adjuster->packAfter($frame, -side => 'left'); my $label = $frame->Label(-width => 30, -text => "Test" . $id, )->pack(-fill => "both"); my $hlist = $frame->Scrolled("HList", -scrollbars => 'osoe', -selectmode => 'extended' )->pack(-fill => "both", -expand => 1); } MainLoop;
Basicly, I want the possability to be able to drag, say the title (Label) at the top either to the left or right, and drop it anywhere (in the pane I suppose). After that the idea is to rearrange the columns. So, basicly, I wanna be able of rearranging the columns by drag&drop. I read some tutorial on the web (and I have some code in here from that), but if I put the label I wanna have dragable in the $drag_source, the whole label "disapears". Or, well, it seems to be there, but its not packed properly (height 1 pixel or something..?) The idea with 2 frames ($frame1 and $frame2) was to make just ONE of those frames dragable, so I wouldnt get wierd things happening, when it may think I wanna drag the HList. That is, but Label on one frame, and the HList on the other.

Else the layout should be as I want it when the script is ran as is.

Natrually, I need something to be a DropSite to. Appreciate help on that too! Suppose the pane is a good kandidate.

As a plus, it would also be nice to somehow show a "ghost" of the column dragged while dragging. Would be nice if anyone here knew how to do that aswell!

Thanks,
Ace

Replies are listed 'Best First'.
Re: Drag & Drop Problem
by vkon (Curate) on May 20, 2006 at 05:46 UTC
    I suggest you to not implement drag-n-drop functionality of your widgets within your script.
    Much better is to use a widget with draggable columns.
    I beleive perl/Tk has something to suggest.

    But if I were asked to implement draggable columns with perl with Tk, I would use for example TkTreeCtrl, which allows draggable columns.
    Also I used to write something with similar functionality recently using tablelist widget, which is damn powerful.

    Well, I do not limit myself into limeted set of perl/Tk widgets, and I use all Tcl/Tk widgets freely with a use of Tcl::Tk CPAN module.

      Well, 2 things.

      1. Code is actually taken from the module. Meaning its not a seperate script I pasted using the module.

      2. The idea with this was to add whatever per column. As it it in this example, I have a HList, but the idea later was to add any kind of widget into a column (frame). That is, we have the label at the top (so we have something to "drag"), and whatever Tk thing below as wanted... Not sure if those things mentined supports that...
        perlTk has DropSite, like you searched.

        As I looked into it, it is poorly documented (see perldoc Tk::DropSite) but perlTk source tree contain demos to start with.

        Sorry for not very much help specific to your question.

Re: Drag & Drop Problem
by liverpole (Monsignor) on May 20, 2006 at 19:04 UTC
    Hi Ace128,

    I'm not sure exactly what you're trying to do, and I haven't done much with Drag and Drop (more today than ever before!), but here's a suggested rewrite that shows how to drag any of the labels into the Canvas object created in each frame:

    #!/usr/bin/perl -w use Tk; use Tk::HList; use Tk::Pane; use Tk::DragDrop; use Tk::DropSite; use Data::Dumper; use strict; use warnings; my $eyeball = ' /* XPM */ static char * eyeball[] = { "32 12 4 1", " c None", "@ c #000000", "x c #ffffff", "o c #3f3fff", " @@@@@@@@@ ", " @@@@@@@@@@@@@ ", " @@xxxxxxxxxxx@@ ", " @@xxxx@@@@@@xxx@@ ", " @@xxx@@oooo@@xxx@@ ", " @@xx@@oooooo@@xx@@ ", " @@xx@@oooooo@@xx@@ ", " @@xxx@@oooo@@xxx@@ ", " @@xxx@@@@@@xxx@@ ", " @@xxxxxxxxxx@@ ", " @@@@@@@@@@@@ ", " @@@@@@@@ ", '; my $mw = MainWindow->new; my $psym = make_image($mw, $eyeball); my $pane = $mw->Scrolled( 'Pane', -width => 800, -height => 400, -sticky => 'nsew', -scrollbars => 'os', )->pack(-fill => 'both', -expand => 1); addCol(1); addCol(2); addCol(3); sub addCol { my $id = shift; my $width = 200; my $frame = $pane->Frame(-relief => "ridge", -bd => 1, -width => +$width); $frame->pack(-side => 'left', -fill => 'both', -expand => 1); # DnD SUPPORT! #my $frame1 = $frame->Frame()->pack(); #my $frame2 = $frame->Frame()->pack(); my $adjuster = $pane->Adjuster(); $adjuster->packAfter($frame, -side => 'left'); my $label = $frame->Label(-width => 30, -text => "Test" . $id); $label->pack(-fill => "both"); my $drag = $label->DragDrop( -event => '<B1-Motion>', -sitetypes => [qw/Local/], -image => $psym, ); my $can = $frame->Canvas(-bg => 'skyblue')->pack(); my $drop = $can->DropSite( -droptypes => [qw(Local)], -dropcommand => [\&perform_drop, $can, $label], -entercommand => [\&hover_over_drop, $can], ); my $hlist = $frame->Scrolled( "HList", -scrollbars => 'osoe', -selectmode => 'extended' )->pack(-fill => "both", -expand => 1); } MainLoop; # # Inputs: $1 ... the top-level widget # $2 ... the image data # # Outputs: $1 ... a pointer to the image for use with other widgets # sub make_image { my ($w, $data) = @_; my $img = $w->Pixmap(-data, $data); return $img; } # # Inputs: $1 ... the widget being hovered over # $2 ... nonzero if entering, zero if leaving # sub hover_over_drop { my ($drop_site, $b_entry) = @_; $drop_site->configure(-bg => $b_entry == 1 ? 'hotpink' : 'skyblue' +); } # # Inputs: $1 ... the widget being dropped into # $2 ... the widget being dropped # sub perform_drop { my ($drop_site, $drop_obj) = @_; $drop_site->configure(-bg => 'skyblue'); print "Debug: Now do something with drop_obj $drop_obj\n"; }

    Some notes:

    1. An "eyeball" symbol was added to show how you can drag something other than the default.
    2. A Canvas widget was added to each Frame, to have something to drag into.
    3. You need to create the widget you're dragging *before* you attempt to drag it (that may have been part of your problem, if you wanted to drag the Label widget).
    4. You should include Tk::DropSite in addition to Tk::DragDrop, and call the associated DropSite() method.
    5. I added the subroutines hover_over_drop() and perform_drop() which get called when the drop site is entered/exited, or has the widget dropped into it.
    6. The subroutine perform_drop() should be filled in with whatever action you want to take on the Label widget (named parameter $drop_obj) which is dropped.

    I hope that may be of some use to you.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
      LOVELY! Finlly I can make progress! Big Thanks!

      1. Yea. Thanks. Knew about that.
      2 and 3. Well, acually, tutorial I read seems to have missleaded me. I though you had to create this $drag_source from say $frame, and THEN create those you wanna have dragable on that drag source. Apparently you can just make anything dragable by $wanna_drag->DragDrop( ... );
      4. Yea, I meantioned that too, but I just wanted the drag to work. Dropsource would be easy later...
      5 & 6. Yea, thanks for making it easier for me.

      Now, the dropframe is nice, but do you think there is some other better way to add a dropsite? I mean, the frame just for droping and is taking place. Im gonna try making.. say the whole pane. Then use some bbox or something to figure out where its dropped...

      Update: Hmm, maybe implementation like Tk::Mlistbox.. (however it does it)
      Alrighty. Modified and updated that.
      use Tk; use Tk::HList; use Tk::Pane; use Tk::DragDrop; use Tk::DropSite; use strict; use warnings; my $mw = MainWindow->new; my $eyeball = ' /* XPM */ static char * eyeball[] = { "32 12 4 1", " c None", "@ c #000000", "x c #ffffff", "o c #3f3fff", " @@@@@@@@@ ", " @@@@@@@@@@@@@ ", " @@xxxxxxxxxxx@@ ", " @@xxxx@@@@@@xxx@@ ", " @@xxx@@oooo@@xxx@@ ", " @@xx@@oooooo@@xx@@ ", " @@xx@@oooooo@@xx@@ ", " @@xxx@@oooo@@xxx@@ ", " @@xxx@@@@@@xxx@@ ", " @@xxxxxxxxxx@@ ", " @@@@@@@@@@@@ ", " @@@@@@@@ ", '; my $psym = make_image($mw, $eyeball); my $pane = $mw->Scrolled('Pane', -width => 800, -height => 400, -sticky => 'nsew', -scrollbars => 'os', )->pack(-fill => 'both', -expand => 1); addCol(1); addCol(2); addCol(3); sub addCol { my $id = shift; my $width = 200; my $frame = $pane->Frame( -relief => "ridge", -bd => 1, -width => $width, )->pack(-side => 'left', -fill => 'both', -expand => 1, ); # DnD SUPPORT! #my $frame1 = $frame->Frame()->pack(); #my $frame2 = $frame->Frame()->pack(); my $adjuster = $pane->Adjuster(); $adjuster->packAfter($frame, -side => 'left'); my $label = $frame->Label(-width => 30, -text => "Test" . $id, -b +g => "lightgray", )->pack(-fill => "both"); # my $label = $frame->Button(-width => 30, -text => "Test" . $id, # -command => sub { print "!!!!!!!!!!!! +"; }, # )->pack(-fill => "both"); my $press = sub { my ($c_src, $label_obj, $drag_source) = @_; #$drag_id = $c_src_id; #my $type = $c_src->type($drag_id); $drag_source->configure(-text => $label_obj->cget(-text)); }; my $drag_source = $label->DragDrop( -event => '<B1-Motion>', -sitetypes => [qw/Local/], #-image => $psym, ); $label->bind('<ButtonPress-1>' => [$press, $label, $drag_source]); #_dragOrSort($button); #my $can = $frame->Canvas(-bg => 'skyblue')->pack(); my $hlist = $frame->Scrolled("HList", -scrollbars => 'osoe', -selectmode => 'extended' )->pack(-fill => "both", -expand => 1); my $drop = $hlist->DropSite( -droptypes => [qw(Local)], -dropcommand => [\&perform_drop, $label], -entercommand => [\&hover_over_drop, $label], # -dropcommand => [\&perform_drop, $can, $label], # -entercommand => [\&hover_over_drop, $can, $label], ); my $drop2 = $label->DropSite( -droptypes => [qw(Local)], -dropcommand => [\&perform_drop, $label], -entercommand => [\&hover_over_drop, $label], # -dropcommand => [\&perform_drop, $can, $label], # -entercommand => [\&hover_over_drop, $can, $label], ); } MainLoop; # # Inputs: $1 ... the top-level widget # $2 ... the image data # # Outputs: $1 ... a pointer to the image for use with other widgets # sub make_image { my ($w, $data) = @_; my $img = $w->Pixmap(-data, $data); return $img; } # # Inputs: $1 ... the widget being hovered over # $2 ... nonzero if entering, zero if leaving # $3 ... label over # sub hover_over_drop { my ($drop_site, $b_entry, $x_pos, $label_obj) = @_; $drop_site->configure(-bg => $b_entry == 1 ? 'gray' : 'lightgray') +; if ($b_entry) { #print $label_obj . "\n"; #$label_obj->cget(-text); } } # # Inputs: $1 ... the widget being dropped into # $2 ... the widget being dropped # sub perform_drop { my ($drop_site, $x_pos, $drop_obj) = @_; #use Data::Dumper; #print Dumper(@_); #$pane->idletasks; #$pane->update; #$x_pos->configure(-bg => 'yellow'); #$drop_site->configure(-bg => 'yellow'); #$drop_site->configure(-bg => 'skyblue'); #print "Debug: Now do something with drop_obj $drop_obj " . $drop +_obj->cget(-text) . "\n"; } sub _dragOrSort { my ($w, $c) = @_; #my $w = $pane; #unless ($w->cget('-moveable')) { #if ($c->cget('-sortable')) { # $w->sort (undef, $c); #} #return; #} my $h = shift; # The heading button of the column. my $start_mouse_x = $h->pointerx; my $y_pos = $h->rooty; # This is constant through the whole opera +tion. my $width = $h->width; my $left_limit = $w->rootx - 1; # Find the rightmost, visible column my $right_end = 0; foreach (@{$w->{'_columns'}}) { if ($_->rootx + $_->width > $right_end) { $right_end = $_->rootx + $_->width; } } my $right_limit = $right_end + 1; # Create a "copy" of the heading button, put it in a toplevel that + matches # the size of the button, put the toplevel on top of the button. my $tl=$w->Toplevel; $tl->overrideredirect(1); $tl->geometry(sprintf("%dx%d+%d+%d", $h->width, $h->height, $h->rootx, $y_pos)); my $b=$tl->Button (map{defined($_->[4]) ? ($_->[0]=>$_->[4]) : ()} $h->configure) ->pack(-expand=>1,-fill=>'both'); # Move the toplevel with the mouse (as long as Button-1 is down). $h->bind("<Motion>", sub { my $new_x = $h->rootx - ($start_mouse_x - $h->pointerx); unless ($new_x + $width/2 < $left_limit || $new_x + $width/2 > $right_limit) { $tl->geometry(sprintf("+%d+%d",$new_x,$y_pos)); } }); $h->bind("<ButtonRelease-1>", sub { my $rootx = $tl->rootx; my $x = $rootx + ($tl->width/2); $tl->destroy; # Don't need this anymore... $h->bind("<Motion>",''); # Cancel binding if ($h->rootx == $rootx) { # Button NOT moved, sort the column.... if ($c->cget('-sortable')) { $w->sort(undef, $c); } return; } # Button moved..... # Decide where to put the column. If the center of the dragged # button is on the left half of another heading, insert it -before # the column, otherwise insert it -after the column. foreach (@{$w->{'_columns'}}) { if ($_->ismapped) { my $left = $_->rootx; my $right = $left + $_->width; if ($left <= $x && $x <= $right) { if ($x - $left < $right - $x) { $w->columnShow($c,-before=>$_); } else { $w->columnShow($c,'-after'=>$_); } $w->update; $w->Callback(-configurecommand => $w); } } } }); }
      One problem with this I've noticed. If I resize the columns, the drag and drop it still thinks the columns are the size they were! If I drap-n-drop one time, and try again, it works. So, how to fix this?

      Oh, and I dont really get why I cant send the reference to the label into "hover_over_drop" and "perform_drop". All I get is "XdndSelection". Whats up with that? _dragOrSort is taken from Tk::MlistBox. Not really used yet. Just there for reference if needed to be used later. Btw, love such "hacks" as the eyeball!
        As far as resizing columns, the only thing I can suggest is maybe you need to call a subroutine in response to pane resize events, and in that subroutine reissue the call to DropSite.  As I said in my disclaimer, I'm not really familiar with DragDrop yet, and it doesn't look like it's especially well-commented.

        With respect to sending the reference to the label into "hover_over_drop" and "perform_drop", can you show the exact code you're using to try to do that?  It may just be (and this is an "off-the-wall" guess) that you have the wrong number of parameters to the subroutine where you're using the reference.

        The "eyeball" hack, I have to admit, was just supposed to be a "bullseye"-like icon, but when I ran the code, it looked more like an "eyeball" than anything else!


        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Drag & Drop Problem
by rminner (Chaplain) on May 20, 2006 at 12:02 UTC
    Hi,
    like vkon said - simply use a widget which supports rearranging of columns.
    A widget which supports sorting, resizing, and rearranging of columns is for example Tk::MListbox.
    If you want to bind shortcuts to that widget, please note that there is a trick to it Some Tk::TableMatrix and Tk::MListbox excentricities.
    This widget is to slow for 10 000 entries, but if you are going to put less than 1000 into it, it should be fast enough.
    The tablelist mentioned by vkon could be a good solution aswell, if you can use Tcl::Tk. (Depends on whether you are allowed to install tcl::tk, and whether you have to compile your programm.)
Re: Drag & Drop Problem
by ldln (Pilgrim) on May 20, 2006 at 21:01 UTC
    Ace128, are you trying to make replacement module for Tk::Column (old, buggy and slow) or Tk::MlistBox (ugly)?

    I could kill you for modified Tk::TableMatrix module, with sortable columns, drag and drop and whatnot (a'la windows explorer). Tk::TableMatrix is superfast and it would be soo cool?

    Anyway, why does nobody ever create new widgets with Tk::TableMatrix? It's one of the most powerful widget available IMO. (I would do it if I could..)

      Well, dunno about Tk::Column (never used it). Tl::MlistBox - well, almost exactly like that, but more flexible what to put into the columns. Oh, and what else does Tk::TableMatrix has that is bad and wanted reimplemented? Actually, installing and running latest gives me following annoyances:

      Had to create Tk::XlibVtab unexpectedly at c:/Perl/lib/DynaLoader.pm line 253.
      2442874 is not a hash at c:/Perl/site/lib/Tk/Widget.pm line 190.

      This application has requested the Runtime to terminate it in an unusual way.
      Please contact the application's support team for more information.
        Tk::TableMatrix which comes with Active State Perl 5.8.4 and later seems to be compiled against a wrong Tk-version, so it doesn't work (see also Some Tk::TableMatrix and Tk::MListbox excentricities). You have to download it from somewhere else, or compile it yourself.