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

Hi fellows, This is my first time on PerlMonks and honestly speaking I am somewhat nervous as I do not consider myself to be a professional programmer, however, I and doing my best to learn the ropes. Maybe one day. In the meanwhile, I have started to develop a Perl Tk Gui that will ultimately output a Organisational Chart in Excel. I seem to be able to get the GUI to add numerous rows of listbox from which a user will be able to select values (to be retrieved from an Excel CSV). The only problem that I have now is to retrieve the values from all the input (i.e. all the entry and list boxes). I am stuck and would like to know how I could go about doing this. Any pointers would be greatly appreciated.
use Tk; require Tk::BrowseEntry; #use warnings; use strict; my ($entry,$f,$mw,$top,$entry2,$f1); $mw = MainWindow->new(-title => "Organizational Chart Generator"); $mw->geometry( "800x400" ); $f = $mw->Frame(-borderwidth => 2, -relief => 'groove') ->pack(-side => 'top', -fill => 'x'); $entry = $f->Label(-text=>"Division Title:")->pack(-side => "left", -anchor => "n", -fill => "x", -expand => "y"); $entry = $f->Entry( -textvariable => '', -width => 40); $entry->pack(-side => "left", -anchor => "n", -fill => "x", -expand => "y"); my $addbutton = $f->Button(-text => "Add Chart Blocks", -command => \&addwidget )->pack(-anchor => 's'); my $addbutton2 = $f->Button(-text => "Generate Chart", -command => \&genchart )->pack(-anchor => 's'); $mw->Button(-text=>'exit', -command=>sub{exit;})->pack(); MainLoop; sub addwidget { my @divisions = qw (AF AF CSPL CSPL CSO DGG DG FR FRAT OC OCX OCX + OC); my @unique; my %seen; @unique = grep { ! $seen{$_}++ } @divisions; my @s = sort {$a cmp $b} @unique; my $f1 = $f->Frame->pack(-side => 'top', -expand => 1, -fill =>'y', -before => $addbutton); # my $f2 = $f->Frame->pack(-side => 'top', -expand => 1, # -fill =>'y', -before => $addbutton2); $mw = $f1->Label(-text=>"Division Unit:")->pack( -side => "left", -anchor => "n", -fill => "x", -expand => "y"); $mw = $f1->Entry( -textvariable => '', -width => 40); $mw->pack(-side => "left"); $f1->ScrlListbox(-label => 'Right-Hand Area of Chart', -selectmode => 'multiple', -height => 5, -listvariable => \@divisions, -exportselection => 0) ->pack(-ipady => '5', -side=>'left'); $mw = $f1->Entry(-textvariable => '', -width => 40); $mw->pack(-side => "left",); $f1->ScrlListbox(-label => 'Middle Area of Chart', -selectmode => 'multiple', -height => 5, -listvariable => \@divisions, -exportselection => 0) ->pack(-ipady => '5', -side=>'left'); $mw = $f1->Entry(-textvariable => '', -width => 40); $mw->pack(-side => "left",); $f1->ScrlListbox(-label => 'Right-Hand Area of Chart', -selectmode => 'multiple', -height => 5, -listvariable => \@divisions, -exportselection => 0) ->pack(-ipady => '5', -side=>'left'); } sub genchart { #This does not work my @inds = $f1->curselection(); my @sels; foreach my $i ( @inds ){ print $f1->get( $i ), "\n"; } #I have tried this, and no luck. # print join ':', map { $_->Subwidget('entry')->get } @be; }
Debug error follows:
Tk::Error: Can't call method "curselection" on an undefined value at C +:\Users\Ettore Vecchione\Desktop\ORG CHART APP\chart.pl line 135. Tk callback for .frame.button1 Tk::__ANON__ at C:/Perl64/site/lib/Tk.pm line 251 Tk::Button::butUp at C:/Perl64/site/lib/Tk/Button.pm line 175 <ButtonRelease-1> (command bound to event)
I do not know how to pass the values from the gui to the subroutine I have named genchart(). Should I use a bind method via the Generate Chart submit button instead? I have tried to capture via the sub widget method, however, this does not work. I wonder if I should be building my widgets using the grid method as well.

