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

Hi Monks

I am having troubles in dynamically creating Tk entries fields tied to values from a hash and updating such entries when the hash changes. I have created a simple script to show the problem. After clicking on "Update" the hash is updated, but no changes in the Tk fields. What am I doing wrong?

use strict; use warnings; use Tk; my $mw = tkinit; $mw -> geometry("200x200"); our %MyValues=("1", "term1", "2", "term2", "3", "term3"); for my $DBcolumn (1..3) { $mw->Entry(-textvariable => \$MyValues{$DBcolumn},-justify +=>"left",-width => 25)->pack(-side => 'top', -anchor => 'w'); } my $button1 = $mw->Button(-text => 'Update Values', -command => +\&LoadNewValues)->pack(); my $button2 = $mw->Button(-text => 'Print Out Content', -command + => \&PrintOutContent)->pack(); MainLoop(); sub PrintOutContent{ print "$MyValues{1} $MyValues{2} $MyValues{3}\n"; } sub LoadNewValues{ %MyValues=(); %MyValues=("1", "hallo", "2", "ok", "3", "fine"); }

Replies are listed 'Best First'.
Re: Updating Tk Entries with Hash
by AnomalousMonk (Archbishop) on Dec 31, 2016 at 19:13 UTC
    sub LoadNewValues{ %MyValues=(); %MyValues=("1", "hallo", "2", "ok", "3", "fine"); }

    The basic problem with this  LoadNewValues() function from the OPed code is that it assigns a new set of referents to the global  %MyValues hash, but it does not change the referents originally assigned as  -textvariable references in the  $mw->Entry(-textvariable => ...) statements. (I hope I've made myself clear on this point; references are tricky.)

    Using something like your original approach, this works as I expect you expected it would:

    c:\@Work\Perl\monks>perl -le "use warnings; use strict; ;; use Tk; ;; my $mw = tkinit; $mw->geometry('200x200'); ;; our %MyValues = qw(1 term1 2 term2 3 term3); ;; for my $DBcolumn (1..3) { $mw->Entry( -textvariable => \$MyValues{$DBcolumn}, -justify => 'left', -width => 25, )->pack(-side => 'top', -anchor => 'w'); } ;; my $button1 = $mw->Button( -text => 'Update Values', -command => \&LoadNewValues, )->pack(); ;; my $button2 = $mw->Button( -text => 'Print Out Content', -command => \&PrintOutContent, )->pack(); ;; MainLoop(); ;; sub PrintOutContent { print qq{$MyValues{1} $MyValues{2} $MyValues{3}}; } ;; my $inc_them; sub LoadNewValues { if ($inc_them++) { $MyValues{$_}++ for qw(1 2 3); } else { @MyValues{ qw(1 2 3) } = qw(hallo ok fine); } } " term1 term2 term3 hallo ok fine hallp ol finf hallq om fing

    Update: The code using  configure() that you posted here works fine, but does not address the basic conceptual problem with handling references that lurks in the OPed code.


    Give a man a fish:  <%-{-{-{-<

Re: Updating Tk Entries with Hash
by Marshall (Canon) on Dec 31, 2016 at 22:44 UTC
    First, I agree with the post from AnomalousMonk.
    The problem that you are having relates to referants (references).
    The OP'ed code will work with this modification to LoadNewValues() subroutine:
    sub LoadNewValues { @MyValues{qw(1 2 3)}=("hallo", "ok", "fine"); }
    More usual in my code would be passing a reference to the "hash of button values" to the udating subroutine instead of using a globally scoped variable:
    my $button1 = $mw->Button(-text => 'Update Values', -command => sub{ +LoadNewValues(\%MyValues)})->pack(); sub LoadNewValues { my $href = shift; $href->{1} = "hallo"; #hash slice syntax also possible $href->{2} = "ok"; $href->{3} = "fine"; }
    I am wondering about your use of "our"? i.e., our %MyValues=("1...")
    I don't know what you think this does or why you used it. However, in this example, there is no reason to do that at all. A "normal" lexically scoped "my" variable at the highest level will also have global scope.

    An "our" variable goes into the global namespace. This is used for 2 main reasons: (a) To allow one separately compilied package to address a variable in another and (b) to allow "localization" of a variable, local $x;. Neither of these 2 things apply in your code.

    I would not use an "our" variable when a "my" variable would suffice.

Re: Updating Tk Entries with Hash
by kcott (Archbishop) on Jan 01, 2017 at 00:56 UTC

    As a general rule, keep your GUI construction code lexically separated from your GUI processing. This will (usually) prevent you from running into these sorts of problems. It will also help in avoiding monolithic GUI scripts where global variables and action-at-a-distance become the bane of your existence: you could, for instance, put your subroutines in modules where they were tested independently and perhaps reused by any number of other scripts.

    Both ++AnomalousMonk and ++Marshall have made excellent points[1, 2] regarding the use of references. As a guiding principle, whenever you use the -command option with a function that requires arguments, pass those arguments as references.

    In the code below, I've reduced your "simple script" to a very bare-bones version. Notice the anonymous block that lexically isolates %db_data from the subroutines: they have no knowledge of it; can't directly access it; and wouldn't know the difference if you passed \%a_different_set_of_db_data as an argument.

    #!/usr/bin/env perl -l use strict; use warnings; use Tk; { my %db_data = qw{1 term1 2 term2 3 term3}; my $mw = MainWindow::->new; $mw->Entry(-textvariable => \$db_data{$_})->pack for sort keys %db +_data; $mw->Button(-text => 'Print', -command => [\&print_vals, \%db_data +])->pack; $mw->Button(-text => 'Load', -command => [\&load_vals, \%db_data +])->pack; } MainLoop; sub print_vals { print "@{$_[0]}{1..3}" } sub load_vals { @{$_[0]}{1..3} = qw{X Y Z} }

    I tested by: printing the initial values; changing the values of 1 then 3 then 2 (intentionally out of order) and printing after each individual change; loading new values and printing; and, finally, changing all values first and then printing once. Here's the output:

    term1 term2 term3 A term2 term3 A term2 C A B C X Y Z D E F

    — Ken

Re: Updating Tk Entries with Hash
by Anonymous Monk on Dec 31, 2016 at 12:49 UTC

    After some thinking, I came up with the following working solution.

    use strict; use warnings; use Tk; my @w; my $mw = tkinit; $mw -> geometry("200x200"); our %MyValues=("1", "term1", "2", "term2", "3", "term3"); for my $DBcolumn (1..3) { $w[$DBcolumn]=$mw->Entry(-textvariable => \$MyValues{$DBco +lumn},-justify=>"left",-width => 25)->pack(-side => 'top', -anchor => + 'w'); } my $button1 = $mw->Button(-text => 'Update Values', -command => +\&LoadNewValues)->pack(); my $button2 = $mw->Button(-text => 'Print Out Content', -command + => \&PrintOutContent)->pack(); MainLoop(); sub PrintOutContent{ print "$MyValues{1} $MyValues{2} $MyValues{3}\n"; } sub LoadNewValues{ %MyValues=(); %MyValues=("1", "hallo", "2", "ok", "3", "fine"); for my $DBcolumn (1..3) { $w[$DBcolumn]->configure(-textvariable=>\$MyValues{$DBcolumn}); } }
Re: Updating Tk Entries with Hash
by Anonymous Monk on Jan 02, 2017 at 10:33 UTC

    Thank you so much for all the precious comments. Stuff for thoughts, learning and improving!