Diciembre 13, 2004

[PPC] Aplicación del modelo-vista-controlador a una aplicación para PocketPC

Vamos a construir una aplicación muy sencilla, pero que puede ser un buen ejemplo de cómo aplicar el paradigma del modelo-vista-controlador a una aplicación para PocketPC. Y además, utilizando la nueva clase Delegate.

Pues el caso es que había quedado con unos amigos para jugar al scalextric, pero alguien se acordó de que el cuentavueltas no funcionaba, así que pensé: "podemos usar un lápiz y un papel, seguro, pero ¡éste es un trabajo para mi pocket pc!".

Así que hice un cuentavueltas. Éste es el resultado final:

lapcounter_ok.jpg

La aplicación es muy simple. Pero la he implementado utilizando el modelo-vista-controlador, y por probar, usando los componentes de IU estándares de flash. No he utilizado los propios del PocketPC CDK porque, aunque ya ha pasado un año desde la última aplicación que hice para PocketPC, todavía no se me ha pasado el susto. Dicho de otro modo, no es que sean demasiado eficaces.

Antes de comenzar, una pequeña nota al margen. Utilizo mi propio sistema de eventos. Ya lo he utilizado con anterioridad en otros posts, pero lo he incluído en la descarga con las fuentes de esta aplicación. Básicamente, consiste en una clase base que deben extender todas las que sean emisoras de eventos ( EventSource ), y que implementa un interfaz llamado IEventRegister. También utilizo un HashMap, que esta incluido en el zip

Pues al ataque. Los componentes y el textfield para la salida están colocados en el stage. Esos son los gráficos que estarán controlados por la vista ( CounterView.as )

import mx.utils.Delegate;

import net.designnation.lapcounter.LapCounterController

class net.designnation.lapcounter.CounterView
{
 private var timeline		: MovieClip;
 private var controllerVal 	: LapCounterController;
 	
 function CounterView( timeline: MovieClip )
 {
  this.timeline = timeline;
  
  this.initView( );		
 }	
 	
 private function initView( )
 {
  var viewRef: CounterView = this;
  			
  this.timeline.resetButton.label= "Reset";
  this.timeline.resetButton.addEventListener( "click",
  	Delegate.create( this, onResetBTClicked ) );
  			
  this.timeline.countButton.label = "Tap me, please!";
  this.timeline.countButton.addEventListener( "click",
  	Delegate.create( this, onCountBTClicked ) );
  
  this.timeline.switchCB.setStyle( "color", 0xFFFFFF );
  this.timeline.switchCB.label="increment";	
  		
  this.timeline.switchCB.addEventListener( "click", 
  	Delegate.create( this, onCheckBoxClicked ) );
  		
  this.timeline.switchCB.selected = true;
 }
 	
 public function set controller( controllerRef: LapCounterController )
 {
  this.controllerVal = controllerRef;
 }
 	
 public function onCounterChanged( param: Object )
 {
  this.timeline.result.text = param.value;
 }
 	
 public function onCountBTClicked( )
 {
  this.controllerVal.screenClicked( );
 }
 	
 public function onResetBTClicked( )
 {
  this.controllerVal.reset( );
 }	
 	
 public function onCheckBoxClicked( )
 {
  this.controllerVal.incFlagChanged( );	
 }	
}

Esta clase asigna las etiquetas a los botones y al checkbox, y utiliza la clase Delegate para asignar los manejadores de los eventos de dichos componentes. Por tanto, cuando se haga click en countButton ( el botón grandote ), el evento será escuchado por el método "onCountBTClicked" the la clase CounterView.

Hay un método para actualizar la salida por pantalla ( asignando el valor de un campo de texto llamado result ), y tres métodos para manejar las acciones del usuario ( uno por componente, gracias a la delegación ). Estos métodos ejecutan a su vez un método público del controlador