Replies are listed 'Best First'.
Re: TK Gui Help
by zentara (Cardinal) on Jun 25, 2014 at 17:13 UTC
    First, ditto on the comments above. You make $f1 a global, but you create the scrolled listbox in another sub using my $f1. The $f1 won't be seen in genchart that way.

    You also don't need "use Tk::BrowseEntry" since you are using scrolled listboxes.

    Your code is pretty messed up to try and fix, but I will show you how to get the selections out of a set of scrolled listboxes. Notice in the following example, the indice count starts at 0, and to get the text of the indice you need to run $lb->get on it.

    #!/usr/bin/perl use strict; use warnings; use Tk; my $mw = MainWindow->new; $mw->geometry(q/+100+100/); $mw->title("Test Script"); #need exportselection=>0 for using multiple selection boxes my $lb0 = $mw->Listbox(-bg => 'white', -selectmode => 'extended', -exportselection=> 0 )->pack; $lb0->insert( 'end', qw/foo bar baz/ ); my $lb1 = $mw->Listbox(-bg => 'lightseagreen', -selectmode => 'extended', -exportselection=> 0 )->pack; $lb1->insert( 'end', 1..20 ); print "$lb0 : $lb1\n"; $mw->Button( -text => "Selections", -command => sub{ print "1: ", join(" ", $lb0->curselection), "\n", "2: ", join(" ", $lb1->curselection), "\n", "\n"; my @selections0 = $lb0->curselection; my @selections1 = $lb1->curselection; foreach my $select (@selections0){ print $lb0->get($select) +,"\n"}; foreach my $select (@selections1){ print $lb1->get($select) +,"\n"}; } )->pack( -side => 'left' ); $mw->Button( -text => "Done", -command => \&exit )->pack; MainLoop;

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
      Thanks for all the input. I have now gone about creating another version implementing everyone's suggestions, however, there remains the issue of capturing values from additional widgets added dynamically. I cannot foresee how many widgets (i.e. listbox or entry ) will be added by the user. And so, I would really appreciate some additional pointers regarding how to implement this kind of coding. I cannot find any suggestions based on Learning Perl/Tk nor Mastering Perl/Tk. Here is the new, yet, still defective code. I have implemented the grid method to dynamically generate the widgets. As you can see, I can retrieve values only for one row of list and entry boxes based on static assignment but nothing beyond this. Moreover, the entry boxes overlap the listbox widgets.
      use strict; use warnings; use Tk; my $lb=1; my $textboxrow = 2; my $textboxrowMid = 2; my $fieldbuttonrow = 3; my $textbox = 0; my $textbox2 = 1; my $textboxMid = 0; my $textboxMid2 = 1; my ($be,$be1, @be,@me,$label,$label2, $ent,$ent2,$entry_1,$entry_2); my $mw = tkinit; $mw -> geometry("400x300"); my $button = $mw->Button(-text => 'Add Field', -command => \&addtext +box); my $button2 = $mw->Button(-text => 'Print texts',-command => \&printte +xts); addtextbox(); MainLoop(); sub addtextbox{ my @divisions = qw (AF CSPL CSO DGG DG FR FRAT OCX OC); $label=$mw->Label(-text=>"Division Unit:")->pack(); $ent = $mw -> Entry(-textvariable => \$entry_1,) -> pack(); $be = $mw->Listbox(-bg => 'white', -selectmode => 'extended', -exportselection=> 0 )->pack; $be->insert( 'end', @divisions ); $label2=$mw->Label(-text=>"Division Unit:")->pack(); $ent2 = $mw -> Entry(-textvariable => \$entry_2,) -> pack(); $be1 = $mw->Listbox(-bg => 'white', -selectmode => 'extended', -exportselection=> 0 )->pack; $be1->insert( 'end', @divisions ); $label ->grid(-row=>$textbox++, -column=>1); $ent ->grid(-row=>$textbox2++, -column=>1); $be ->grid(-row=>$textboxrow++, -column=>1); $label2 ->grid(-row=>$textboxMid++, -column=>2); $ent2 ->grid(-row=>$textboxMid2++, -column=>2); $be1 ->grid(-row=>$textboxrowMid++, -column=>2); $button ->grid(-row=>$fieldbuttonrow++,-column=>1,-columnspan=>2); $button2->grid(-row=>$fieldbuttonrow ,-column=>1,-columnspan=>2); } sub printtexts { my @entry_1 = $ent->get(); my @entry_2 = $ent2->get(); my @values = $be->curselection(); my @values2 = $be1->curselection(); foreach (@entry_1) { print $_,"\n" } foreach my $v (@values) { print $be->get($v),"\n"; } foreach (@entry_2) { print $_,"\n" } foreach my $vv (@values2) { print $be1->get($vv),"\n"; } }
      Many thanks again.
        however, there remains the issue of capturing values from additional widgets added dynamically.

        When in doubt, just start stuffing selections into a global hash, then the selections will always be available. You get your data out by looping thru the hash keys. Every new widget gets a new hash entry, so you can add to your heart's content, keeping everything in a hash.


        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku ................... flash japh

        Why are you calling addtextbox() without arguments?

        Do it like this, write code this way, pass arguments all over, keep track of the life cycle of your variables, its coping with scoping, only use variables you declare pass, never any others

        sub GoTkGui { my $mw = tkinit; my @stackOfEntries; ## or %division or %meaningfulName SetUpTheCheeze( $mw, \@stackOfEntries ); } sub SetUpTheCheeze { my( $mw, $entriesRef ) = @_; ... $mw->Button(-text => 'Add Field', -command => [ \&addtextbox, $mw, + $entriesRef ], ); ... } sub addtextbox { my( $mw, $entriesRef ) = @_; ... ## you decide what you need want ## push @$entriesRef, $ent; ## push @$entriesRef, $entry_1; }

        Re: Tk: Creating label in hash Re: TK Submenus (Tk::Menu , global variables/ spirit of strict), Re: widget box problem in tk

Re: TK Gui Help
by RonW (Parson) on Jun 25, 2014 at 16:52 UTC

    In your code, you create an Entry widget:

    $mw = $f1->Entry( -textvariable => '', -width => 40); $mw->pack(-side => "left");

    The easiest way to get the value entered is to supply a reference to a scalar variable:

    my $f1_value; $mw = $f1->Entry( -textvariable => \$f1_value, -width => 40); $mw->pack(-side => "left");

    When the user enters or changes the text in the Entry widget, the variable $f1_value will be updated withe value entered.

    Other widgets have similar mechanisms, possibly using -variable instead of -textvariable. Some require calling a method to fetch the value, such as Text, with requires using Text::Contents to fetch the text entered. The implementations of the widgets have some inconsistencies.

    Unfortunately, you will likely have to read multiple pages of documentation to find the information you need.

Re: TK Gui Help
by Anonymous Monk on Jun 25, 2014 at 16:41 UTC

    Concerning the error: In your addwidget routine you're declaring my $f1, creating a new variable named $f1 which is local to addwidget and which hides the global $f1 you declared at the start of your program. The $f1 you're accessing in genchart is the global one, which is still undef at that point, causing the error. Try removing the my from $f1 inside addwidget.

Re: TK Gui Help
by zentara (Cardinal) on Jun 30, 2014 at 16:44 UTC
    Your code is a bit to hard for me to patch up, so I will show you some tips using the code I showed in the node above.

    First, you want to make a single Scrolled Pane to hold as many widget sets as you need. I may have misled you by making a Scrolled Pane for each widget set, but these are just demonstration scripts. :-).

    Now, for each widget set, you make a frame, add your widgets to the frame, and pack the Frames into your Scrolled Pane. To get all the data out when the selection button is pushed, loop thru all your hash keys, then insert the data into your entry boxes. You can add as many frames as you want, they will just be off in the Scrolled Pane. If you want to keep all widgets showing, you could make a few rows with a Scrolled Pane in each row. I've included a second example of doing this below, using canvases as the widget, but you can put whatever you want in there. Notice the various -expand and -fill packing options, allowing the window to be resized correctly.

    If you want to make individual buttons to load each entry separately, just add a button to each frame to just loop thru that portion of the hash.

    #!/usr/bin/perl use strict; use warnings; use Tk; use Tk::Pane; my $mw = MainWindow->new; $mw->geometry(q/600x400+100+100/); $mw->title("Test Script"); #need exportselection=>0 for using multiple selection boxes my %lbhash; # you want to use the listbox to hold your # data, so store the widget's actual object # with the name you want to give it my @choices = ('afg', 'dfg', 'erty', 'etc', 'foo','bar','wxyz'); # main Pane for all widget groups my $main_pane = $mw->Scrolled('Pane', -bg=>'black', -scrollbars=>'osoe', -sticky=>'nwse', )->pack(-expand=>1, -fill=>'both'); foreach my $choice(@choices){ # main frame for each widget set my $frame = $main_pane->Frame(-bg=>'lightblue') ->pack(-side => 'left', -expand=>1, -fill=>'y'); my $label = $frame->Label(-text=> $choice, -bg=>'hotpink') ->pack(-side=>'top'); $lbhash{ $choice }{'entry'}= $frame->Entry( -bg=>'lightyellow') ->pack(-side=>'top'); $lbhash{ $choice }{'widget'} = $frame->Scrolled('Listbox', -bg => 'white', -selectmode => 'extended', -exportselection=> 0 ) ->pack(-side=>'top', -expand=>1, -fill=>'y'); my @random_data; for(1..20){push @random_data ,int rand(100) } $lbhash{ $choice }{'widget'} ->insert( 'end', @random_data ); } $mw->Button( -text => "Selections", -command => sub{ foreach my $key( keys %lbhash ){ my @selects = $lbhash{ $key }{'widget'}->curselection; print "$key: "; my @inserts; foreach my $select (@selects){ push @inserts, $lbhash{ $key }{'widget'}->get($selec +t); } my $insert_string = join " ", @inserts; $lbhash{ $key }{'entry'}->insert(0, $insert_string); } } )->pack( -side => 'bottom' ); $mw->Button( -text => "Done", -command => \&exit )->pack; MainLoop;

    A second example showing how to load a Scrolled Pane to act like a scrollable grid.

    #!/usr/bin/perl use strict; use Tk; use Tk::Pane; my $mw = MainWindow->new; $mw->geometry('400x250'); my $mwf = $mw->Scrolled('Pane', -scrollbars=>'osoe', -sticky=>'nwse', )->pack(-expand=>1, -fill=>'both'); my %frame; # hash for holding frames for (1..4){ $frame{$_} = $mwf->Frame()->pack(-expand=>1,-fill=>'both'); } my %canv; for (0..4){ $canv{$_}{'obj'} = $frame{1}->Scrolled('Canvas', -height => 100, -width => 100, -bg =>'white', -scrollbars => 'osoe', -scrollregion => [ 0, 0, 500, 500 ], )->pack(-side =>'left' ,-padx=>10,-pady=>10, -expand=>1, -fill=>'bo +th'); $canv{$_}{'obj'}->createText(50,50, -text => $_, -anchor => 'center', ); } for (5..9){ $canv{$_}{'obj'} = $frame{2}->Scrolled('Canvas', -height => 100, -width => 100, -bg =>'lightseagreen', -scrollbars => 'osoe', -scrollregion => [ 0, 0, 500, 500 ], )->pack(-side =>'left', -padx=>10,-pady=>10 ,-expand=>1, -fill=>'bo +th'); $canv{$_}{'obj'}->createText(50,50, -text => $_, -anchor => 'center', ); } for (6..10){ $canv{$_}{'obj'} = $frame{3}->Scrolled('Canvas', -height => 100, -width => 100, -bg =>'yellow', -scrollbars => 'osoe', -scrollregion => [ 0, 0, 500, 500 ], )->pack(-side =>'left', -padx=>10,-pady=>10 ,-expand=>1, -fill=>'bo +th'); $canv{$_}{'obj'}->createText(50,50, -text => $_, -anchor => 'center', ); } for (11..15){ $canv{$_}{'obj'} = $frame{4}->Scrolled('Canvas', -height => 100, -width => 100, -bg =>'hotpink', -scrollbars => 'osoe', -scrollregion => [ 0, 0, 500, 500 ], )->pack(-side =>'left', -padx=>10,-pady=>10 ,-expand=>1, -fill=>'bo +th'); $canv{$_}{'obj'}->createText(50,50, -text => $_, -anchor => 'center', ); } MainLoop();

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh