perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

Am trying to develop my first (maybe 1st.5) GUI in Tk. I had program working in non-object oriented mode and have been converting to object oriented (only 2.3x in size (751 lines ->1776 lines), so far; :-) ).

I seem to be running into an "oddity".

There are "handler" functions, in Tk, that can be called when events happen like keyboard or timer input.

I'm running into a "unique" problem from the object oriented structure I'm converting to. When my classes are being called from a Tk event handler, calls are not finding "base" or ancestor "methods" in handler routines. Sample error messages:

Tk::Error: Undefined subroutine &Entry_Display_In_Wins::next_rand_or_r +ecorded_file called at shown.pl line 1403. <Key> (command bound to event) at blib/lib/Tk.pm (autosplit into blib/lib/auto/Tk/Error.al) line 488 Tk::Error('MainWindow=HASH(0x667084)', 'Undefined subroutine &Entr +y_Display_In_Wins::next_rand_or_rec...', '<Key>', '(command bound to +event)') called at /usr/lib/perl5/vendor_perl/5.8/cygwin/Tk.pm line 4 +06 eval {...} called at /usr/lib/perl5/vendor_perl/5.8/cygwin/Tk.pm l +ine 406 Tk::MainLoop called at shown.pl line 1720 main::main() called at shown.pl line 1727 ---- and ---- Tk::Error: Undefined subroutine &Entry_Display_In_Wins::write_db calle +d at shown.pl line 1392. <Key> (command bound to event) at blib/lib/Tk.pm (autosplit into blib/lib/auto/Tk/Error.al) line 488 Tk::Error('MainWindow=HASH(0x667084)', 'Undefined subroutine &Entr +y_Display_In_Wins::write_db cal...', '<Key>', '(command bound to even +t)') called at /usr/lib/perl5/vendor_perl/5.8/cygwin/Tk.pm line 406 eval {...} called at /usr/lib/perl5/vendor_perl/5.8/cygwin/Tk.pm l +ine 406 Tk::MainLoop called at shown.pl line 1720 main::main() called at shown.pl line 1727
Both "write_filedb" and "next_rand_or_recorded_file" are in Base classes of the handler routines.

Relevant packages are like this

package File_Rating_DB; #pckg ONE sub write_db(){}... package Entry_Ordering; #pckg TWO our @ISA = qw(File_Rating_DB); sub next_rand_or_recorded_file() {}... package Entry_Display_In_Wins; #pckg THREE our @ISA = qw(Entry_Ordering); sub __hk_space(){ call next_rand_or_recorded_file}... sub __hk_quit(){ ...calls write_db on exit } sub handle_key($tkcontext,$dbhandle,$key_event, $key_code) {} package Timed_Display_In_Wins; #pckg FOUR our @ISA = qw(Entry_Display_In_Wins); sub _handle_timer(){}
I've disabled timer ticks for now, so only events coming in are from keyboard, but got similar error:
"Tk::Error: Undefined subroutine Timed_Display_In_Wins::write_db not found..."
when it was enabled.

As may or may not be evident, the handler routines are located in pckg's THREE and FOUR, and try to call routines in pckg's TWO and ONE.

Any special "magic" I was supposed to use to get my handling routine's "base" classes to be searched?

It is, as if is "losing" my "Base" classes as it enters into my "handler(s)" from Tk.

Focusing on the keyboard, I bind it with:

$main_win->bind('<Any-KeyPress>'=>[\&handle_key, $s,Ev('A'), Ev('K')]) +;
so the routines are called like "$main_win->handle_key($s, Ev('A'), Ev('K'))", where "$s" is the main "self" data structure/pointer.

Subroutines in the same "package" seem to be called correctly, like "handle_key->calls->hk_space, but hk_space can't find "next_rand_or_recorded" from its parent.

What am I missing? :-?
Thanks!
Linda

