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

Hello, all.

I have the following code:
use Tk::NoteBook; $notebook = $frmNotebook->NoteBook()->pack(); my $billTab = $notebook->add('Bill', -label => 'Billing'); my $shipTab = $notebook->add('Ship', -label => 'Shipping'); my $acctTab = $notebook->add('Acct', -label => 'Accounting'); my $notesTab = $notebook->add('Notes', -label => 'Notes'); my $invoiceTab = $notebook->add('Invoicing', -label => 'Invoicing' +); #put widgets into the notebook tabs... &customersBillAddr_ui($billTab); &customersShipAddr_ui($shipTab); &customersAccounting_ui($acctTab); &customersNotes_ui($notesTab); &customersInvoice_ui($invoiceTab);
In each of the subroutines, widgets are created with private variables, as is normal (and good coding style, I think). The issue I have is accessing the child widgets in the Notebook pages. Here's another code snippet from customersBillAddr_ui:
my $mnuTerms = $root->BrowseEntry ( -textvariable => \$cust->{'terms'}, -state => 'readonly', -choices => \@items, );
How does one get at $mnuTerms without making it global? What am I missing?

Your servant,

Soko

Replies are listed 'Best First'.
Re: Tk::Notebook and scope
by hawtin (Prior) on Sep 21, 2003 at 08:33 UTC

    Another thing that is well worth knowing about is "closures". This is where a function (or even better a group of functions) share a variable that is hidden from everything else.

    So you could do something like:

    # These brackets limit the scope of the lexical variables { # So this is a global variable that can only be seen # between here and the close bracket below my(%parts); sub register_table { my($part_name,$part) = @_; $parts{$part_name} = $part; } sub get_table { my($part_name) = @_; return($parts{$part_name}); } } # So out here we can call &register_table and # &get_table but we cannot see %parts

    OK so this is a rather simplistic explination of what a closure does. It is good enough for a first level of understanding and there are lots of much better explinations on the web if you are interested

Re: Tk::Notebook and scope
by jdtoronto (Prior) on Sep 21, 2003 at 03:16 UTC
    I regularly use a global hash to store the hash variable for widgets where I need a global type of access. But there is another trick. Generally speaking the place where you want to use a widget is in the subroutines you call from that notebook page - in your example case. Create the widget without setting the -command values, then use the configure method to create commands which have the widget hashref in them, this passes the widget reference to the sub andyou can manipulate it there.

    If you want to have access to all of the widgets on a page - create a hash of references and pass that to the subs. But be careful, wait until you have created all the widgets BEFORE you start building the callbacks using the configure method.

    jdtoronto

Re: Tk::Notebook and scope
by kvale (Monsignor) on Sep 21, 2003 at 02:24 UTC
    In general, if you need a lexical variable, such as  $mnuTerms, outside of the routine that created it, simply return it upon exiting the routine and store it in a variable or hash for further processing. Because there is a reference to the returned parameter, the variable will continue to hang around after going out of scope and will retain its value. Local parameter passing can go both up and down the call chain.

    -Mark

Re: Tk::Notebook and scope
by graff (Chancellor) on Sep 21, 2003 at 02:53 UTC
    I haven't tried this myself yet, but there is a method defined by Tk::Widget ("base class of all widgets") called "children", which "returns a list containing all the children" of the widget that invokes the method. Assuming that in your snippet from "cusotmersBillAddr_ui()", the $root widget is the handle ($billTab) being passed in from the caller, then the immediate children of $billTab would include a handle for $mnuTerms (though the information available via the widget handle would not include that variable name).

    If you study the Tk::Widget man page, you should be able to work out a way to retrieve and correctly identify all the child widgets you want whose original variable names happen to be out of scope, so long as some level of parent widget is within scope.

Re: Tk::Notebook and scope
by blssu (Pilgrim) on Sep 23, 2003 at 15:18 UTC

    graff has the right idea. I've attached some tested code that puts a pretty face on it. There's probably more than you need in the code, but hopefully it's worth reading.

    First of all, you need to stop thinking in terms of the variables that you store your Tk widgets in. Tk doesn't know or care you have a variable named $mnuTerms. What Tk cares about is the widget name -- the 'Name' attribute you pass in when creating a widget. If you don't provide a name, then Tk will generate a name. Widgets always have names even if the name is something like 'button37'.

    It's useful to think of Tk as a little widget database. Names are the keys used to fetch widgets.

    The Notebook add method is a little different. The first argument is the name of the page you are adding. Notebook hides the details, but a page is really just a Frame and the name of the page is just the frame's 'Name'.

    Examples:

    my $label = $frame->Label('Name' => 'title', -text => 'Tk Names Tutorial'); my $dialog = $top->DialogBox(-title => 'Generated Names', -buttons => [ 'OK' ]); my $page = $notebook->add('Bill', -label => 'Billing');

    Using the attached direct_descendant method, you can fetch those widgets like this:

    my $title = $frame->direct_descendant('title'); my $button = $dialog->direct_descendant('bottom.button'); my $bill = $notebook->direct_descendant('Bill');

    You may need to fetch a widget with a distinctive name, but you don't know which frame it's in. The more general descendant method allows wildcards to skip over any number of frames. This is slower than direct_descendant, but it's generally fast enough if the search is started close to the widget you want to find.

    my $title = $top->descendant('*title'); my $mnuTerms = $notebook->descendant('*Bill*terms');

    But what if you don't know the widget name? This often happens because Tk doesn't force you to give widgets names. Use list_descendant_names while debugging to print out the names of all children. Use full_name to print out the complete path to a single widget.

    print join("\n", $notebook->list_descendant_names()), "\n"; print $mnuTerms->full_name(), "\n";

    You must give descriptive names to widgets that will be fetched later. If you don't, any changes to the order of widget creation will cause Tk to assign different names and that's a maintenance disaster waiting to happen.

    Hope that helped. I find this database style of Tk programming eliminates most global variables. Add in a few judicious closures for callbacks and you've got a really clean and modular application.