http://qs1969.pair.com?node_id=11110970

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

Hello Monks

I need to monitor if a new element has been programmatically added to a Listbox. If a new element has been added, a subroutine should be called. Since I am not aware of any callback for this, in the script below, I try to use programmatically select a Listbox element with selectionSet (whenever a new element is added) in order to let the <<ListboxSelect>> call a subroutine. The approach does not work, since <<ListboxSelect>> seems to be called only if a Listbox element is selected by the user. Using <<Modified>> does not work too.

Any suggestion on how I could solve this?

use strict; use warnings; use Tk; my $mw = MainWindow->new(); my $lbox = $mw->Listbox()->pack(); my @list = ( "a", "b", "c", "d", "e", "f" ); $lbox->insert('end', @list ); $lbox->selectionSet('1'); #$mw->Button(-text => "Set!", -command=>sub{$lbox->selectionSet('2');} +)->pack();#to test if sectionSet works with <<ListboxSelect>> $lbox->bind('<<ListboxSelect>>', sub{warn "ListBox changed\n"} ); $mw->MainLoop;

EDIT

The code above is in Perl/Tk and not in Tcl::Tk simply because I was looking for a solution independently of Tcl::Tk, as I thought a callback triggered by a state change in the Listbox was easier/possible to achieve then working on the Tcl bridge to Perl through Tcl::Tk (or the similar Tcl::pTk)

Replies are listed 'Best First'.
Re: Tk programmatically monitor change in Listbox
by choroba (Cardinal) on Jan 05, 2020 at 20:59 UTC
    If you don't feel like patching Tk (probably somewhere here), you can check for the selection periodically using a timer:
    #! /usr/bin/perl use strict; use warnings; use Tk; my $mw = 'MainWindow'->new; my $lbox = $mw->Listbox->pack; my @list = qw( a b c d e f ); $lbox->insert(end => @list); $lbox->selectionSet(1); my @selection; $lbox->repeat(100, sub { my @current = $lbox->curselection; warn "Listbox changed!\n" if "@selection" ne "@current"; @selection = @current; }); my $f = $mw->Frame->pack; $f->Button(-text => 'Add', -command => sub { $lbox->selectionSet(2) })->pack(-side => +'left'); $f->Button(-text => 'Clear', -command => sub { $lbox->selectionClear(0, 'end') })->pack; MainLoop();
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Thank you for the idea with repeat. However, I am not sure if this will have negative implications for my app, if this is set to "run" all the time when the UI is open. I will need to test it. Probably, hacking the source will be a better solution.

Re: Tk programmatically monitor change in Listbox
by Marshall (Canon) on Jan 06, 2020 at 09:42 UTC
    Your code which I simplified below works fine.

    How would @list get modified? That is the real question here.
    You don't show any code for that.
    Do you want a right-click menu to add/delete? Do you want an Add/Delete menu bar?

    Adding a new element to the listbox means that

    my @list = ( "a", "b", "c", "d", "e", "f" );
    has been modified. Perhaps:
    my @list = ( "a", "b", "c", "d", "e", "f","g" ); or perhaps ( "a", "b", "NEW", "c", "d", "e", "f" ) or perhaps ( "a", "b", "c")

    I would be thinking of just deleting all element in the Listbox
    and then re-insert the new elements from this modified list.
    That is one way to change the contents of a Listbox.

    It would be very helpful if you could describe what the user is
    going to do that results in a different displayed Listbox contents?

    I think there is a wrong thought model here. If you change the Listbox,
    then you know you did that. Call the update Listbox routine. Or perhaps
    the window with list box, fetches new data every time is is displayed.
    My last thought would be a timed update every x seconds.

    ================================= use strict; use warnings; use Tk; my $mw = MainWindow->new(); my $lbox = $mw->Listbox()->pack(); my @list = ( "a", "b", "c", "d", "e", "f" ); $lbox->insert('end', @list ); $lbox->selectionSet('2'); #default starting selection in the box,"c" $lbox->bind('<<ListboxSelect>>', sub{warn "new ListBox element selecte +d!\n"} ); MainLoop;

      The user will add a new element (or a set of elements) to the set of elements displayed in the Listbox. This is done programmatically. The list can be recreated with no problem at any time. The problem is that this operation is done programmatically in Tcl (embedded in Perl) through Tcl.pm/Tcl::Tk inside a

      $int->Eval();#Tcl code which adds the element to the Listbox goes here

      Unfortunately, me and my collegue were not able to find a way to call a Perl subrutine from inside the Eval code at the moment of adding the new element (I guess it is not possible at all). This is the reason why I am searching for an alternative to call this subrutine when the set of elements changes. It goeas without saying that for the meoment we can not substitute the Tcl code with Perl code, which would make this operation superfluous.

      The original code posted in this thread is in Tk because I want(ed) to find a solution to my proposed approach - which is indipendent to Tcl.

        Do you need the exact changes made or do you only care about the final state? If the latter, you could simply read back the Listbox contents after the $int->Eval(); returns and call a Perl handler there.

        The original code posted in this thread is in Tk because I want(ed) to find a solution to my proposed approach - which is indipendent to Tcl.

        Here is a rule of thumb, whenever you're 100% sure the boundary you're crossing isn't relevant, but you can't write the solution -- the boundary is critical.

        Tcl::Tk needs to be in your title to attract Courage/VKON

Re: Tk programmatically monitor change in Listbox
by jcb (Parson) on Jan 06, 2020 at 01:41 UTC

    You will need to subclass Tk::Listbox and override the relevant methods.

    Trapping programmatic changes to the UI is probably a bad idea, however, and you should arrange for a notification to be processed by some other means. Tk works very well as the "V" in "MVC", so you will probably get much cleaner code if you have a method in another object that represents whatever the listbox choices are. Preserve that separation and your code will be easier to maintain. Right now, your code seems to be headed towards "tissue of hacks" and Big Ball of Mud.

    Sadly, Tk does not appear to have a mechanism for passing generic program-generated events to callbacks.

      Sadly, Tk does not appear to have a mechanism for passing generic program-generated events to callbacks.

      sure it does.

        What is it? Where is it documented? How do I register a handler for an application-specific high-level event and how do I send that event to fire the handler? Can I add other details to the event itself or do I need a separate queue for additional information?

        Thanks. You're super helpful.

Re: Tk programmatically monitor change in Listbox
by Anonymous Monk on Jan 07, 2020 at 04:54 UTC