Replies are listed 'Best First'.
Re: Missing base classes when called from Tk
by graff (Chancellor) on Sep 27, 2007 at 03:58 UTC
    I (sort of) wish I could help you with whatever OO incantations are missing or misfiring in your code, but I can't -- I hope some other monk can help with that. I just was curious about why you're doing this...

    I had program working in non-object oriented mode and have been converting to object oriented

    Um, if it wasn't broke, why are you "fixing" it?

    (only 2.3x in size (751 lines ->1776 lines), so far; :-) ).

    It must be my own naivete regarding OO concepts (which are admittedly somewhat foreign to me still), but I would have expected that a "proper" OO design would yield fewer lines of code than an old-fashioned procedural/structured design.

    If you have a working non-OO version, and an equivalent OO version would entail writing 2 or 3 times more lines of code, what exactly is the benefit you derive from an OO solution, such that it would offset all the extra effort to create it (and presumably, with so many more lines of code, the extra maintenance load that it's likely to impose)?

      Well, working is one thing, but it was getting a bit over-sized for its britches and it wasn't in a form I wanted to enhance it in. I expect the lines / functionality ratio will go down eventually. :-) It's also a learning/educational thing.

      Like somehow I don't quite yet see how OO is supported in perl. Is the machinery to trace my back my ancestor namespaces in the perl binary or some perl code? Shouldn't it just look in the "handler (key or timer handler) routine's namespace for an @ISA? Then "it" (whatever it is), follows the package list depth-first, left-to-right? Yes? (or no...) But that sequencing doesn't seem to explain why it's not "properly" searching parent class (package) for matches after the current package when called from Tk in a callback function.

      l

Re: Missing base classes when called from Tk
by zentara (Cardinal) on Sep 27, 2007 at 16:39 UTC
    It boggles my mind when I try to convert a regular Tk script to an OO one. But make sure you use Tk::Derived and set your Super so $mw gets passed in. Look at this simplified example.
    #!/usr/bin/perl use warnings; use strict; use Tk; package ZCanTree; #without Tk::Derived the option -dooda will fail use base qw(Tk::Derived Tk::Canvas); Tk::Widget->Construct('ZCanTree'); sub ClassInit { my ($class, $mw) = @_; #set the $mw as parent $class->SUPER::ClassInit($mw); } # end ClassInit sub Populate { my ( $self, $args ) = @_; #------------------------------------------------------------------- #take care of args which don't belong to the SUPER, see Tk::Derived my $xtra_arg = delete $args->{-dooda}; #delete and read same time if( defined $xtra_arg ) { $self->{'dooda'} = $xtra_arg } #----------------------------------------------------------------- $self->SUPER::Populate($args); $self->{'can'} = $self->Canvas( )->pack(-expand=>1,-fill=>'both'); $self->Advertise( Canvas => $self->{'can'} ); print "2\n"; } sub get_dooda{ my $self = shift; return $self->{'dooda'}; } 1; package main; use Tk; my $mw = MainWindow->new; $mw->geometry("400x400"); my $canf = $mw->ZCanTree( -bg => 'black', -dooda => 42, )->pack(-fill=>'both',-expand=>1); my $button = $mw->Button( -text=>'Dooda', -command=> sub{ print $canf->get_dooda(),"\n" } )->pack(); my $button1 = $mw->Button( -text=>'Exit', -command=> sub{exit} )->pack +(); Tk::MainLoop;

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      Since this is my first Tk prog I haven't done too much converting. I think the fact that I want to do more work on the GUI is one of the driving my feeling that I want the program in an OO form before I go forward...:-)?

      I haven't gotten to the point of doing anything complicated in Tk. No objects based in Tk (yet). Right now I'm leaning toward abstracting the Tk-specific parts, toward eventually allowing other windowing systems.
      L

Re: Missing base classes when called from Tk
by graff (Chancellor) on Sep 27, 2007 at 12:24 UTC
    I tried to study up on our... so I'm sort of fishing in the dark here, but I wonder: are those four packages in the same source file? If so, what if you were to push @ISA, ...; instead of always replacing its contents via "="?

    According to the docs for "our":

    Multiple "our" declarations in the same lexical scope are allowed if they are in different packages. If they happened to be in the same package, Perl will emit warnings if you have asked for them. use warnings; package Foo; our $bar; # declares $Foo::bar for rest of lexical scope $bar = 20; package Bar; our $bar = 30; # declares $Bar::bar for rest of lexical scope print $bar; # prints 30 our $bar; # emits warning
    So maybe you want:
    package File_Rating_DB; #pckg ONE sub write_db(){}... package Entry_Ordering; #pckg TWO our @ISA = qw(File_Rating_DB); sub next_rand_or_recorded_file() {}... package Entry_Display_In_Wins; #pckg THREE our @ISA; push @ISA, qw(Entry_Ordering); sub __hk_space(){ call next_rand_or_recorded_file}... sub __hk_quit(){ ...calls write_db on exit } sub handle_key($tkcontext,$dbhandle,$key_event, $key_code) {} package Timed_Display_In_Wins; #pckg FOUR our @ISA; push @ISA, qw(Entry_Display_In_Wins); sub _handle_timer(){}
    Update: Then again, if the four packages are in the same file, maybe you only want "our @ISA" once (in package TWO) -- try leaving out the "our @ISA" in the later packages, and just push onto it.

    (As I said, I'm just guessing, but it seems worth a try, maybe?)

    BTW, regarding my earlier reply, I do know what you mean when you say the old code "wasn't in a form I wanted to enhance it in." I've had that same feeling about some of the stuff I've written, and somehow it usually seems to involve Tk scripts... Good luck!

      Well....they *are* all in one file. But @ISA should be local to each package's namespace. Not that I'm sure what effect it has (if any), but I also have brackets around each package & its code, ala:
      {package one;....} {package two; @ISA = qw(one); use base qw(one);...} {package three; @ISA = qw(two); use base qw(two);...} {package four; @ISA = qw(three); use base qw(three);...}
      I have other duplicate named, but in different package variables too. I think each @ISA is specific / package -- otherwise how can you walk the symbol table for a package to see if it ISAnother type package?
Re: Missing base classes when called from Tk
by Anonymous Monk on Sep 27, 2007 at 08:33 UTC
    so the routines are called like
    They are not. \&subroutine (or Foo::subroutine) is not Foo->subroutine
      This may be an area of confusion for me.

      When I said "the routines are called like"...I meant my "key handler routine" is called by Tk with a calling sequence that looks like: "$main_win->handle_key($s, Ev('A'), Ev('K'))", where "$s" is the main "self" data structure/pointer."

      i.e. The bind for my "handle_key" routine is:

      { use Tk; #needed for "Ev" defines next code line $retval = $main_win -> bind('<Any-KeyPress>' => [\&handle_key, $s, Ev('A'), Ev('K')]); }
      From the man page on Tk::callbacks, I see:
      Another use of arguments allows you to write generalized method +s which are easier to re-use: $a->bind("<Next>",['Next','Page']); $a->bind("<Down>",['Next','Line']); This will call $a->Next('Page') or $a->Next('Line') respectivel +y.
      From my call sequence:
      "$main_win" -> bind('<Any-KeyPress>', \&handle_key, $s, Ev('A'), Ev('K'));"
      I'd expect "handle_key" to be entered with the following in '@_':
      my ($main_win, $s, $EvA, $EvK) = @_;
      I assume "$main_win (handle of win I'm binding to) to be first, and the other 3 args should taken from my bind statement.

      I pass the key_handler routine addr to Tk via the bind, but it is from the man pages I get that they are calling &handle_key as being called through the "$main_win" handle. I pass my 1 object (at top level is a singleton, I believe the terminology is) -- that contains the "database" I'm sequencing through. The routine that has the bind statement in it, is passed "$s" ($self), which it passes in a param list to &handle_key. I then try to invoke methods on the "$self" object that is passed in via the parameter list. It is some of those "methods" that the "call" should find in Base classes but isn't (doesn't seem to be searching through the base classes at all).

      Did that clarify anything or where I might be using it incorrectly?
      Sorry for any confusion....
      L

        Tk callbacks don't respect use -> (or @ISA )