Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

TK Placing Widgets in a Scrolling Pane

by saiftynet (Initiate)
on Dec 02, 2021 at 22:28 UTC ( [id://11139336]=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks,

I am learning Perl and GUI development, and working currently on code that will work on multiple toolkits (Tk, Wx, Gtk etc). I need help with Tk. For my purposes I have to place all my widgets (rather than pack->() them). I wish to create a fixed sized, fixed position scrolling Listbox, to be later populated by other items. This works perfectly well if I insert strings, I appear not to be able to insert other widgets (e.g Checkbuttons).

#!/usr/bin/perl use strict; use Tk; my $mw = MainWindow->new; $mw->geometry('400x250'); my $canvas = $mw->Canvas( -bg => 'lavender', -relief => 'sunken', -width => 400, -height => 250)->pack(-expand => 1, -fill => 'both'); my $size=[200,200]; # size of the widget my $location=[50,12]; # position of widget my @strings2 = qw/apples bananas pears grapes/ x 5; $canvas->{"listbox"}=$mw->Scrolled("Listbox", -bg => 'white', -scrollbars => "e", -selectmode => 'extended', -width => (${$size}[0])/7, # some scaling -height => (${$size}[1]+12)/15); # scaling $canvas->{"listbox"}->insert('end',@strings2); # This works $canvas->createWindow(${$location}[0] ,${$location}[1], -anchor => "nw", -window => $canvas->{"listbox"}); MainLoop();
Have searched many places, and most suggest I should use pack->(), but I guess mixing pack and place does not work, and I am committed to use pack for my use-case. Your guidance will be gratefully received,

saif

Replies are listed 'Best First'.
Re: TK Placing Widgets in a Scrolling Pane
by kcott (Archbishop) on Dec 03, 2021 at 17:12 UTC

    G'day saif,

    Welcome to the Monastery.

    The code you've posted works fine. If that's intended as an example of what the GUI should ultimately look like, that's great; however, it doesn't tell us what problems you're encountering with your actual development using Tk::place. Please provide another script — that works poorly; or perhaps not at all — so that we know how we can help.

    Now, although that code works as is, it does have some problems. In addition, some of your assumptions seem questionable. Given "I am learning Perl and GUI development', that's perfectly understandable. Please go through the following list and apply the various points to your new script.

    • Using strict is good; not using warnings is bad. See "Safety net", saiftynet :-)
    • In Tk::Listbox' description, you'll see: "A listbox is a widget that displays a list of strings, one per line.". If you add something other than a string, it will be stringified; no warning is emitted.
      • For example, if you add a Tk::Frame object, you'll get a tcl/Tk path (.frame); an undef will render as a blank entry in the list; \*STDERR will appear as something like GLOB(0xhhhhhhhhh); and so on. Arrayrefs are a bit of a curiosity rendering what I believe is some sort of tcl array format: [1,2,3] becomes 1 2 3; [1,[2,3,[4,5,6],7,8],9] becomes 1 {2 3 {4 5 6} 7 8} 9. Have a play around with that if you're interested.
      • Have a look in Tk::DItem. There's links to a number of list widgets that are probably more suitable for your needs. Also see the Widget Demo for working examples and full code.
    • $canvas->{"listbox"} is a huge no-no!
      • You are taking a Tk::Canvas object:
        $ perl -E 'use Tk; my $mw = MainWindow::->new(); say $mw->Canvas' Tk::Canvas=HASH(0x8003cb500)
        You then create a new, or perhaps even overwrite an existing, "listbox" key which you populate with a Tk::Frame object; yes, that's correct, not a Tk::Listbox object as your naming suggests:
        $ perl -E 'use Tk; my $mw = MainWindow::->new(); say $mw->Listbox' Tk::Listbox=HASH(0x8006859e8) $ perl -E 'use Tk; my $mw = MainWindow::->new(); say $mw->Scrolled("Li +stbox")' Tk::Frame=HASH(0x800647820)
      • You have broken encapsulation and are dealing directly with the internal implementation of the object. You should only ever use an object's published interface! This is fundamental and applies to all objects. At anytime, the author of any class can change the implementation without notice — the object type could change; the object's internal structure could be rearranged; invalid or unknown keys might be suppressed; the key or index you thought was unused may be added later — while still keeping the published interface.
      • It would have been simpler, less work, and not error-prone to have written something like:
        my $scrl_listbox = $mw->Scrolled("Listbox", ...);
        and then just used $scrl_listbox everywhere you currently have $canvas->{"listbox"}.
      • My apologies if that section came across as an angry rebuke. I know you're learning and that was not my intent. It is, however, important that you understand that you don't interfere with the internal implementation, and only access it through the published interface.
    • You don't need to write (${$varname}[$index]). It's verbose and doesn't help readability. That would have been much better simply as $varname->[$index]. The same would apply to other code like (${$varname}{$key}) vs. $varname->{$key}.
    • Hard-coding values is generally a bad move, especially when those values appear more than once. If they need changing, will you remember to change them all? If, for instance, you have 12 in multiple places, and they have a variety of purposes — indices, sizes, durations, and so on — how easy would it be to identify which 12s to change? If you see $some_array[12], how easy will it be for you to remember what value is held by the 13th index? Why would you want to make this rod for your back?
      • A better course of action would be to give them names and only store the value once. There are a number of ways to do this, hashes and the constant pragma are often good choices.
    • The title has "... Scrolling Pane". Have you seen Tk::Pane?
    • You can use more than one geometry manager in a single Tk application; in fact, it's often quite useful. Your general layout might have frames managed by Tk::pack and, within some of those, you might have elements managed by Tk::grid.
      • You mentioned mixing Tk::pack and Tk::place, but gave no indication of what you were planning, so I can't really comment further on that. Your new script should indicate how you were going to mix those two.

    Overall, for someone just learning, that's a pretty good effort.

    — Ken

      Learned Brother Ken,

      Thank you so much for the insight. I code poorly, largely because I borrow code from here and there and crowbar some functionality more by chance than intelligent design. Your teachings are helpful and will probably completely alter my code for the better.

      The rather bizarre looking $canvas object was from a progressive overloading of an initially simple object that accumulated more and more functionality. I am, I am ashamed to say, the author of GUIDeFATE; GUIDeFATE results in the production of a GUI, returning a single object that can be used to query/manipulate all the widgets, as well as being able to manipulate the canvas itself. You will immediately get the impression that my failings have already permeated widely in the code affecting multiple sub-modules as I struggled to get a common method for managing multiple different back-end toolkits.

      My goal had been to be able to generate a scrolling pane of a predetermined size, placed in a specific location in the window. (hence the need for "place"). This was then to be populated by Tk::Checkbuttons or their equivalent in other toolkits...but if I use relative positioning to place these widgets, they leak out of the Pane widget, rather than remain contained somewhere in the scrollspace of the pane (perhaps unsurprisingly). The vast majority of the examples I have seen on the interweb use ->pack() to produce a list of checkbuttons

      But I am grateful for your wisdom. I know I have to do a major rewrite but the fear is great

      saif

        Hi Saif,

        Thank you for providing runnable code! But I am still not sure exactly what you are trying to achieve?

        I think you "short-sell" pack(). This the most commonly used geometry manager. The more complex ones like grid() are less commonly used. I have never used Canvas.

        When using pack(), you need to think about packing Frames within Frames. Think of a Frame as a widget unto itself. The most common mistake when using pack() is to not use enough Frames.

        In the below code, I did have trouble getting the required minimum amount of "blank violet colored space" on the screen. But I haven't coded an Tk in a long time. The main idea with pack, is that if you want your list box to have some indentation from the left, or perhaps to center it on the screen, make a Frame which contains a "dummy" spacer frame, then your Listbox and pack appropriately (often when using enough frames, the default pack will be fine).

        When using pack() with some complicated GUI object that needs multiple widgets to "line up" in a vertical sense, get that working say in simple main window Frame on the left hand side of the screen. Then instead of using the mainWindow, put that stuff into a separate Frame and pack that Frame probably within a Frame within the mainWindow.

        #!/usr/bin/perl use strict; use Tk; my $mw = MainWindow->new; #$mw->geometry('400x250'); #some issues with this... #see my post my $FirstFrame = $mw->Frame( -bg => 'lavender', -relief => 'sunken', -width => 400, -height => 250)->pack(-expand => 1, -fill => 'both'); my $spacer_frame = $FirstFrame->Frame( -width =>10, # ADJUST THIS TO SEE WHAT IT DOES )->pack(-side => 'left'); my $listbox = $FirstFrame->Scrolled ('Listbox', -bg => 'white', -scrollbars => "e", -selectmode => 'extended', -width => 10, -height => 5, )->pack(-side=>'left'); my @strings2 = qw/apples bananas pears grapes/ x 5; $listbox->insert('end',@strings2); MainLoop();
Re: TK Placing Widgets in a Scrolling Pane
by tybalt89 (Monsignor) on Dec 03, 2021 at 21:29 UTC

    Here's my guess for what I think you are saying you want.
    Whether it's actually what you want ???

    #!/usr/bin/perl use strict; use warnings; use Tk; use Tk::Pane; my $mw = MainWindow->new; $mw->geometry('400x250'); my $frame = $mw->Frame( -bg => 'lavender', -relief => 'sunken', -width => 400, -height => 250, )->pack(-expand => 1, -fill => 'both'); my $pane = $frame->Scrolled( Pane => -scrollbars => 'e', -sticky => 'n +sew', )->place( -x => 50, -y => 12, -width => 200, -height => 200); my @strings2 = qw/apples bananas pears grapes/ x 5; $pane->Checkbutton(-text => $_, -anchor => 'w', )->pack(-fill => 'x') for @strings2; MainLoop();
Re: TK Placing Widgets in a Scrolling Pane
by Anonymous Monk on Dec 03, 2021 at 18:09 UTC
    You could use Tk:Pane and put multiple canvases where you want, and fill them as you want. Here is something to look at.
    #!/usr/bin/perl use strict; use Tk; use Tk::Pane; my %frames; my %subframes; my %labels; my %buttons; my $mw = MainWindow->new; my $pane = $mw->Scrolled("Pane", -scrollbars => 'se', -sticky => 'nsew', -bg => 'black', -width => 300, -height => 300 )->pack(-expand => 1, -fill => 'both'); my $realpane = $pane->Subwidget('scrolled'); my $xbar = $pane->Subwidget('xscrollbar'); my $ybar = $pane->Subwidget('yscrollbar'); $ybar->configure(-background=>'blue', -activebackground=>'blue', -troughcolor => 'black', -width=> 50); $xbar->configure(-background=>'green', -activebackground=>'green', -troughcolor => 'black', -width=> 50); foreach my $row (0 .. 3) { $frames{$row} = $pane->Frame(-bg => 'black')->pack( -padx => 5, -pady => 5, -expand => 1, -fill => 'both', -anchor => 'nw', ); foreach my $col (0 .. 3) { createTile($row, $col); } } MainLoop; sub createTile { my ($row, $col) = @_; $subframes{$row}{$col}{'mainframe'} = $frames{$row}->Frame( -bg => 'black')->pack( -side => 'left', -padx => 5, -pady => 5, -expand => 1, -fill => 'both', -anchor => 'nw', ); my $frame = $subframes{$row}{$col}{'mainframe'}; my $sc = $frame->Scrolled('Canvas', -scrollbars => 'osoe', -scrollregion => [0, 0, 1200, 1200], -width => 125, -height => 125, -background => randColor() )->pack(-fill=>'both',-expand => 1, -side =>'top'); my $count = int(rand(6)) + 2; foreach my $i (1 .. $count) { my ($x, $y) = (randNumber(), randNumber()); my $size = randNumber(); $sc->createRectangle($x, $y, $x+$size, $y+$size, -fill => randColor(), -outline => 'black', -width => 2 ); } #miniframe for labels $subframes{$row}{$col}{'subframe_a'} = $frame->Frame( -bg => 'black')->pack(-side => 'top',-expand=> 1,-fill=>'bo +th'); #mini frame for buttons $subframes{$row}{$col}{'subframe_b'} = $frame->Frame( -bg => 'black')->pack(-side => 'top',-expand=> 1,-fill=>'bo +th'); #labels $labels{$row}{$col}{1} = $subframes{$row}{$col}{'subframe_a'}->Labe +l( -text => "$row - $col" , -bg => 'black', -fg => 'green', )->pack(-side => 'left', -padx => 5); $labels{$row}{$col}{2} = $subframes{$row}{$col}{'subframe_a'}->Label +( -text => "$row - $col" , -bg => 'black', -fg => 'hotpink', )->pack(-side => 'right', -padx => 5); #buttons $buttons{$row}{$col}{1} = $subframes{$row}{$col}{'subframe_b'}->Butt +on( -text => " Zoom Out ", -command => sub { $sc->scale(qw/all 0 0 .5 .5/); } )->pack(-side => 'left', -padx => 5); $buttons{$row}{$col}{2} = $subframes{$row}{$col}{'subframe_b'}->Butt +on( -text => " Zoom In", -command => sub { $sc->scale(qw/all 0 0 2 2/); } )->pack(-side => 'right', -padx => 5); } sub randColor { my @colors = qw(red yellow blue orange green purple); return $colors[rand($#colors + 1)]; } sub randNumber { my ($max, $min) = (100, 10); my $size = int(rand($max)); $size += $min if $size < $min; return $size; }
Re: TK Placing Widgets in a Scrolling Pane
by Anonymous Monk on Dec 03, 2021 at 18:23 UTC
    Hi, here is another way to put checkboxes with your text. There is a small mismatch in the scrolling, but you should be able to fix it.
    #!/usr/bin/perl use warnings; use strict; use Tk; my @List = (0..50); my @cbvalue; my @cbs; my $mw = tkinit; $mw->geometry("400x400+100+100"); $mw->fontCreate('big', -weight=>'bold', -size=> 14 ); my $button = $mw->Button(-text => 'show selected', -command => \&show_selected, -bg => 'yellow', -font => 'big', )->pack(); my $pane = $mw->Scrolled('Pane',-bg=> 'lightblue') ->pack( -expand => 1, -fill => 'y' ); my $canvas = $pane->Canvas( -bg => 'white', -width => 400, -height => 800, )->pack(-expand => 1, -fill => 'both'); foreach my $i ( 0 .. $#List ) { $cbs[$i] = $canvas->Checkbutton( -text => "Number $List[$i]", -onvalue => 1, -offvalue => 0, -variable => \$cbvalue[$i], -font => 'big', -bg => 'lightseagreen', )->pack(); $cbvalue[$i] = 0; #initialize selections to off } MainLoop; sub show_selected { my @selected; foreach my $i ( 0 .. $#List ) { if ($cbvalue[$i] == 1){push @selected, $i} } print "selected: @selected\n"; }
      Fix scroll region
      my $pane = $mw->Scrolled('Pane',-bg=> 'lightblue') ->pack( -expand => 1, -fill => 'both' );

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (7)
As of 2024-04-18 05:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found