« [J2ME] First Application: Dice | Inicio | [OT] Laptop archeology »

[PPC] The model-view-controller architecture applied to a pocket pc application

This is a extremely simple application, but it can be an example of how to develop pocket pc applications using and adapting the movel-view-controller architecture. And I’ve also used the Delegate class!.

Well, I was going to play scalextric with some friends, and we found that the lap counter was broken, so I thought: "sure, we can use a pencil and a paper sheet, but, this is a job for my pocketpc!".

So, I’ve built a lap counter. Here’s an screenshot:

lapcounter_ok.jpg

The application is extremely simple. But it’s implemented using the model-view-controller architecture, and just for the shake of testing, using the V2 UI components, instead of those included in the PocketPC CDK . A year has passed since my last pocketpc application, and I still remember how dificult was to work using those components.

First of all, I’m, using my own event system. I’ve used it in other posts before, and the classes are included in the source code that you can download below, but long story short, there is a base class ( EventSource ) that implements all the methods that any event source needs. The interface of that class is IEventRegister. I also use a hashmap, wich is also included.

Back to LapCounter. The components and output textfield are on the stage. Those graphic assests will be managed by the View ( 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( ); } }

This class sets the labels of the buttons and checkbox, and uses the Delegate class to set the components event handlers. So, when countButton ( the big button ) is clicked, the event will be listened by the method "onCountBTClicked" of the CounterView class.

As you could see, there is one method to update the screen output ( a textfield named result ), and three diferent methods to handle the user input ( one for each component, thanks to delegation ). When one of those methods is executed, it executes a public method of the controller.

I know the view can, and maybe should, attack the model directly to change its data, but that’s something I don’t like. I just prefer to do it through the controller.

But where’s the controller?. The whole application is initialized in the fla’s first frame, thanks to this code:

import net.designnation.lapcounter.* var controller: LapCounterController = new LapCounterController( new CounterView( this ) );

So, the controller is initialized, and receives and instance of the view. Let’s take a closer look at the controller.

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( ); } }

The controller holds a reference to the view, and creates the model ( LapCounter ). It doesn’t hold a reference to the model itself, but to the model’s interface ( IlapCounterActions ). But we’ll go back to the model later.

When the model is created, the controller register the view as an event listener of the model. So, when the model changes its internal state ( the data ), it fires an event that is listened by the view.

There are also three methods ( screenClicked, incFlagChanged, and reset ) that are the ones that the view uses to change the model.

And, finally, the model ( 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( ); } }

This class extends EventSource ( so, it inherits the event handling methods ), and implements an interface ( ILapCounterActions ). That interface will be the reference of the model that is hold by the controller. So, we could change the model without affecting the rest of the application ( if the new model implements the same interface, of course ). That interface also extends the event registration interface.

The model is not very complex ( the whole application isn’t ). It just holds the counter, and a flag to know if the counter increments or decrements. And there’s also a method to update the view ( updateView ), that fires the event that is listened by, you guessed it, the view itself.

And that’s all. Just publish the application as flash 6, create an html to hold it, and you can safely play scalextric!.

And, of course, the source code: download it here

Comentarios

It lokks cool, but for what version of Player do you build the application?

Flash player 6. It's the one supported by windows mobile 2003 ( I think that's the OS name )

So you have used the publish settings Flash Player 6 and ActionScript 2.0...
I made a sample applicatione some time ago, and I make the choice to use the "classic" publish settings for Player 6 for safe compatibility.

Sure, I've done that before, but this time, I just wanted to try what happens when you write exactly the same code for a web and a pocket pc application....