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

Hello

Giving a script as simple as this

use strict; use warnings; use utf8; use Tk; my $mw = tkinit(); my $name = 'bound to entry'; my $label = $mw->Label( -text => 'Enter:', )->grid( -row => 0, -column => 0, ); my $entry = $mw->Entry( -textvariable => \$name )->grid( -row => 0, -column => 1, ); my $button = $mw->Button( -text => 'Clear', -command => sub{ $name = ''; }, )->grid( -row => 2, -column => 0, -columnspan => 2, ); $mw->MainLoop();

how can a call a subroutine if $name is programmatically changed? (In this case by clicking of the button, but in my real case it is changed from a third party component.

Replies are listed 'Best First'.
Re: call subroutine if scalar is changed
by haj (Vicar) on Jun 08, 2019 at 12:22 UTC

    I'd go for Tie::Scalar for such a case. It is part of the core modules.

    In your class based on Tie::Scalar you then override the STORE method with one which calls your routine, and then the original STORE from the base class. perltie gives an overview about the technique.

Re: call subroutine if scalar is changed
by haukex (Archbishop) on Jun 08, 2019 at 12:26 UTC

    I see haj made the same suggestion as I was working on the following code: you can use tie to tie a scalar to a class, where you can customize the "fetch" and "store" events. See also perltie and Tie::Scalar. I tried this with your sample code and it works there as well.

    use warnings; use strict; { package Tie::Scalar::Callbacks; sub TIESCALAR { my $class = shift; my %self; @self{qw/ val store_cb fetch_cb /} = @_; return bless \%self, $class; } sub FETCH { my $self = shift; $self->{fetch_cb}->($self->{val}) if $self->{fetch_cb}; return $self->{val}; } sub STORE { my $self = shift; my $val = shift; $self->{store_cb}->($self->{val}, $val) if $self->{store_cb}; $self->{val} = $val; }; sub DESTROY { %{shift()}=() } } tie my $name, 'Tie::Scalar::Callbacks', "foo", sub { print "storing <$_[1]>, was <$_[0]>\n" }; print "name is $name\n"; $name = "bar"; print "name is $name\n"; __END__ name is foo storing <bar>, was <foo> name is bar

    Minor update to constructor.

      What I am doing wrong combining the two scripts?

      use Tk; my $mw = MainWindow->new(); { package Tie::Scalar::Callbacks; sub TIESCALAR { my $class = shift; my %self; @self{qw/ val store_cb fetch_cb /} = @_; return bless \%self, $class; } sub FETCH { my $self = shift; $self->{fetch_cb}->($self->{val}) if $self->{fetch_cb}; return $self->{val}; } sub STORE { my $self = shift; my $val = shift; $self->{store_cb}->($self->{val}, $val) if $self->{store_cb}; $self->{val} = $val; }; sub DESTROY { %{shift()}=() } } tie my $name, 'Tie::Scalar::Callbacks', "foo", sub { print "Entry value has changed!\n" }; my $label = $mw->Label( -text => 'Enter:', )->grid( -row => 0, -column => 0, ); my $entry = $mw->Entry( -textvariable => \$name )->grid( -row => 0, -column => 1, ); print "name is $name\n"; $name = "bar"; print "name is $name\n"; MainLoop;

      (Note that I took away the button as I only a simulation of the third party component changing the scalar value)

        You're changing the value before the main loop has been entered. Modify the final paragraph to
        $mw->after(1000, sub { print "name is $name\n"; $name = "bar"; print "name is $name\n"; }); MainLoop;
        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        What I am doing wrong combining the two scripts?

        Nothing, as far as I can tell, other than that in this code you aren't changing $name once the main loop has been entered, as choroba pointed out.

        a simulation of the third party component changing the scalar value

        If it doesn't work with this third party component, then probably that is the culprit, and you'd have to tell us more about it and provide an SSCCE that reproduces the problem.

Re: call subroutine if scalar is changed
by ikegami (Patriarch) on Jun 08, 2019 at 23:56 UTC

    tie is overkill. Use Variable::Magic.

    use Variable::Magic qw( wizard cast ); my $wiz = wizard( set => sub { print "Now set to ${$_[0]}.\n" }, ); cast my $name, $wiz;

      Now that is interesting.

      Thanks for a new rabbit hole that I didn't have time for :)

      I suppose this could be used to couple two variables @a=@$a or vice versa $a=@$a such that the coupling survives new assignments and the partner is destroyed as soon as the original is? 🤩

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        Yes, though I think it would be easier with tie.

Re: call subroutine if scalar is changed
by choroba (Cardinal) on Jun 08, 2019 at 12:25 UTC
    You can schedule a periodic check for the value using repeat:
    { my $previous_name = $name; $mw->repeat(200, sub { if ($name ne $previous_name) { warn "$previous_name -> $name"; } $previous_name = $name; }); }
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]