Ya sé que la vista puede, y probablemente deba, atacar directamente al modelo para que éste cambie su estado, pero eso no es algo que me guste mucho. Yo prefiero, siempre que sea posible, hacerlo a través del controlador

Pero, ¿ dónde está el controlador ?. Pues se construye en el primer frame del fla, mediante el siguiente código:

import net.designnation.lapcounter.*

var controller: LapCounterController = new LapCounterController( new CounterView( this ) );

Por tanto, se inicializa el controlador y se le pasa una instancia de la vista. Veamos el controlador con más detalle.

import net.designnation.lapcounter.*

class net.designnation.lapcounter.LapCounterController
{
 private var lapCounter	: ILapCounterActions;
 private var counterView	: CounterView;
 private var timeline	: MovieClip;
 	
 function LapCounterController( view: CounterView )
 {
  this.counterView = view;
  this.counterView.controller = this;
  		
  this.initCounter( );
 }
 	
 private function initCounter( )
 {
  this.lapCounter = new LapCounter( );
  		
  this.lapCounter.registerEvent( this.counterView, "onCounterChanged" );
 }
 	
 public function screenClicked( )
 {
  this.lapCounter.addLap( );
 }
 	
 public function incFlagChanged( )
 {
  this.lapCounter.setInc( );
 }
 	
 public function reset( )
 {
  this.lapCounter.reset( );	
 }
}

El controlador tiene una referencia a la vista, y es el encargado de crear el modelo ( LapCounter ). No mantiene una referencia al modelo como tal, sino al interaz del mismo ( ILapCounterActions ). Pero veremos el modelo más tarde

Cuando se crea el modelo, el modelo registra a la vista como event listener del modelo. Por tanto, cuando éste cambie su estado ( los datos que contiene ), emitirá un evento que será escuchado directamente por la vista.

También hay tres métodos ( screenClicked, incFlagChanged, and reset ), que son los que utiliza la vista para comunicarse con el modelo

Y, por último, llegamos al modelo ( LapCounter.as )

import net.designnation.events.EventSource

import net.designnation.lapcounter.ILapCounterActions

class net.designnation.lapcounter.LapCounter extends EventSource implements ILapCounterActions
{
 private var actualLaps	: Number;
 private var incFlag	: Boolean;
 	
 function LapCounter( )
 {
  this.actualLaps = 0;
  this.incFlag	= true;
 }
 	
 public function addLap( )
 {
  var multiplier: Number = ( this.incFlag )? 1: -1;
  		
  this.actualLaps+= 1* multiplier;
  		
  if ( this.actualLaps< 0 ) this.actualLaps = 0;
  		
  updateView( );
 }
 	
 private function updateView( )
 {
  fireEvent( "onCounterChanged", { value: this.actualLaps } );
 }	
 	
 public function setInc( )
 {
  this.incFlag = 	!this.incFlag;
 }
 	
 public function reset( )
 {
  this.actualLaps = 0;	
  		
  updateView( );
 }
}

Esta clase extiende EventSource ( para heredar así el manejo de eventos ), e implementa un interfaz ( ILapCounterActions ). Ese interfaz será la referencia que tiene el controlador del modelo. De este modo, se podría cambiar el modelo, sin afectar al resto de la aplicación ( siempre y cuando el nuevo modelo también implemente ese interfaz, claro ). Este interfaz extiende a su vez del interfaz de registro de eventos.

El modelo no es muy complejo ( la aplicación tampoco ). Simplemente maneja un contador, y una bandera para saber si el contador debe incrementarse o decrementarse. También hay un método para actualziar la vista ( emitiendo el evento pertinente ).

Y eso es todo. Simplemente hay que publicar la aplicación como flash 6, crear un html para contenerla, y ¡a jugar al scalextric!

Y por supuesto, el código fuente: está aquí

Escrito por Cesar Tardaguila en: Diciembre 13, 2004 07:48 AM | TrackBack
Comentarios