Hello,

I refactored the Tk interface example under the POE Cook Book to demonstrate
how I think POE can be used to implement the MVC design for a GUI
application. (use of Moose is optional). Basically my (very limited)
understanding of MVC design is that there are following key elements to it
1. The Model is in charge of all the data. It does not know any details
about the View. It simply notifies all the Views that are observing it of
any changes.
2. A View is just that, a view into the model's data. It never save any of
the actual data within itself but just attaches to the Model for it.
3. Controller is the View's interface into the Model. View will relay the
user inputs/events to controller to request appropriate query/modification
of the Model(data).

With this very high level understanding, I refactored the Tk Interface
example. Does the code/design below make sense?

Looking for your comments/suggestions/critic.

BR
--- DCP

############################################################################################
#!/usr/bin/perl
use strict;
use warnings;

#---- The View Package
package MyView;
use Tk;
use POE;

sub new {
    my $class = shift;
    my $model = shift;
    #--- For this example it is associated with only one Model.
    #--- but can be extended easily so that it can be associated with
multiple models
    my $self  = { "Model" => $model };
    bless $self, $class;
    $self->spawn();
    return $self;
}

sub spawn {
    my $self = shift;
    POE::Session->create(
        inline_states => {
            _start   => \&ui_start,
            _notify  => \&handle_model_change,
            ev_count => \&ui_ctrl_count,
            ev_clear => \&ui_ctrl_clear,
        },
        args => [$self],
    );

}

sub attach_model {
    my ( $self, $model ) = @_;
    $self->{"Model"} = $model;
    $model->Register( $self->{"Session"} ) if ( defined $model );
}

sub ui_start {
    my ( $kernel, $session, $heap, $self ) = @_[ KERNEL, SESSION, HEAP, ARG0
];
    $kernel->alias_set("COUNTERDISP");
    $self->{"Session"} = "COUNTERDISP"; #--- This is the name which will
register with the model

    #--- Attach the view to the model
    my $model = $self->{"Model"};
    $self->attach_model($model);
    $heap->{"AppInstance"} = $self;

    $poe_main_window->Label( -text => "Counter" )->pack;

#--- Note actual counter data is not stored in view but comes from Model.
    $heap->{counter_widget} = $poe_main_window->Label( -text => "SomeValue"
)->pack;

    $poe_main_window->Button(
        -text    => "Clear",
        -command => $session->postback("ev_clear")
    )->pack;

    $kernel->yield("ev_count");
}

#--- Model sends a _notify event to all the registered observers.
#--- This view uses following function for handling the _notify event
sub handle_model_change {
    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
    my $self = $heap->{"AppInstance"};
    my $counter =
    $self->{"Model"}->counter();    #--- Get the relevant data from the
model.
    $heap->{counter_widget}->configure( -text => $counter ); #--- Update
view
    $poe_main_window->update;         # Needed on SunOS & MacOS-X
}

#--- Controller for this view to act upon a user input and request
#--- a change to the Model
sub ui_ctrl_count {
    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
    my $self = $heap->{"AppInstance"};
    #--- Send a request to the Model to increment the count
    $self->{"Model"}->inc_counter();

    $_[KERNEL]->yield("ev_count");
}

sub ui_ctrl_clear {
    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
    my $self = $heap->{"AppInstance"};
    $self->{"Model"}->reset_counter();
}

#---- Tne Model Package
package MyModel;
use POE;
use Moose;
use Moose::Util::TypeConstraints;
has 'counter' => (
    traits  => ['Counter'],
    is      => 'rw',
    isa     => 'Num',
    default => 0,
    handles => {
        inc_counter   => 'inc',
        dec_counter   => 'dec',
        reset_counter => 'reset',
    },
    trigger => \&_notify_observers
);

has 'Observers' => (
    traits  => ['Array'],
    default => sub { [] },
    is      => 'rw',
    isa     => 'ArrayRef[Str]',
    handles => {
        all_Observers    => 'elements',
        add_Observer     => 'push',
    },

);

#--- Method for views to register to this model as observers
sub Register {
    my $self    = shift;
    my $session = shift;
    $self->add_Observer($session) if ( defined $session );
}

#--- Method to notify all the views observing this model of any change
sub _notify_observers {
    my $self = shift;
    return if ( !defined $self->all_Observers() );
    my @List_of_Observers = @{ $self->Observers() };
    foreach my $observer (@List_of_Observers) {
        POE::Kernel->post( $observer, "_notify" );
    }
}

#--- The main app
package main;
my $SM = new MyModel;
my $SV = new MyView($SM);    #--- Create a view and attach to the Model
POE::Kernel->run();

############################################################################################

Reply via email to