http://www.perlmonks.org?node_id=551990

One thing I don't enjoy much is developing and maintaining Tk GUIs. Not that I have anything against Tk, it's very powerful and, for such a large, complex library, relatively bug free. Writing GUIs does tend to be donkey work though - placing a single widget involves writing 50 chars or so, and one is constantly tweaking widget placing etc.

When I discovered ZooZ a while back, I was delighted. Here was an opportunity to escape some of the donkey work. I was a little disappointed by the resulting code though - ZooZ allows you to assign variables to widgets visually, but defines the vars as globals. The problem of maintaining the code (as discussed in OO circularity) remains as well. Some of my older Tk scripts (I've just returned to a project I worked on 3 years ago) are so littered with Tk related globals & Tk widget construction in random places as to be all but impossible to maintain.

So at that point ZooZ looked like it was fine for just creating a basis for your Tk script and/or prototyping, and not much else.

Since then I've been experimenting with some alternative techniques for keeping things tidy, and worked out what I think is a pretty neat way of handling the problems that works well with ZooZ:

Here's the output from ZooZ for a simple demo script:

###########################zoozdemo.pl########################## #!perl ################## # # This file was automatically generated by ZooZ.pl v1.2 # on Sat May 27 12:43:24 2006. # Project: Project 1 # File: # ################## # # Headers # use strict; use warnings; use Tk 804; # # Global variables # my ( # MainWindow $MW, # Hash of all widgets %ZWIDGETS, ); # # User-defined variables (if any) # my $Label1content = undef; my $check1 = undef; my $check2 = undef; ###################### # # Create the MainWindow # ###################### $MW = MainWindow->new; ###################### # # Load any images and fonts # ###################### ZloadImages(); ZloadFonts (); # Widget Entry1 isa Entry $ZWIDGETS{'Entry1'} = $MW->Entry()->grid( -row => 2, -column => 0, ); # Widget Checkbutton1 isa Checkbutton $ZWIDGETS{'Checkbutton1'} = $MW->Checkbutton( -text => 'Checkbutton1', -variable => \$check1, )->grid( -row => 3, -column => 0, ); # Widget Checkbutton2 isa Checkbutton $ZWIDGETS{'Checkbutton2'} = $MW->Checkbutton( -text => 'Checkbutton2', -variable => \$check2, )->grid( -row => 4, -column => 0, ); # Widget Label2 isa Label $ZWIDGETS{'Label2'} = $MW->Label( -text => 'Label2', -textvariable => \$Label1content, )->grid( -row => 5, -column => 0, ); # Widget Label1 isa Label $ZWIDGETS{'Label1'} = $MW->Label( -text => 'Label1', )->grid( -row => 6, -column => 0, ); # Widget Button1 isa Button $ZWIDGETS{'Button1'} = $MW->Button( -text => 'Update', )->grid( -row => 5, -column => 1, ); # Widget Button2 isa Button $ZWIDGETS{'Button2'} = $MW->Button( -text => 'short loop', )->grid( -row => 6, -column => 1, ); ############### # # MainLoop # ############### MainLoop; ####################### # # Subroutines # ####################### sub ZloadImages { } sub ZloadFonts { }

After thinking about this for a while, I thought 'why not write a worker class to do all the work, that presents an interface to which the Tk script can bind'. ZooZ doesn't provide the facility to use object attributes or accessors instead of variables (although I'm hoping to either convince the author to include it, or write the extension myself), but it will forgive you if you put an attribute/accessor in the scalar variable field like this:

# Widget Label1 isa Label $ZWIDGETS{'Label1'} = $MW->Label( -text => 'Label1', -textvariable => \$worker->{labeltext}, )->grid( -row => 5, -column => 0, );

that results in a file header like this:

# # User-defined variables (if any) # my $worker->{labeltext} = ;

which has to be cleaned up later. Note that the code for assigning commands to widgets is a little less forgiving. Give it a method call like $worker->updatelabeltext and it converts it to 'main::_worker__updatelabeltext', but it's easy enough to change this in the code/write a script to replace it with the syntax you want.

Of course, there are times when you want to update the GUI dependant on the outcome of a function. For this reason I pass a reference to %ZWIDGETS into the worker class, like this:

my %ZWIDGETS; my $worker = ZooZDemo->new(\%ZWIDGETS);

and make it an attribute of the worker object.

Once you've built your GUI, export your perl code:

