http://qs321.pair.com?node_id=497688

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

I have a rather complex application that uses Perl::Tk to manage its GUI. I find myself quite frustrated by how cluttered and ugly the code is--all the setup and declaration of all the widgets gets rather unweildy. It occurred to me recently that I could use modules to separate out chunks of my app into different files and namespaces. For any variables that I need to share between modules, I can always use the exporter or set package globals via an intialization subroutine.

The thing is, I've looked at a lot of example code that's available online, and it all uses one big file with lots of declarations approach. No one seems to be using modules in the way I describe.

This leaves me with some burning questions:

  1. Am I missing some major reason not to use libraries to hide my widget declarations?
  2. What namespace should I put all this junk into?
  3. I am mostly familiar with OO style modules, should I stick with OO style or should I go for a classic-exporter using style? Or should I stick with what everyone else seems to be doing?

Here's an example of what I'm talking about. I don't actually set up lots of variables to store settings and define callbacks for interaction, so real code gets much more wordy.

#!/usr/bin/perl # Normal code use strict; use warnings; use Tk; my $mw = MainWindow->new; $mw->title("A phone list"); my $top = $mw->Frame()->pack( -fill => 'both', -expand => 'x', -ipadx => 5, -ipady => 5, ); my $list = $mw->Frame()->pack( -fill => 'both', -expand => 'both', -ipadx => 5, -ipady => 5, -anchor => 'n', ); $top->Label( -text => 'Enter some phone numbers.', )->pack( -side => 'top', ); $top->Label( -text => 'Check numbers to activate them.', )->pack( -side => 'top', ); $list->Label( -text => 'Enabled', )->grid( -column => 0, -row => 0, ); $list->Label( -text => 'Phone number', )->grid( -column => 1, -row => 0, ); $list->Label( -text => 'Description', )->grid( -column => 2, -row => 0, ); for ( 1..5 ) { $list->Checkbutton( -textvariable => \$nList->[$_]{enable}, -offvalue => '', -onvalue => 'ACTIVE', )->grid( -column => 0, -row => $_, ); $list->Entry( -textvariable => \$nList->[$_]{number} )->grid( -column => 1, -row => $_, ); $list->Entry( -textvariable => \$nList->[$_]{description} )->grid( -column => 2, -row => $_, ); }

Here I broke almost all the widget setup into another package. The goal here is to keep like stuff together, not really reuse.

use strict; use warnings; use PhoneList; my $mw = MainWindow->new; $mw->title("A phone List"); my $phoneNumbers = [ { number => '123-4321', enable => 'ACTIvE', description => 'Bob', }, { number => '321-1234', enable => '', description => 'Not Bob', }, ]; PhoneList::setup($mw, -numbers => $phoneNumbers;); MainLoop; ### A Different file package PhoneList; sub setup { my $parent = shift; my %args = @_; my $top = $parent->Frame()->pack( -fill => 'both', -expand => 'x', -ipadx => 5, -ipady => 5, ); my $list = $parent->Frame()->pack( -fill => 'both', -expand => 'both', -ipadx => 5, -ipady => 5, -anchor => 'n', ); $top->Label( -text => 'Enter some phone numbers.', )->pack( -side => 'top', ); $top->Label( -text => 'Check numbers to activate them.', )->pack( -side => 'top', ); $list->Label( -text => 'Enabled', )->grid( -column => 0, -row => 0, ); $list->Label( -text => 'Phone number', )->grid( -column => 1, -row => 0, ); $list->Label( -text => 'Description', )->grid( -column => 2, -row => 0, ); for ( 1..5 ) { $list->Checkbutton( -textvariable => \$args{-numbers}->[$_]{enable}, -offvalue => '', -onvalue => 'ACTIVE', )->grid( -column => 0, -row => $_, ); $list->Entry( -textvariable => \$args{-numbers}->[$_]{number} )->grid( -column => 1, -row => $_, ); $list->Entry( -textvariable => \$args{-numbers}->[$_]{description} )->grid( -column => 2, -row => $_, ); } } 1;

Update: Added readmore tags.


TGI says moo

Replies are listed 'Best First'.
Re: Name spaces and Perl::Tk coding style
by ides (Deacon) on Oct 05, 2005 at 18:21 UTC

    No you're right to break things into different modules. As for whether or not to use OO I would suggest using whatever is comfortable.

    And use whatever namespace you want. If you app is called foo.pl I'd create Foo::Widgets or something.

    I agree Tk and most GUI widget sets are unwieldy to code with. One think you might think about is using Glade and Gtk2::GladeXML. This lets you "draw" your GUI, which is defined in an XML file, that that CPAN module reads and does all of the "building" for you. You then just write the callbacks and the portion you need to modify.

    Hope this helps.

    Frank Wiles <frank@wiles.org>
    http://www.wiles.org

Re: Name spaces and Perl::Tk coding style
by chester (Hermit) on Oct 05, 2005 at 18:55 UTC
    So far as making this OO, I wrote something vaguely similar to this recently, in that it had rows of mostly identical widgets to collect user input. I decided that a "row" was a good candidate to be made a class. On a level above that, you could have a "grid" or "row collection" class that handles things like adding and removing rows to a list or grid. Each of those classes would be partially handling some widgets.

    In addition to being easier to read, the good thing about this is that in my app you can add and remove rows dynamically. So when you click a button it just calls "add_row()"; click another and it calls "remove_row()". The "grid" object would handle that, and it can make sure you don't have less than $MIN_ROWS rows or more than $MAX_ROWS rows, and keep track of the current number of rows, etc. etc.

    Depends what your requirements are though. Only use OO if it makes sense to use it.

      What I have is is a complex app that talks to up to 200+ sensors and displays the data, logs it. It also provides a simple serial terminal and an on-line help system. I am adding the ability to trigger notifications based on filtering the sensor readings. I wrote it like all the examples I saw, and despite my best efforts, it's big, ugly, and unweildy.

      For the filter creation I'm going to have to build a rule editor that allows you to specify N clauses ( <FEILD> < IS / IS LESS THAN / IS GREATER THAN > <VALUE> ) and specify whether they are combined as a conjuction (ALL) or disjunction (ANY). I think I will be making a Clause mega-widget. I think I'll use an HList to manage my clause widgets, as it provides the add_row() functionality you describe. HList is pretty cool, if you haven't used it, check it out.


      TGI says moo

Re: Name spaces and Perl::Tk coding style
by liverpole (Monsignor) on Oct 06, 2005 at 03:06 UTC
    I like your idea of using libraries to hide widget definitions.  I would even suggest going a step further, and creating wrappers to simplify some of your widget subroutines, since many of them use the same parameters.  Here's how I would be inclined to simplify the library module (note btw, that in the call to pack(), you should set the value of -expand to either 0 or nonzero; it's -fill which takes one of "none", "x", or "y", or "both"):
    ### A Different file package PhoneList; use Tk; sub placement($$) { my ($w, $placement) = @_; if ($placement =~ s/^p,(\w+),(\w+),(\w+)//) { $w->pack(-side => $1, -fill => $2, -expand => $3); } elsif ($placement =~ s/^g,(\d+),(\d+)//) { $w->grid(-column => $1, -row => $2); } else { die "Illegal placement format '$placement'\n"; } return $w; } sub myFrame($$$$) { my ($w, $ix, $iy, $placement) = @_; my $fr = $w->Frame(); return &placement($fr, $placement); } sub myLabel($$$) { my ($w, $text, $placement) = @_; my $lbl = $w->Label(-text => $text); return &placement($lbl, $placement); } sub myEntry($$$) { my ($w, $tvar, $placement) = @_; my $e = $w->Entry(-textvariable => $tvar); return &placement($e, $placement); } sub myCheckbutton($$$$$) { my ($w, $v, $b0, $b1, $placement) = @_; my $c = $w->Checkbutton(-textvar => $v, -offvalue => $b0, -onvalue + => $b1); return &placement($c, $placement); } sub setup { my $parent = shift; my %args = @_; my $top = myFrame $parent, 5, 5, 'p,top,x,1'; my $list = myFrame $parent, 5, 5, 'p,top,x,1'; myLabel $top, 'Enter some phone numbers.', 'p,top,none,0'; myLabel $top, 'Check numbers to activate them.', 'p,top,none,0'; myLabel $list, 'Enabled', 'g,0,0'; myLabel $list, 'Phone number', 'g,1,0'; myLabel $list, 'Description', 'g,2,0'; for ( 1..5 ) { my $textvar = \$args{-numbers}->[$_]{enable}; myCheckbutton $list, $textvar, '', 'ACTIVE', "g,0,$_"; myEntry $list, \$args{-numbers}->[$_]{number}, "g,1,$_"; myEntry $list, \$args{-numbers}->[$_]{description}, "g,2,$_"; } MainLoop } 1;
    And here's the main program (with a fix to the line "PhoneList::setup($mw, -numbers => $phoneNumbers;);", which had an extraneous ';' before the final parenthesis):
    #!/usr/bin/perl -w use strict; use warnings; use lib "."; use PhoneList; my $mw = MainWindow->new; $mw->configure(-title => "A phone List"); my $phoneNumbers = [ { number => '123-4321', enable => 'ACTIvE', description => 'Bob', }, { number => '321-1234', enable => '', description => 'Not Bob', }, ]; PhoneList::setup($mw, -numbers => $phoneNumbers);
Re: Name spaces and Perl::Tk coding style
by zentara (Archbishop) on Oct 05, 2005 at 18:50 UTC
    Most of the examples you see floating around here are big monolithic scripts. Mostly for simplicity. 1 file is alot easier for someone to deal with. You can keep it single file, and put your 00 code into a "package" within the script. Do whatever you like. But when code gets posted here, it is alot trickier to download 3 or 4 files, than 1 big self-contained file.

    I'm not really a human, but I play one on earth. flash japh
Re: Name spaces and Perl::Tk coding style
by strat (Canon) on Oct 06, 2005 at 07:35 UTC

    For my Tk-Apps, I often use package/module names like (if my App is called MyApp):

    • MyApp::Widgets: little wrapper around widgets with some defaults (e.g. background, font, ...) to be able to get a flexible layout and shorter widget calls. Some enhanced widgets (e.g. with Tk::Derived) may be specified here as well if the code is short, although then there is more than one package in a module...
    • MyApp::Forms: most of the GUI functionality is coded here to separate layout and code as far as possible
    • MyApp::Callbacks: callbacks are specified here; if necessary, there even may be more modules grouped by topic)
    • MyApp::DB: the interface to Databases, or MyApp::LDAP as interface to Directory Services, ... so I don't need to search through the whole program if the interface changes
    • maybe MyApp::Config: this sometimes contains the configuration in global package variables which I treat as readonly, sometimes as configuration objects or an interface to an external configuration
    • sometimes MyApp::Global for saving global variables like $Mw, $ToplevelEdit, ...

    Best regards,
    perl -e "s>>*F>e=>y)\*martinF)stronat)=>print,print v8.8.8.32.11.32"

Re: Name spaces and Perl::Tk coding style
by pg (Canon) on Oct 05, 2005 at 18:54 UTC

    A while back, I thought everything in Perl should be OO. But I have changed my mind in a way, and my thought now is to go with non-OO if Perl OO seems laborious to you. After all, you want to develop your Perl applications quick, which is one of the key point of using Perl.

Re: Name spaces and Perl::Tk coding style
by elwarren (Priest) on Oct 05, 2005 at 21:13 UTC
    You are correct, put it into modules if you can. When I wrote an app with Win32::GUI I found myself constantly frustrated with scrolling through so much of the init code. Pages and pages just to setup the windows and labels. When I put it into modules it was easier to work with, but then I went overboard. I started trying to make it too much of a general purpose module for GUI apps, it got too complex, and got pushed back to the end of my "to work on later" list, and I haven't thought about it in years.

    Ah, so much wasted code, never to see the light of day...

      I've been bitten by the excessive generality bug a few times as well. It's the easiest way to never finish a project. I've done in a few projects this way myself--on the good side, I learned a lot while doing it.

      My app is currently weighing in at around 3000 lines of code (including whitespace and comments, but not counting the libraries I wrote for backend stuff). It's just too damn big to navigate around easily. Amazingly, it is maintainable, I was able to get in and make some changes after 8 months away from working on it without too much pain. Let's hear it for good documentation practices.

      Any thoughts on the namespace issue? I'm planning on keeping the libraries in the same directory with the script, so top level names are OK. I'm leaning towards MyApp::PhoneList and MyApp::DisplayData and so forth.


      TGI says moo

Re: Name spaces and Perl::Tk coding style
by duelafn (Parson) on Oct 10, 2005 at 15:04 UTC

    One can also (is supposed to (?)) create a Tk::composite object.

    package myApp::MyWidget; use strict; use warnings; use base qw/Tk::Frame/; our $VERSION = 0.1; Construct Tk::Widget 'MyWidget'; =pod =head1 NAME myApp::MyWidget - Widget for use in myApp =head1 SYNOPSIS use strict; use Tk; use myApp::MyWidgeet; my $mw = new MainWindow; my $widget = $mw->MyWidget( %options ); $widget->pack; =cut sub Populate { my($self, $args) = @_; $self->SUPER::Populate($args); my $top = $self->Frame()->pack( -fill => 'both', -expand => 'x', -ipadx => 5, -ipady => 5, ); # ... Other stuff ... # $self->ConfigSpecs( # ... # ); # $self->Delegates('DEFAULT' => $scale); } 1;

    The ConfigSpecs and Delegates control which options/methods go to which subwidgets. There is also a Component method that can be called that can give you access to the subcomponents (such as your checkboxes), or you can create custom methods that process the information from the form before returning it (which is probably the better route anyway).

    Good Day,
        Dean