http://www.perlmonks.org?node_id=176490

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

I just started learning Perl/Tk today, and doing an example encountered a problem. (I'm using Mastering Perl/Tk, an O'reilly book, but it doesn't seem to address this)

I need to have 30 groups of 4 radio buttons (with values 1, 2, 3, and 4, respectively). This, however, takes up more than a screen, so I needed to use a scroll bar. The main window, for some reason, cannot have a scroll bar (that I can figure out, anyway), so I'm using the PaneWidget.

I tried using the pack geometry manager, however it was a MESS! I couldn't control the output. I realized, though, that the grid manager would be just what I required (to make each group of four on a separate line {or in this case, row}).

And herein lies the problem. . . (sorry for the long intro, but it kind of sets the context)

I CAN'T GET THE FRAME (the pane widget) TO OCUPY THE WHOLE WINDOW. That is, when I enlarge the window, the frame is still in a tiny portion in the middle. Using pack, I did the fill option to try to take care of it, and it worked, but I don't know what to do with grid. PLEASE HELP.

The code is here. (p.s., I tried columnspan, rowspan, but it didn't seem to have any success)

#!/usr/bin/perl use lib 'C:\MyPerlLib'; use Tk; my $i = 0; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osw/)->grid(); $mw->Label( -text => "... <insert directions here> ..." )->grid(); for (1..30) { $pane->Radiobutton( -text => "1", -value => "1", -justify => "left", -variable => \${"rb1_q$_"}, )->grid( $pane->Radiobutton( -text => "2", -value => "2", -justify => "left", -variable => \${"rb2_q$_"}, ), $pane->Radiobutton( -text => "3", -value => "3", -justify => "left", -variable => \${"rb3_q$_"}, ), $pane->Radiobutton( -text => "4", -value => "4", -justify => "left", -variable => \${"rb4_q$_"}, )); } MainLoop;

Replies are listed 'Best First'.
Re: Some beginning Tk help
by dree (Monsignor) on Jun 22, 2002 at 18:48 UTC
    You have to bind the <Configure> event and then resize the window:
    $mw->bind('<Configure>', \&Window_Resize);

    This should work for you:
    #!/usr/bin/perl use lib 'C:\MyPerlLib'; use Tk; use Tk::Pane; my $i = 0; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osw/)->grid(); my $label=$mw->Label( -text => "... <insert directions here> ..." )- +>grid(); for (1..30) { $pane->Radiobutton( -text => "1", -value => "1", -justify => "left", -variable => \${"rb1_q$_"}, )->grid( $pane->Radiobutton( -text => "2", -value => "2", -justify => "left", -variable => \${"rb2_q$_"}, ), $pane->Radiobutton( -text => "3", -value => "3", -justify => "left", -variable => \${"rb3_q$_"}, ), $pane->Radiobutton( -text => "4", -value => "4", -justify => "left", -variable => \${"rb4_q$_"}, )); } $mw->bind('<Configure>', \&Window_Resize); MainLoop; sub Window_Resize { my $e = $mw->XEvent; return if !$e; my $mww=$mw->width; my $mwh=$mw->height; $pane->place( -height => $mwh-30, -width => $mww-20); $label->place(-y=>$mwh-20,-x=>$mww/2-70); }
Re: Some beginning Tk help
by kvale (Monsignor) on Jun 22, 2002 at 18:51 UTC
    To have your Pane fill the panel, it is best to use pack at the main window level:
    #!/usr/bin/perl use Tk; use Tk::Pane; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osw/ )->pack(-fill => "both", -expand => 1); $mw->Label( -text => "... <insert directions here> ..." )->pack(); for (1..30) { $pane->Radiobutton( -text => "button 1", -value => "1", -justify => "left", -variable => \${"rb1_q$_"}, )->grid( $pane->Radiobutton( -text => "button 2", -value => "2", -justify => "left", -variable => \${"rb2_q$_"}, ), $pane->Radiobutton( -text => "button 3", -value => "3", -justify => "left", -variable => \${"rb3_q$_"}, ), $pane->Radiobutton( -text => "button 4", -value => "4", -justify => "left", -variable => \${"rb4_q$_"}, )); } MainLoop;
    Note that the button sizes will not expand to fill the window; the grid method creates a fixed grid. But the pane, which uses pack, will adjust scroll bars according to window size, even disappearing when not needed.

    -Mark
Re: Some beginning Tk help
by Util (Priest) on Jun 22, 2002 at 21:14 UTC
    Two different solutions:
    1. pack/grid hybrid: Use pack to position Pane and Label, and give pack the options to dynamically resize Pane. Use grid to position buttons within Pane.
      Oops, kvale beat me to this one.
    2. pack only: Put each set of 4 radiobuttons in a Frame, and pack the frames into Pane.
    Other general recommendations:
    • For your button variables, in each row, use the same variable for each of the 4 buttons, otherwise pressing one won't clear the other three!
    • For your button variables, use an array instead of soft-referenced global variables.
    • Use warnings (or -w) and strict.
    • Add use Tk::Pane to prevent the "Assuming 'require Tk::Pane;'" warning.
    • If you are using grid, then make your first row Labels, to avoid having to label each button.
    • Use Data::Dumper during testing to see the values of your input controls.
    The code below demonstrates all the recommendations, and both solutions.
    #!/usr/bin/perl -w use strict; use Tk; use Tk::Pane; # Set to 0 or 1 before running. my $grid_method = 1; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osow/)->pack( -expand => 'yes', -fill => 'both', ); $mw->Label( -text => "... <insert directions here> ..." )->pack(); my @button_values; if ($grid_method) { foreach my $j (1..4) { $pane->Label( -text => $j, )->grid( -column => $j, -row => 1, ); } foreach my $i (1..30) { foreach my $j (1..4) { $pane->Radiobutton( -value => $j, -variable => \$button_values[$i-1], )->grid( -column => $j, -row => $i+1, ); } } } else { foreach my $i (1..30) { my $frame = $pane->Frame->pack(); foreach my $j (1..4) { $frame->Radiobutton( -text => $j, -value => $j, -variable => \$button_values[$i-1], )->pack( -side => 'left', ); } } } MainLoop; use Data::Dumper; print Dumper \@button_values;
      Well, I couldn't figure out how to update the node after a few minutes, so I'll reply (It's been a while since I've been here).

      Thanks for all the solutions (especially the general recommendations--it's been a while since I've been using Perl, and just getting back to it I find that I've forgot some things).

      I, however, have another question

      It's just to get me pointed in the right direction. I was thinking it might be desirable that one group of radio buttons were displayed at one time (with buttons saying first, previous, next). Would all that would be required to do this be to undraw the current screen (via the forget method?) and then re-draw with the desired buttons (and, of course, handle in the command subs which buttons need to be drawn)? I want to make sure that I'm not missing any subtelties somewhere.

      Update: I changed the code around a little bit (minor things), and came across one final question. In order to make a 'finalized' product, so to speak, it would be nice if I knew how to make a Tk window that would open to a specified size. I looked through Mastering Perl/Tk, and got the notion that the geometry method might have something to do with it, but the book never explicitly said anything about how to (that I saw). What I'm thinking, for this example, is to have the code run, and come up simply as a rectangle that displays all the information (buttons) in one window. What would be the general method for this?

      (A side thought: since there's usually a preview button around when one is submitting a node for the first time, would such a button be desirable when editing a node?)

      Update 2
      Well, its not necessary for an answer I guess, but here's the code I forgot to put up before (hmm. . . about that preview button. . .)

      #!/usr/bin/perl -w use strict; use Tk; use Tk::Pane; ## In case this seems mysterious, the example is eventually ## going to administer a survey in which the participant ## responds with a 1-4 about certain adjectives my @words = ("word") x 30; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osow/)->pack( -expand => 'yes', -fill => 'both', ); my $frame = $pane->Frame->pack(); $mw->Label( -text => "... <insert directions here> ..." )->pack(); my @button_values; my @frame_widgets; my $unanswered = 30; sub button_set { my $i = int(shift); my $prev = $i - 1; my $next = $i + 1; my $first = 1; my $last = 30; $prev = $first if $prev < $first; $_->destroy foreach reverse @frame_widgets; @frame_widgets = (); push @frame_widgets, $frame->Label( -text => "#$i. $words[$i-1]", )->pack(); push @frame_widgets, $frame->Button( -text => "First", -command => sub { set_unasnwered(); button_set($first) }, )->pack( -side=>'left', ); push @frame_widgets, $frame->Button( -text => "Back to $prev", -command => sub { set_unasnwered(); button_set($prev) }, )->pack( -side=>'left', ); foreach my $j (1..4) { push @frame_widgets, $frame->Radiobutton( -text => $j, -value => $j, -variable => \$button_values[$i-1], )->pack( -side => 'left', ); } my $state1 = ("normal","disabled")[$next > $last]; push @frame_widgets, $frame->Button( -text => "Forward to $next", -command => sub { set_unasnwered(); button_set($next) }, -state => $state1, )->pack( -side => 'left', ); my $state2 = ("disabled","normal")[$unanswered < 1]; push @frame_widgets, $frame->Button( -text => "Finished", -command => sub { $mw->destroy; calc() }, -state => $state2, )->pack( -side => 'left', ); } sub set_unasnwered { my $i = 29; foreach my $j (@button_values) { $i-- if $j } $unanswered = $i; } sub calc { # Make sure all questions are answered due to few, but unlikely, #ways of getting past the preliminary check my $i = 30; foreach my $j (@button_values) { $i-- if $j } if ($i > 0) { print "Survey not completed. Exiting. . ."; exit } # Calculate score and save to file (Survey is indeed completed) } button_set(1); MainLoop; use Data::Dumper; print Dumper \@button_values;

        I couldn't figure out how to update the node
        See How do I change/delete my post?

        Would all that would be required to do this be to undraw the current screen (via the forget method?) and then re-draw with the desired buttons

        1. I think (but have not verified) that just using gridForget or packForget would cause a memory leak, for they only remove the widget from screen management; they don't actually get rid of the widget. Calling destroy does both.
        2. You only have to destroy and recreate the affected widgets, if they are all in one Frame or other pack-able container; no need to do the whole screen.

        Other than that, you are correct. Here is an implimentation:
        #!/usr/bin/perl -w use strict; use Tk; use Tk::Pane; my $mw = MainWindow->new( -title => "SACL Survey" ); my $pane = $mw->Scrolled(qw/Pane -scrollbars osow/)->pack( -expand => 'yes', -fill => 'both', ); my $frame = $pane->Frame->pack(); $mw->Label( -text => "... <insert directions here> ..." )->pack(); my @button_values; my @frame_widgets; sub button_set { my $i = int(shift); my $prev = $i - 1; my $next = $i + 1; my $first = 1; my $last = 30; $prev = $first if $prev < $first; $next = $last if $next > $last; $_->destroy foreach reverse @frame_widgets; @frame_widgets = (); push @frame_widgets, $frame->Label( -text => "Survey Question #$i", )->pack(); push @frame_widgets, $frame->Button( -text => "First", -command => sub { button_set($first) }, )->pack( -side=>'left', ); push @frame_widgets, $frame->Button( -text => "Back to $prev", -command => sub { button_set($prev) }, )->pack( -side=>'left', ); foreach my $j (1..4) { push @frame_widgets, $frame->Radiobutton( -text => $j, -value => $j, -variable => \$button_values[$i-1], )->pack( -side => 'left', ); } push @frame_widgets, $frame->Button( -text => "Forward to $next", -command => sub { button_set($next) }, )->pack( -side => 'left', ); push @frame_widgets, $frame->Button( -text => "Last", -command => sub { button_set($last) }, )->pack( -side => 'left', ); } button_set(1); MainLoop; use Data::Dumper; print Dumper \@button_values;

        A side thought: since there's usually a preview button around when one is submitting a node for the first time, would such a button be desirable when editing a node?
        I have wished that it were so, but I have searched for discussions about this, and found much disagreement. As a workaround, you can use your scratchpad to preview anything you care to write.

        Update: ... and came across one final question.
        In future posts, please consider making this kind of update into a separate reply instead. It will be easier for other monks to follow the conversation. As it is, no one can tell for sure that my second reply was to the pre-update version of your reply, and my third reply was to the post-update version of your reply. It is clear to you and I today, but it will be less clear to future monks searching for enlightenment.

        ...have the code run, and come up simply as a rectangle that displays all the information (buttons) in one window.
        The window manager should do this automatically. After a good night's sleep, I see that I missed something in my last example. Now that we are not displaying all 30 button sets at once, we no longer need the scrolling, and so $pane can be removed, allowing $frame to become a child of $mw. After this change, the application starts up with the correct size. In diff notation:

        -my $pane = $mw->Scrolled(qw/Pane -scrollbars osow/)->pack( - -expand => 'yes', - -fill => 'both', -); -my $frame = $pane->Frame->pack(); +my $frame = $mw->Frame->pack();
        Does this do what you wanted?
Re: Some beginning Tk help
by stefp (Vicar) on Jun 22, 2002 at 17:24 UTC
    I first tried to use $pane->gridPropagate(1); before mainloop but it does not change anything. I also tried  $pane->pack(expand  => 1, fill => both ); but it worse: nothing is ever displayed. It is a long time I have no using Tk so some people surely kwow better. Good luck.

    -- stefp -- check out TeXmacs wiki