#!perl ################## # # This file was automatically generated by ZooZ.pl v1.2 # on Sat May 27 13:23:54 2006. # Project: Project 1 # File: # ################## # # Headers # use strict; use warnings; use Tk 804; # # Global variables # my ( # MainWindow $MW, # Hash of all widgets %ZWIDGETS, ); # # User-defined variables (if any) # my $worker->{looptext} = undef; my $worker->{check2} = undef; my $check1 = undef; my $worker->{text} = undef; my $check2 = undef; my $Label1content = undef; my $badger->getlabeltext = ; my $worker->getlabeltext = ; my $worker->{check1} = undef; my $worker->looptext = ; ###################### # # Create the MainWindow # ###################### $MW = MainWindow->new; ###################### # # Load any images and fonts # ###################### ZloadImages(); ZloadFonts (); # Widget Entry1 isa Entry $ZWIDGETS{'Entry1'} = $MW->Entry( -textvariable => \$worker->{text}, )->grid( -row => 2, -column => 0, ); # Widget Checkbutton1 isa Checkbutton $ZWIDGETS{'Checkbutton1'} = $MW->Checkbutton( -text => 'Checkbutton1', -variable => \$worker->{check1}, )->grid( -row => 3, -column => 0, ); # Widget Checkbutton2 isa Checkbutton $ZWIDGETS{'Checkbutton2'} = $MW->Checkbutton( -text => 'Checkbutton2', -variable => \$worker->{check2}, )->grid( -row => 4, -column => 0, ); # Widget Label2 isa Label $ZWIDGETS{'Label2'} = $MW->Label( -text => 'Label2', -textvariable => \$worker->{Label2}, )->grid( -row => 5, -column => 0, ); # Widget Label1 isa Label $ZWIDGETS{'Label1'} = $MW->Label( -text => 'Label1', -textvariable => \$worker->{looptext}, )->grid( -row => 6, -column => 0, ); # Widget Button1 isa Button $ZWIDGETS{'Button1'} = $MW->Button( -command => 'main::_worker__updatelabeltext', -text => 'Update', )->grid( -row => 5, -column => 1, ); # Widget Button2 isa Button $ZWIDGETS{'Button2'} = $MW->Button( -command => 'main::_worker__doshortloop', -text => 'short loop', )->grid( -row => 6, -column => 1, ); ############### # # MainLoop # ############### MainLoop; ####################### # # Subroutines # ####################### sub ZloadImages { } sub ZloadFonts { } sub _worker__doshortloop { } sub _worker__updatelabeltext { }

So all that remains is to modify it to remove the sub stubs at the end, replace the function calls with sub{$worker->methodname} and replace the global definition section with your worker instantiation:

#!perl ################## # # This file was automatically generated by ZooZ.pl v1.2 # on Sat May 27 13:23:54 2006. # Project: Project 1 # File: # ################## # # Headers # use strict; use warnings; use Tk 804; # # Global variables # my ( # MainWindow $MW, # Hash of all widgets %ZWIDGETS, ); use ZooZDemo; my $worker = ZooZDemo->new(\%ZWIDGETS); ###################### # # Create the MainWindow # ###################### $MW = MainWindow->new; ###################### # # Load any images and fonts # ###################### ZloadImages(); ZloadFonts (); # Widget Entry1 isa Entry $ZWIDGETS{'Entry1'} = $MW->Entry( -textvariable => \$worker->{text}, )->grid( -row => 2, -column => 0, ); # Widget Checkbutton1 isa Checkbutton $ZWIDGETS{'Checkbutton1'} = $MW->Checkbutton( -text => 'Checkbutton1', -variable => \$worker->{check1}, )->grid( -row => 3, -column => 0, ); # Widget Checkbutton2 isa Checkbutton $ZWIDGETS{'Checkbutton2'} = $MW->Checkbutton( -text => 'Checkbutton2', -variable => \$worker->{check2}, )->grid( -row => 4, -column => 0, ); # Widget Label2 isa Label $ZWIDGETS{'Label2'} = $MW->Label( -text => 'Label2', -textvariable => \$worker->{Label2}, )->grid( -row => 5, -column => 0, ); # Widget Label1 isa Label $ZWIDGETS{'Label1'} = $MW->Label( -text => 'Label1', -textvariable => \$worker->{looptext}, )->grid( -row => 6, -column => 0, ); # Widget Button1 isa Button $ZWIDGETS{'Button1'} = $MW->Button( -command => sub{$worker->updatelabeltext}, -text => 'Update', )->grid( -row => 5, -column => 1, ); # Widget Button2 isa Button $ZWIDGETS{'Button2'} = $MW->Button( -command => sub{$worker->doshortloop}, -text => 'short loop', )->grid( -row => 6, -column => 1, ); ############### # # MainLoop # ############### MainLoop; ####################### # # Subroutines # ####################### sub ZloadImages { } sub ZloadFonts { }

and of course the example code is no use without the demo worker class:

use strict; use warnings; package ZooZDemo; sub new { my $class = shift; my $tk = shift; my %self = ( tk => $tk, text => '', check1 => 0, check2 => 0, Label1 => '', looptext=> '' ); return bless \%self,$class; } sub doshortloop { my $self = shift; for (1..10) { print "$_"; $self->{looptext} = $_; if ($self->{tk}) { $self->{tk}->{Label1}->update(); } sleep 1; } } sub updatelabeltext { my $self = shift; $self->{Label2} = $self->{text}." - ".$self->{check1}." - ".$self- +>{check2}; if ($self->{tk}) { $self->{tk}->{Label2}->update(); } } sub getlabeltext { my $self = shift; return $self->{Label2}; } 1;
So you've now got your Tk interface defined in a script, and all the functioning parts of the code in a separate module. The time comes to make some changes. Although almost all of the Tk code is contained within a mostly autogenerated script, you've still made enough changes to it that you can't just export a new version from ZooZ and expect it to work. The solution to this is twofold: first of all as I said earlier I'm hoping to convince the author to make (or accept from me) some changes to ZooZ to make it more worker class friendly. For the time being though, the following steps will speed things up considerably:

  • Load your GUI definition from the .zooz file (see below)
  • Export it as perl code
  • Make your changes
  • Export the code again, as a new name (increment the version number perhaps)
  • diff the two scripts you've just produced to get a list of changes
  • armed with the diff file, you can quickly and easily find and make the necessary changes to your working code

ZooZ stores projects as .zooz files, and if you want to edit your GUI definition at a later date, it's that file you need to have access to. You can store it in your code repository alongside your working code, or if you aren't controlling code like that (although you should be!) you can cat>> it onto the end of your script, after an __END__ marker.

