Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Structure of a large Perl GUI application

by DarrenM (Initiate)
on Feb 11, 2009 at 16:40 UTC ( #743105=perlquestion: print w/ replies, xml ) Need Help??
DarrenM has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I`m trying to merge some large scripts into a PerlTK GUI application, so the GUI drives the scripts basically but also leaving the application expandable in the future. I was thinking about having a main script that builds the GUI (Windows, menu`s etc). And then when menu`s are selected, calls are made to routines in PM`s that are kept in seperate scripts. The scripts currently are broken down into subroutines so this would be relatively easy. However, the GUI application uses a progress bar and I`ll need to write information back to the the GUI application from the routines in the modules. Is this possible? So if I define a progress bar in the main script where the GUI is built, will I be able to refer to it from the routines within the modules that are called from the main script? Or alternatively, can I have some ideas on better ways of doing it or point me to a resource or samples I can have a look at. I`ve tried to search for examples but I only find small simple Perl TK scripts. I`m building a GUI application that has the potential to grow quite large. Some of the menu items will execture a lot of code for example (mathematical code that also generates graphs ). If I were to put it all in one script which Iknow I could do, it could prove very hard to read/maintain etc. Thanks for any help

Comment on Structure of a large Perl GUI application
Re: Structure of a large Perl GUI application
by zentara (Archbishop) on Feb 11, 2009 at 17:28 UTC
    I'm not the best at breaking things down into modules, but I may offer one bit of advice. You can have a single script be your starting point defining a $mw, with all the various helper scripts, put into modules, that accept the $mw as it's SUPER. Look at my simple module Tk::CanvasDirTree for how to use it. You can make as many of your own modules as you want,using my module as a model, and put update methods in them, then call $mymod->update($values) from your main script.
    use Tk::widgets qw/Canvas/; use base qw/Tk::Derived Tk::Canvas/; sub ClassInit { my ($class, $mw) = @_; $class->SUPER::ClassInit($mw); $mw->bind($class, "<1>" =>'pick_one' ); return $class; }

    I'm not really a human, but I play one on earth Remember How Lucky You Are
Re: Structure of a large Perl GUI application
by boblawblah (Scribe) on Feb 11, 2009 at 21:41 UTC
    I have a similar program created. The main.pl creates a window with shortcuts to different "reports" that may connect to a web server and download information or create a file from information on the local disk. Each report is it's own object - and may be of their own classes, but they all have a new and a create method and whatever else they need to do their assigned work.

    Here is an example of what this looks like (not my actual code, and probably doesn't run, but it should give you and idea). The progress bar is created in the main script, and then a reference to it passed to the report objects that want to update it.
    use warnings; use strict; use Report; my $window = Gtk2::Window->new; $window->set_title('Main Window'); $window->signal_connect('delete-event' => sub {Gtk2->main_quit}); my $vbox = Gtk2::VBox->new; my $bbox = Gtk2::VButtonBox->new; my $b1 = Gtk2::Button->new_with_mnemonic('_Report'); $b1->signal_connect(clicked => \&create_report); my $b2 = Gtk2::Button->new_with_mnemonic('E_xit'); $b2->signal_connect(clicked => sub {Gtk2->main_quit}); my $progress = Gtk2::ProgressBar->new; $bbox->add($b1); $bbox->add($b2); $vbox->add($bbox); $vbox->add($progress); $window->add($vbox); $window->show_all; Gtk2->main; sub create_report { my $dialog = Gtk2::Dialog->new; $dialog->set_title('Report Parameters'); # create the buttons and the content fields my $entry = Gtk2::Entry->new; $dialog->vbox->add($entry); # create the dialog action-area # run the dialog, get the response my $response = $dialog->run; $dialog->destroy; # return if user canceled return undef unless $response eq 'ok'; # create the report my $report = Report->new(parameter => $entry->get_text, progress_b +ar => $progress); $report->create; } package Report; use Gtk2; sub new { my ($class, %args) = @_; my $self = bless \%args, $class; return $self; } sub create { my $self = shift; # do stuff # update the progress bar $self->{progress_bar}->pulse; # required to see the change in the progress bar Gtk2->main_iteration while Gtk2->events_pending; # do stuff # update the progress bar $self->{progress_bar}->pulse; # required to see the change in the progress bar Gtk2->main_iteration while Gtk2->events_pending; }
Re: Structure of a large Perl GUI application
by cmv (Chaplain) on Feb 16, 2009 at 20:54 UTC
    DarrenM-

    I've struggled with this same problem myself, and I offer the solution that I've come up with. It may not be a very popular one, but it suits my needs, and I've come to like it.

    If you're familiar with Object-Oriented Patterns, you may recognize this as the Publisher-Subscriber pattern. In the olden days, we referred to this as a vector-table in writing C code.

    The basic problem occurs when you want this bit of code way over here, to know about something from that bit of code way over there, and the logistics of setting up subroutine calls is too messy. Simply put, a publisher-subscriber (PS) setup allows a piece of code to say Anytime someone announces that THIS happens (the publisher), please run THAT bit of code for me (the subscriber). I use this in my big PerlTK GUIs when I have a number of windows that all need to highlight something upon a mouseclick in a single window.

    The implementation of this is very simple, but it does make following the code a bit more difficult for new folks (or for yourself if your memory is as bad as mine). Here was my first implementation of PS as a module:

    package itemDispatch; use strict; use warnings; # The index to the dispatch hash is an item name, the contents is # a list of the callbacks that have been subscribed... my %DISPATCH; # Dispatch hash #################################################################### # Subroutine: Subscribe (external) # This routine subscribes a callback function to a dispatch item # Arguments: # $_[0] - Item Name # $_[1] - Callback function #################################################################### sub Subscribe { my $item = shift || die "missing dispatch item"; my $callback = shift || die "Missing callback function"; push(@{$DISPATCH{$item}}, $callback); } #################################################################### # Subroutine: Activate (external) # This routine activates a dispatch item # Arguments: # $_[0] - Item Name # $_[1]..$[-1] - Callback args #################################################################### sub Activate { # Get arguments... my $item = shift || die "Missing dispatch item"; my @args = @_; # Callback arguments my $coderef; foreach $coderef (@{$DISPATCH{$item}}) { &$coderef(@args); } } 1;
    If you combine that module, with a module that does some work, like this example module:
    package doWork; use strict; use Tk; use itemDispatch; my $PROGRESS = 0; sub StartWork { my $increment = shift || die "Missing Increment"; my $c=0; while ($c<99) { sleep 1; itemDispatch::Activate('PROGRESS', $increment); $c += $increment; } } 1;
    Then an example perl script using these two modules would look like this:
    use strict; use Tk; use Tk::StatusBar; use itemDispatch; use doWork; # Subscribe to PROGRESS events... itemDispatch::Subscribe('PROGRESS', \&_updateProgress); my $PROGRESS = 0; my $INCREMENT = 5; my $mw = MainWindow->new(); $mw->Button( -text => 'Start', -command => # Publish a START event... sub{ doWork::StartWork($INCREMENT)})->pack(); my $sb = $mw->StatusBar(); $sb->addProgressBar( -length => 60, -from => 0, -to => 99, -variable => \$PROGRESS, ); MainLoop(); sub _updateProgress { my $progress = shift || die "Missing Progress\n"; print STDERR "Updating progress by $progress\n"; $PROGRESS += $progress; $mw->update; }
    Hope that helps!

    -Craig

Re: Structure of a large Perl GUI application
by dragonchild (Archbishop) on Feb 18, 2009 at 14:57 UTC
    pubsub is really the way to go for this. The Javascript frameworks provide this sort of thing and it's really useful. Essentially, you create a place for subscribers to go subscribe. Each subscriber will register a callback with the central office, in essence "When XYZ is published to, please call this function with whatever arguments are provided." Then, the publisher will, when publishing to the event, ring up the central office and say "I want to publish to XYZ with ($foo, $bar)". The central office then trips the callbacks with ($foo, $bar).

    Something like the following could be used:

    package CentralOffice; my %events; my %names; my $counter = 0; sub subscribe { my $self = shift; my ($event, $callback, $name) = @_; $event = lc $event; $name ||= sprintf( "subscription_%04d", $counter++ ); $name = lc $name; $events{$event} ||= {}; # This is the subscribe step $events{$event}{$name} = $callback; return $name; } sub unsubscribe { my $self = shift; my ($event, $name) = @_; $event = lc $event; $name = lc $name; # This is the unsubscribe step return delete $events{$event}{$name}; } sub publish { my $self = shift; my ($event, @args) = @_; return unless %{$events{$event}}; # This is the publish step. $_->(@args) for values %{$events{$event}}; return 1; }
    Usage should be pretty self-explanatory.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://743105]
Approved by almut
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (9)
As of 2014-10-25 11:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (143 votes), past polls