Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Tk: cascade widget != menu

by yike (Novice)
on Feb 14, 2007 at 10:08 UTC ( [id://599901]=perlquestion: print w/replies, xml ) Need Help??

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

Dear monks, I wonder if it is possible to 'cascade' the optionmenu widget or other widget different from the menu widget. Why? Consider below example. I want to avoid a too long list of options. The elements in the list always consists out of a static part+an index. So it would be ideal if I could show the static part in the main optionlist and once this static part is selected, the dynamic part should appear.

So iso:
opt1 opt2 opt3 opt4 dinner1 dinner2 dinner3 dinner4 desert1 desert2 desert3 desert4 desert5

I want, visualized in Tk:
opt -> opt1 opt2 opt3 opt4
dinner -> dinner1 dinner2 dinner3 dinner4
desert -> desert1 desert2 desert3 desert4 desert5


#!/usr/local/bin/perl use Tk; # Main Window $mw = new MainWindow; my $var; my $opt = $mw -> Optionmenu(-options => [qw(opt1 opt2 opt3 opt4 dinner +1 dinner2 dinner3 dinner4 desert1 desert2 desert3 desert4 desert5 )], -variable => \$var, )->pack; $mw->Button(-text=>'Exit', -command=>sub{$mw->destroy})->pack; MainLoop;

Replies are listed 'Best First'.
Re: Tk: cascade widget != menu
by Anonymous Monk on Feb 14, 2007 at 12:38 UTC
    Yes its possible. The widget demonstration has an example.
      widget demonstration? Please tell me where to find this or attach the example here.
        \perl\bin\ look for widget, isn't that mentioned in the Tk docs?

        jdtoronto

Re: Tk: cascade widget != menu
by TGI (Parson) on Feb 14, 2007 at 23:32 UTC

    The bad news is that the Optionmenu widget does not allow for cascades or any other 'fancy' menu behaviors.

    The good news is that it shouldn't be too hard to extend it to get the behavior you want.

    For a project, I needed to make an Optionmenu widget that could include separator items--also not possible with a basic Optionmenu. The answer was to subclass the Optionmenu widget and override the normal addOptions method.

    sub addOptions { my $w = shift; my $menu = $w->menu; my $tvar = $w->cget(-textvariable); my $vvar = $w->cget(-variable); my $oldt = $$tvar; my $width = $w->cget('-width'); my %hash; my $first; while (@_) { my $val = shift; my $label = $val; if (ref $val) { if ($vvar == $tvar) { my $new = $label; $w->configure(-textvariable => ($tvar = \$new)); } ($label, $val) = @$val; } my $len = length($label); $width = $len if (!defined($width) || $len > $width); # This is the only bit that's different ===== if ( $label eq '-') { $menu->separator(); } else { # This was copied from the original method. $menu->command(-label => $label, -command => [ $w , 'setOption' +, $label, $val ]); } # END OF CHANGES ============================ $hash{$label} = $val; $first = $label unless defined $first; } if (!defined($oldt) || !exists($hash{$oldt})) { $w->setOption($first, $hash{$first}) if defined $first; } $w->configure('-width' => $width); }


    TGI says moo

      Thx TGI for this very usefull comment.
      I gave it a try, but I still encounter some problems:
      - every sublist starts with some separator alike dashes. The number of dashes depends of the number of elements in that sublist
      - if I add a 'subelement' to an optionmenu later on, then I cannot check if the 'mainpart' of this element already exist
      my code:
      #!/usr/local/bin/perl use Tk; # Main Window $mw = new MainWindow; my $var; my $opt = $mw -> Optionmenu(-options => [qw(opt1 opt2 opt3 opt4 dinner +1 dinner2 dinner3 dinner4 desert1 desert2 desert3 desert4 desert5 )], -variable => \$var )->pack; $opt->addOptions('dinner5'); $mw->Button(-text=>'Exit', -command=>sub{$mw->destroy})->pack; MainLoop;
      my changed sub addOptions:
      sub addOptions { my $w = shift; my $menu = $w->menu; my $tvar = $w->cget(-textvariable); my $vvar = $w->cget(-variable); my $oldt = $$tvar; my $width = $w->cget('-width'); my %hash; my %MainFields; my $first; while (@_) { my $val = shift; my $label = $val; if (ref $val) { if ($vvar == $tvar) { my $new = $label; $w->configure(-textvariable => ($tvar = \$new)); } ($label, $val) = @$val; } my $len = length($label); $width = $len if (!defined($width) || $len > $width); ##### BEGIN CHANGED ####### if ( $label eq '-') { $menu->separator() } elsif ( $label =~ /^(\w+)(\d+)$/ ) { my ($One, $Two)=($1, $2); if ( !defined($MainFields{'DaTa'.$One}) ) { $MainFields{'DaTa'.$One}=$menu->Cascade(-label=>$One); } $MainFields{'DaTa'.$One}->command(-label =>$One.$Two,-command +=> [ $w , 'setOption', $One.$Two ]); } else { $menu->command(-label => $label, -command => [ $w , 'setOption +', $label, $val ]); } ##### END CHANGED ####### # $menu->command(-label => $label, -command => [ $w , 'setOption', +$label, $val ]); $hash{$label} = $val; $first = $label unless defined $first; } if (!defined($oldt) || !exists($hash{$oldt})) { $w->setOption($first, $hash{$first}) if defined $first; } $w->configure('-width' => $width); }

        I'm not sure I understand your code, but it looks like you are directly modifying the Optionmenu widget code. This approach is best avoided. What you should be doing is subclassing the Optionmenu. I've attached my complete SepOptionmenu class file for you to see how I did it. The advantages of subclassing are many, but the biggest is that you won't have scripts mysteriously breaking when Optionmenu gets upgraded.

        As far as adding options to an existing widget, I would modify addOptions to handle some sort of nested data structure. Perhaps where each element in the list is a menu item, vanilla items (that just set a value) could be plain text strings, while other times would be an array ref of (type, label, data). Type could be 'command' or 'cascade'. If type is command, the data item would be a callback. If cascade data would be another item list like the one being parsed.

        $menuItems = [ 'foo', 'bar', [ 'command', 'baz', [ \&BazRoutine, $bazarg0, $bazarg1 ] ], [ 'cascade', 'qux', [ 'huzzah', 'fizzle', [ 'command', 'bagel', \&BagelRoutine ], ], 'frank', ];

        As for accessing information about the menu itself, you can use the $om->menu method to get access to the Tk::Menu widget. Once you have the menu widget, you can use the index method to find out about what items are in the menu.

        You may want to add additional methods to your subclass that support particular tasks, like adding or removing cascaded items to a particular index.

        Finally, here's the subclassing example. If you aren't familiar with subclassing and inheritance, check out perlboot and perltoot. This code is licensed under the same terms as perl.

        package Tk::SepOptionmenu; use base ('Tk::Optionmenu', 'Tk::Derived'); use strict; use warnings; use Carp; our $VERSION = '1.0'; Construct Tk::Widget 'SepOptionmenu'; sub ClassInit { my ($class, $mw) = @_; $class->SUPER::ClassInit($mw); } sub addOptions { my $w = shift; my $menu = $w->menu; my $tvar = $w->cget(-textvariable); my $vvar = $w->cget(-variable); my $oldt = $$tvar; my $width = $w->cget('-width'); my %hash; my $first; while (@_) { my $val = shift; my $label = $val; if (ref $val) { if ($vvar == $tvar) { my $new = $label; $w->configure(-textvariable => ($tvar = \$new)); } ($label, $val) = @$val; } my $len = length($label); $width = $len if (!defined($width) || $len > $width); if ( $label eq '-') { $menu->separator(); } else { $menu->command(-label => $label, -command => [ $w , 'setOption' +, $label, $val ]); } $hash{$label} = $val; $first = $label unless defined $first; } if (!defined($oldt) || !exists($hash{$oldt})) { $w->setOption($first, $hash{$first}) if defined $first; } $w->configure('-width' => $width); } 1; __END__ =pod =head1 NAME Tk::SepOptionmenu - A perl/tk Optionmenu widget that can include separ +ators. =head1 SYNOPSIS use Optionmenu; $sopt = $w->Optionmenu( -options => REFERENCE_to_OPTIONLIST, -command => CALLBACK, -variable => SCALAR_REF, ); $sopt->addOptions( OPTIONLIST ); # OPTION LIST is # a) $val1, $val2, $val3,... # b) [ $lab1=>$val1], [$lab2=>val2], ... ] # c) combination of a) and b), e.g., # val1, [$lab2=>val2], val3, val4, [...], ... # # In a), any value may be '-' to indicate a separator. # If using b) style option list, you must still specify # separators as follows: [ [$lab1=>$val1], '-', [$lab2=>$val2], +... ]; =head1 DESCRIPTION This "I<IS A>" Optionmenu widget that can include separator menu items + in its menu. =head1 METHODS =over 4 =item addOptions Adds OPTION_LIST to the already available options. OPTION_LIST may inc +lude an item C<'-'> to indicate a separator. =back =head1 EXAMPLE use Tk; my $mw = MainWindow->new(); my ($var, $tvar); my $opt = $mw->SepOptionmenu( -options => [[jan=>1], [feb=>2], '-', [mar=>3], [apr=>4]], -command => sub { print "got: ", shift, "\n" }, -variable => \$var, -textvariable => \$tvar )->pack; $opt->addOptions('-',[may=>5],[jun=>6],'-',[jul=>7],[aug=>8]); my $f = $mw->Frame(-relief=>'groove', -borderwidth => 2)->pack; $f->Label(-textvariable=>\$tvar)->pack(-side => 'left'); $f->Label(-text => " -> ")->pack(-side => 'left'); $f->Label(-textvariable=>\$var)->pack(-side => 'left'); $mw->Button(-text=>'Exit', -command=>sub{$mw->destroy})->pack; MainLoop; =head1 SEE ALSO L<Tk::Optionmenu> =cut


        TGI says moo

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (1)
As of 2024-04-24 15:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found