This approach is not limited to ZooZ, there's no reason why you shouldn't arrange manually created Tk scripts this way for maintainability (and for all I know, more experienced Tk programmers already do something similar - not being a heavy user of Tk I've never read any of the books on the subject). I've yet to explore some of the other facilities of ZooZ (like exporting code as a .pm file), so no doubt there are improvements to this approach that can be made. It's also worth noting that I don't write large applications in Tk - mostly just small user utilities. So far though, this approach has saved me quite a bit of effort.

--------------------------------------------------------------

"If there is such a phenomenon as absolute evil, it consists in treating another human being as a thing."
John Brunner, "The Shockwave Rider".

Can you spare 2 minutes to help with my research? If so, please click here

Replies are listed 'Best First'.
Re: Writing and maintaining Tk GUIs with ZooZ
by qumsieh (Scribe) on May 28, 2006 at 06:05 UTC
    Thanks g0n.

    I am the author of ZooZ, and to that extent am very well aware of its limitations. You bring up very good points that I have thought about before and that aren't very easily addressable. I have struggled with many such issues as I developed the program. In fact, ZooZ was written from scratch 4 times before I decided on the current format. As you probably guess, I still think it's far from perfect.

    In my opinion, and this might seem ironic, GUI builders aren't really as useful as they are hyped to be. Since it's a program, it will always try to cater to the lowest common denominator of its users, and will thus pollute the final code with a lot of unnecessary junk, which makes it really really hard to tweak manually later on. Furthermore, besides very simple widget with very well defined behaviour, it's really hard to incorporat complex widgets in a GUI builder. You can easily instantiate a canvas, for example, but it's really hard to define all the canvas bindings. For this, you need to intermix manual code with program-generated code, which is a recipe for disaster.

    That's why I think ZooZ is best used for prototyping and creating simple UI collections in the form of Perl modules. This nicely segragates machine-generated code from manually-written code. I mainly use it for that. What this means is that you can create a part of a GUI with ZooZ, and dump that as a .PM file. The .PM file will contain a Tk mega widget that you can include as:

    my $w = $mw->myZooZwidget->pack;
    I struggled with a way to let users easily control the widgets inside the ZooZ mega-widget, and resorted to the ugly hack of making everything a global var, which explains all the main::'s. My plan, going forward, is to define everything as an option of the megawidget, so that they can be accessed like other options:
    $w->configure(-label1_text => 'new text');
    you can sort of do something like that right now using Subwidget to access the internal widgets:
    $w->Subwidget('label1')->configure(-text => 'new text');
    Anyway, enough ranting for me. I'll keep all your points in mind as I think of how to extend ZooZ. To be honest, I received way many more emails about ZooZ than I expected. I didn't expect too many people to take notice of it, but I was wrong (as I often am). This is leading me to think in different directions like having a clean API for "extensions", and dumping the .zooz file in a standard XML format so that it can be generated by any other program, and perhaps having a stand-alone XML to Perl/Tk converter that uses ZooZ's "engine" to dump Perl code from .zooz files.

    As always, I'm open to suggestions. Feel free to contact me at aqumsieh@cpan.org.


    --Ala
      What this means is that you can create a part of a GUI with ZooZ, and dump that as a .PM file. The .PM file will contain a Tk mega widget that you can include as:

      my $w = $mw->myZooZwidget->pack;

      ++ to that, several times over. Now, what would be really cool would be to pass an object into the megawidget, and use that for callbacks/variables in the same way as I described in the OP. The Tk::mega constructor doesn't like more than 2 params, but that can be overloaded in the .pm file like this:

      my $worker; sub new { my ($class, $mw, $worker) = @_; my $self = $class->SUPER::new($mw); return $self; }

      Done this way the callbacks would be acting on $worker, which is in package scope, so don't need to be exported into main::

      This would entail changes to dumpPerlPM to declare objects as undef, then construct the my ($class,$mv,..,..,..) = @_ line in new with a list of the objects. From playing with this last night, I don't think the changes I sent you last week write out object attributes to .pm files correctly anyway - I'll take another look at that.

      That would allow me to create standard megawidgets associated with my classes, and maybe even in the class, create methods like this:

      sub widget { my $self = shift; my $mw = shift; if ($self->{widget}){return $self->{widget}} use autouse 'workerwidget'; my $widget = $mw->workerwidget($self); $self->{widget} = $widget; return $widget; }

      (With the autouse just in case I'm using my class on a system with no tk installed). Then, to put (for example) a list of servers with checkboxes in my window, I'd just need to do this:

      my $worker = ServerListClass->new(); my $serverlistwidget = $mv->$worker->widget($mw)->pack();

      I guess this kind of stuff in general Tk programming terms is probably covered in one of the Tk programming books. Perhaps a trip to the bookshop is called for...

      --------------------------------------------------------------

      "If there is such a phenomenon as absolute evil, it consists in treating another human being as a thing."
      John Brunner, "The Shockwave Rider".

      Can you spare 2 minutes to help with my research? If so, please click here

Re: Writing and maintaining Tk GUIs with ZooZ
by mikasue (Friar) on May 27, 2006 at 15:53 UTC
    g0n,

    This is an awesome meditation!

    I recently posted New Perl/TK Editor asking for suggestions of a TK Editor. One of the replies was to try ZOOZ. I downloaded it and created a simple sceen. The downfalls I saw in it are

    #1 it won't import the TK code i've already written.
    #2 the results of my screen were not whatI expected.
    #3 the global variables

    For 2 of the downfalls you have provided a solution. THANKS. I will try this and I look forward to trying your extention (HINT HINT).
    Thanks for writing this. It was right on time.

      hi milk! your reply is also awesome. Love you babe.
Re: Writing and maintaining Tk GUIs with ZooZ
by vkon (Curate) on May 27, 2006 at 17:54 UTC
    As you explained a problem with GUI design, you must see a link http://www.vkonovalov.ru/vtcl-usage/Using_vtcl_for_creating_Tcl-Tk_GUI_for_Perl.html

    Actually perl/Tk is quite hard to maintain and it is very unobvious on what GUI will be.
    Tcl::Tk provides many TIMTOWTDI alternatives, yet very effective, yet much larger widget set, etc.

    When I switched from perl/Tk to Tcl::Tk I never looked back and feel only improvements.

      hi

    • perl/Tk language feels very perlish
    • there is a lot of useful perl/Tk code, can it run somehow on Tcl::Tk, I suppose some effort will be put into that...or I am wrong to think that?
    • by the way the python/ruby approach was to hook into tcl and call Tk with it, which seems a good way of reusing code :) still doing that you couple your install to the one of the tcl-tk libs, less nice (and the coverity scan does not look as good as perl's) thanks

        Do you have coverity scan of perl/Tk?
        Show me

        Did you ever looked into perl/Tk source tree?
        Its a mess. First file that now I looked at, the file './VERSIONS' in perl/Tk-804.027 say
        This is Tk402.001 The name by the way is Tcl/Tk's 4.2 with three digits for me to mess w +ith. I hope Sun don't get through more than 100 revisions so two sub-versio +n digits will be enough for them. I will subsume Sun's 'patch' stuff (e.g. '3' in 4.2p2) into my digits +as we have tended to be 'ahead' of them on some fixes and behind on others so we + don't map exactly at that level. For my digits I will use the 'even'=stable, 'odd'='experimental' scheme that linux uses: .0xx - inherently 'alpha' .1xx - experimental 'beta' .2xx - stable .3xx - experimental .4xx - stable
        There are *many* such examples, just scratching the surface.

        Do you think entire perl/Tk distribution is smooth and Coverity scan will be smooth??

        About "perl/Tk language feels very perlish" - I agree.
        Tcl::Tk supports perl/Tk syntax. So TIMTOWTDI is wider in Tcl::Tk, thus Tcl::Tk is even more perlish :):)