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

Fellow monks,

I have a Tk app that uses the Tree witdget. Basically, there are two Trees. In the beginning, all entries are in the $CalTree tree and the $listbox tree is empty. Then, the user double-clicks (or drag-and-drops) a selection in the $CalTree tree and it adds the selection to the $listbox tree. The user can remove entries from $listbox similarly.

This works fine. What is strange though is that it is possible to double click on the (+) next to some item in the $listbox and have the entire entry be removed. It is as though the (+) is considered part of the entry as opposed to a tree. How can I aviod this behavior? Thanks in advance.

<readmode>

use strict; use Tk; require Tk::DirTree; require Tk::DragDrop; require Tk::DropSite; require Tk::LabFrame; my $CalTreeWidth = 30; my $CalTreeHeight = 20; my $selectionWidth = 30; my $selectionHeight = 8; my $p1 = MainWindow->new(); my ( $CalTree, $listbox, ); my $leftFrame = $p1->LabFrame(-label=>"System Configuration", -l +abelside=>"acrosstop") ->pack(-side=>"left",-fill=>'y', -anchor=>"nw"); { my $CalTreeScrollBarY = $leftFrame->Scrollbar(); my $CalTreeScrollBarX = $leftFrame->Scrollbar(-orient=>"horiz" +); $CalTree = $leftFrame->Tree(-yscrollcommand=>['set',$CalTreeSc +rollBarY], -xscrollcommand=>['set',$CalTreeSc +rollBarX], -width=>$CalTreeWidth, -height=>$CalTreeHeight, -drawbranch=>1, -selectmode=>"extended", -command=>\&addSelected); $CalTreeScrollBarY->configure(-command=> [yview=>$CalTree]); $CalTreeScrollBarX->configure(-command=> [xview=>$CalTree]); $CalTree->DragDrop(-event => '<B1-Motion>', -text => "", -sitetypes => qw(Local)); $CalTree->DropSite(-droptypes => qw(Local), -dropcommand => [\&removeSelected]); $CalTreeScrollBarX->pack(-side=>"bottom",-fill=>"x"); $CalTree->pack(-side=>"left",-fill=>"both"); $CalTreeScrollBarY->pack(-side=>"left",-fill=>"y"); } { my $selFrame = $p1->LabFrame(-label=>"Selected Items", -labels +ide=>"acrosstop") ->pack(-fill=>"y", -expand=>1); my $listboxScrollBarY = $selFrame->Scrollbar(); my $listboxScrollBarX = $selFrame->Scrollbar(-orient=>"horiz") +; $listbox = $selFrame->Tree(-width=>$selectionWidth,-height=>$s +electionHeight,-drawbranch=>1, -command=>\&removeSelected,-selectm +ode=>"extended", -yscrollcommand=>['set',$listboxScr +ollBarY], -xscrollcommand=>['set',$listboxScr +ollBarX] ); $listboxScrollBarY->configure(-command=> [yview=>$listbox]); $listboxScrollBarX->configure(-command=> [xview=>$listbox]); $listbox->DropSite(-droptypes => qw(Local), -dropcommand => [\&addSelected]); $listbox->DragDrop(-event => '<B1-Motion>', -text => "", -sitetypes => qw(Local)); $listboxScrollBarX->pack(-side=>"bottom",-fill=>"x"); $listbox->pack(-side=>"left",-fill=>"both"); $listboxScrollBarY->pack(-side=>"right",-fill=>"y"); } $CalTree->add("parent", -text=>"parent1111"); $CalTree->addchild("parent", -text=>"child1"); $CalTree->addchild("parent.0", -text=>"grandchild1"); $CalTree->addchild("parent.0", -text=>"grandchild2"); $CalTree->addchild("parent", -text=>"child2"); $CalTree->addchild("parent.1", -text=>"grandchild1"); $CalTree->addchild("parent.1", -text=>"grandchild2"); $CalTree->autosetmode(); MainLoop; sub addSelected { print "InterncalCal.pm: addSelected()\n"; foreach ($CalTree->info("selection")) { print " Found selected: " . $CalTree->entrycget($_,"-text") . " +\n"; my $item = $CalTree->entrycget($_,"-text"); if ($CalTree->info("parent",$_) eq ""){ # has no parent, so selection must have been 'root'. In other wo +rds, 'select all' foreach($CalTree->info("children", $_)){ my $parent = $CalTree->entrycget($_,"-text"); next if ($CalTree->entrycget($_,"-state") eq "disabled"); if (!$listbox->info("exists",$parent)){ $listbox->add("$parent", -text=>$parent); # if parent does n +ot exist, create him first } foreach($CalTree->info("children", $_)){ my $no = 0; my $item = $CalTree->entrycget($_,"-text"); foreach($listbox->info("children", $parent)){ $no++ if ($listbox->entrycget($_,"-text") eq $item); #if b +oth parent and child exist, no need to add again } if (!$no){ $listbox->addchild($parent,-text=>$item); # add item if do +es not already exists } } } } else { my $parent = $CalTree->entrycget($CalTree->info("parent",$_), "- +text"); if ($parent =~ /\d{4}/){ # since we are not tracking from root (which contains 4 digits +, this item should be at the root of the selection list if (!$listbox->info("exists",$item)){ # add item to list if do +es not exist $listbox->add("$item", -text=>$item); } foreach my $child ($CalTree->info('children',$_)){ #add all of + its children as well my $no = 0; my $childname = $CalTree->entrycget($child,"-text"); foreach ($listbox->info('children',$item)){ $no++ if ($listbox->entrycget($_,"-text") eq $childname); } if (!$no){ $listbox->addchild($item,-text=>$childname); } } } else { if ($listbox->info("exists",$parent)){ foreach($listbox->info("children", $parent)){ return if ($listbox->entrycget($_,"-text") eq $item); #if +both parent and child exist, no need to add again } } else { $listbox->add("$parent", -text=>$parent); # if parent does n +ot exist, create him first } $listbox->addchild($parent,-text=>$item); # add item } } } $listbox->autosetmode(); } sub removeSelected { print "InterncalCal.pm: removeSelected()\n"; my @toplevel; foreach ($listbox->info("selection")){ print " Found selected: " . $listbox->entrycget($_,"-text") . " +\n"; # don't remove the top level ones until all the selected items und +erneath have been removed # this is a hack !!! if ($listbox->entrycget($_,"-text") !~ /\s/){ push @toplevel, $_; next; } $listbox->delete("entry",$_); } $listbox->delete("entry",$_) foreach(@toplevel); $listbox->autosetmode(); }

Replies are listed 'Best First'.
Re: Tk::Tree double click on +
by eserte (Deacon) on Apr 28, 2004 at 16:14 UTC
    I am not sure what your problem is. Can you provide complete runnable code, so I can try it?
      The entire executable program can be seen as the updated program code in the body of the original question. As for a sequence of steps I am talking about:

      • Double click on "parent1111" in "System configuration" window.
      • Click on the (-) next to child1 or child2.
      • Double click on the (+) next to child1 or child2.

      The whole entry of child1/2 goes away. Instead, I would like the behavior of only open/close when double-clicking on the (-) or (+).

        You are not going to be able to do this due to the nature of the widget you are using. As I am sure you notice when you click any parent or child in the tree you get the entire frame highlighted. It is double clicking on this frame that performs the specified action. You cannot isolate the + or - by itself because it is part of the widget and frame.

        What you might want to try is to change the -command=>\&addSelected to not transfer from left to right upon activation and also change the -command=>\&removeSelected to also not remove when acticated. Just stick with the drag and drop to transfer and possibly create an alt click (right click) menu to signify the delete function or also use the drag and drop function to remove from the "Selected Files" side.

        I know neither of these ways are probably what you are looking for but the widget you chose to use is limited in this way. I am sure there is a more advanced method of mapping two frames on a single line and having the + be in a seperate frame but this should give you something to go off of. Maybe one of the other monks will devote more time to giving you a more viable solution.

        Here is a simple solution that worked somewhat how you wanted it to.

        use Tk; require Tk::DirTree; require Tk::DragDrop; require Tk::DropSite; require Tk::LabFrame; my $CalTreeWidth = 30; my $CalTreeHeight = 20; my $selectionWidth = 30; my $selectionHeight = 8; my $p1 = MainWindow->new(); our($CalTree, $listbox); my $leftFrame = $p1->LabFrame(-label=>"System Configuration", -labelsi +de=>"acrosstop") ->pack(-side=>"left",-fill=>'y', -anchor=>"nw"); my $CalTreeScrollBarY = $leftFrame->Scrollbar(); my $CalTreeScrollBarX = $leftFrame->Scrollbar(-orient=>"horiz"); $CalTree = $leftFrame->Tree(-yscrollcommand=>['set',$CalTreeScrollBarY +], -xscrollcommand=>['set',$CalTreeScrollBarX] +, -width=>$CalTreeWidth, -height=>$CalTreeHeight, -drawbranch=>1, -selectmode=>"extended", -command=>\&addSelected); $CalTreeScrollBarY->configure(-command=> [yview=>$CalTree]); $CalTreeScrollBarX->configure(-command=> [xview=>$CalTree]); $CalTree->DragDrop(-event => '<B1-Motion>', -text => "", -sitetypes => qw(Local)); $CalTree->DropSite(-droptypes => qw(Local), -dropcommand => [\&removeSelected]); $CalTreeScrollBarX->pack(-side=>"bottom",-fill=>"x"); $CalTree->pack(-side=>"left",-fill=>"both"); $CalTreeScrollBarY->pack(-side=>"left",-fill=>"y"); my $selFrame = $p1->LabFrame(-label=>"Selected Items", -labelside=>"ac +rosstop") ->pack(-fill=>"y", -expand=>1); my $listboxScrollBarY = $selFrame->Scrollbar(); my $listboxScrollBarX = $selFrame->Scrollbar(-orient=>"horiz"); $listbox = $selFrame->Tree(-width=>$selectionWidth,-height=>$selection +Height,-drawbranch=>1, #-command=>\&removeSelected, -selectmode=>"extended", -yscrollcommand=>['set',$listboxScrollBarY], -xscrollcommand=>['set',$listboxScrollBarX] ); $listboxScrollBarY->configure(-command=> [yview=>$listbox]); $listboxScrollBarX->configure(-command=> [xview=>$listbox]); $listbox->DropSite(-droptypes => qw(Local), -dropcommand => [\&addSelected]); $listbox->DragDrop(-event => '<B1-Motion>', -text => "", -sitetypes => qw(Local)); $listboxScrollBarX->pack(-side=>"bottom",-fill=>"x"); $listbox->pack(-side=>"left",-fill=>"both"); $listboxScrollBarY->pack(-side=>"right",-fill=>"y"); $CalTree->add("parent", -text=>"parent1111"); $CalTree->addchild("parent", -text=>"child1"); $CalTree->addchild("parent.0", -text=>"grandchild1"); $CalTree->addchild("parent.0", -text=>"grandchild2"); $CalTree->addchild("parent", -text=>"child2"); $CalTree->addchild("parent.1", -text=>"grandchild1"); $CalTree->addchild("parent.1", -text=>"grandchild2"); $CalTree->autosetmode(); MainLoop; sub addSelected { print "InterncalCal.pm: addSelected()\n"; foreach ($CalTree->info("selection")) { print " Found selected: " . $CalTree->entrycget($_,"-text") . " +\n"; my $item = $CalTree->entrycget($_,"-text"); if ($CalTree->info("parent",$_) eq ""){ # has no parent, so selection must have been 'root'. In other wo +rds, 'select all' foreach($CalTree->info("children", $_)){ my $parent = $CalTree->entrycget($_,"-text"); next if ($CalTree->entrycget($_,"-state") eq "disabled"); if (!$listbox->info("exists",$parent)){ $listbox->add("$parent", -text=>$parent); # if parent does n +ot exist, create him first } foreach($CalTree->info("children", $_)){ my $no = 0; my $item = $CalTree->entrycget($_,"-text"); foreach($listbox->info("children", $parent)){ $no++ if ($listbox->entrycget($_,"-text") eq $item); #if b +oth parent and child exist, no need to add again } if (!$no){ $listbox->addchild($parent,-text=>$item); # add item if do +es not already exists } } } } else { my $parent = $CalTree->entrycget($CalTree->info("parent",$_), "- +text"); if ($parent =~ /\d{4}/){ # since we are not tracking from root (which contains 4 digits +, this item should be at the root of the selection list if (!$listbox->info("exists",$item)){ # add item to list if do +es not exist $listbox->add("$item", -text=>$item); } foreach my $child ($CalTree->info('children',$_)){ #add all of + its children as well my $no = 0; my $childname = $CalTree->entrycget($child,"-text"); foreach ($listbox->info('children',$item)){ $no++ if ($listbox->entrycget($_,"-text") eq $childname); } if (!$no){ $listbox->addchild($item,-text=>$childname); } } } else { if ($listbox->info("exists",$parent)){ foreach($listbox->info("children", $parent)){ return if ($listbox->entrycget($_,"-text") eq $item); #if +both parent and child exist, no need to add again } } else { $listbox->add("$parent", -text=>$parent); # if parent does n +ot exist, create him first } $listbox->addchild($parent,-text=>$item); # add item } } } $listbox->autosetmode(); } sub removeSelected { print "InterncalCal.pm: removeSelected()\n"; my @toplevel; foreach ($listbox->info("selection")){ print " Found selected: " . $listbox->entrycget($_,"-text") . " +\n"; # don't remove the top level ones until all the selected items und +erneath have been removed # this is a hack !!! if ($listbox->entrycget($_,"-text") !~ /\s/){ push @toplevel, $_; next; } $listbox->delete("entry",$_); } $listbox->delete("entry",$_) foreach(@toplevel); $listbox->autosetmode(); }
        Basically I just cleaned up your formatting a bit and commented out the command function of the left side Lab Frame widget.
        www.perlskripts.com
Re: Tk::Tree double click on +
by bbfu (Curate) on Apr 29, 2004 at 13:58 UTC

    You can override the default double-click binding with your own routine that checks to see if the item double-clicked on is the indicator and, if so, mutates the event into an open/close. Alternatively, you could just have it ignore double-click events on the indicator.

    Note, however, that to do so you must replace the binding for the entire class Tk:Tree, so it will affect every Tree widget (and possibly every widget derived from a Tk::Tree) in that window. (You can't bind to the particular Tree instance, because then both callbacks are called, and yours is called last so you can't even use Tk->break to stop propagation, since it's already happened.)

    #!/usr/bin/winperl use warnings; use strict; use Tk; use Tk::Tree; my $mw = MainWindow->new(); my $tr = $mw->Tree( -command => sub { print "Tree::command\n" }, )->pack(); $tr->add($_, -text => $_) for qw(Foo Bar Quux); $tr->addchild('Foo', -text => 'Zod'); $tr->autosetmode(); # Replace normal double-click handler with our special routine { my $old = $mw->bind('Tk::Tree', '<Double-ButtonPress-1>'); $mw->bind('Tk::Tree', '<Double-ButtonPress-1>', [ \&invoke, Ev('x'), Ev('y'), $old, ]); } MainLoop; sub invoke { my ($tree, $x, $y, $old) = @_; # Get info about what item was clicked on my ($entry, $subitem) = $tree->info('item', $x, $y); if(defined($subitem) and $subitem eq 'indicator') { #return; # ignore, if you want # Indicator, so just open/close the entry $tree->IndicatorCmd($entry, '<Activate>'); } else { # Not indicator, so propagate #$tree->Double1(); # Don't do this $old->Call($tree); # Instead, use old callback } }

    Update: Changed the manual call to Tk::HList::Double1 to use the callback instead, to avoid breaking encapsulation and to be more forward-compatible.

    bbfu
    Black flowers blossom
    Fearless on my breath

      Thank you! This is exactly the behavior I wanted. But I do have a question: where is method Double1() defined? It's not part of Tree or HList or Widget.

      Also, a completely unrelated question, I can't seem to figure out how to unhighlight an entry in a tree! (in other words, user clicks on a leaf/branch in a tree, then user clicks elsewhere off the tree; i want to deselect the selected item)

        But I do have a question: where is method Double1() defined?

        You have to look into the source code for this one (either in Tk/HList.pm or Tk/Tree.pm).

        I can't seem to figure out how to unhighlight an entry in a tree

        To deselect an hlist or tree item programmatically, you have to use the selectionClear or selectionSet methods (documented in Tk::HList). This could be done in response to a FocusOut or Leave event.