Friday, August 6, 2010MVP in Flex

Short post of how to implement MVP  with Flex.

The english version of this post is shorter than portuguese, because all the code is commented in English

If you don't understand the MVP concepts, you should  read this post from Martin Fowler.

Bellow the project structure

mvp_estutura


View
: The login view component.

Components:   CustomButton exposes only the methods we need

Interfaces: Interface defines what will be available to the presenter

Presenter: the login presenter

Model: The model interface and implementation.

Going to code.

Login.mxml


<?xml version="1.0" encoding="utf-8"?>
<mx:Panel creationComplete="bindUi()" implements="interfaces.ILoginDisplay" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="180" xmlns:components="components.*">
<mx:Script>
<![CDATA[
import model.ILoginModel;
import model.LoginModel;

/********
* THE MOST IMPORTANT PART THAT SHOULD NOT BE FORGOTTEN IS TO CALL bingUi() METHOD
* IN creationComplete() OF YOUR VIEW.
* This is a login sample of how to use MVP in your flex applications.
* AUTOR : rrigoni@gmail.com
********/
import interfaces.ICustomButton;
import interfaces.ILoginDisplay;

import mx.controls.Alert;
import mx.core.IButton;

import presenters.LoginPresenter;

/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonClear():ICustomButton{
return this.clear;
}
/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonLogin():ICustomButton{
return this.loggin;
}
/**
* This method will be called when a login error occur.
* The presenter will send what was the error.
**/
public function displayErrorMessage(message:String):void{
Alert.show(message, "ERROR!");
}
/**
* This method will notify the user and perform with the login success.
* Its her responsability define what will be done.
**/
public function loginSuccess():void{
Alert.show("Login success with MVP.");
}
/**
* Bind the view with the presenter.
* Keep on eye on this method, need to called on creationComplete of your view!
**/
public function bindUi():void{
new LoginPresenter(this);
}
/**
* This method clean fields as needed
* How to clean, or witch fields need to be clean the view will define
**/

public function cleanFields():void{
this.username.text = null;
this.password.text = null;
}
/**
* Return the current model.
**/
public function model():ILoginModel{
return new LoginModel(username.text, password.text);
}
]]>
</mx:Script>
<mx:Form label="Login">
<mx:FormItem label="Username:">
<mx:TextInput id="username" />
</mx:FormItem>
<mx:FormItem label="Password:">
<mx:TextInput displayAsPassword="true" id="password" />
</mx:FormItem>
<mx:HBox>
<components:CustomButton id="loggin" label="Loggin" />
<components:CustomButton id="clear" label="Clear" />
</mx:HBox>
</mx:Form>
</mx:Panel>

CustomButton.as, custom button to exposes only the addEventListener method.

package components
{
import interfaces.ICustomButton;

import mx.controls.Button;
/**
*Custom Button, need extends Button and implement ICustomButton
* interface because we need to expose only the ICustomButton methodos
* to the presenter.
* @author ronaldo
*
*/
public class CustomButton extends Button implements ICustomButton{
public function CustomButton(){
}
}
}

ICustomButton.as, interface for the custombutton


package interfaces{
import flash.events.Event;
/**
* This interface exposes only the addEventListener function
* Until now is only we need to work.
* @author ronaldo
*
*/
public interface ICustomButton{
function addEventListener (type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void;
}
}

IDisplay.as,  basic interface, all the view's interfaces need to implement the method bindUi() to bind the view with the presenter.


package interfaces{
public interface IDisplay{
/**
* Bind the view with the presenter, obey to call this method on creationComplete of your view.
*
*/
function bindUi():void;
}
}

ILoginDisplay, this interface is all the presenter need to know.

<pre>package interfaces{
import model.ILoginModel;

/**
* interface to Login.mxml
* Exposes only what you need to work.
* @author ronaldo
*
*/
public interface ILoginDisplay extends IDisplay{
/**
*Return the model
* @return  the model
*
*/
function model():ILoginModel;
/**
*Exposes the clear button interface
* @return the button clear interface
*
*/
function buttonClear():ICustomButton;
/**
*Exposes the login button interface
* @return the login button interface
*
*/
function buttonLogin():ICustomButton;
/**
* Define in the view method to display error messages
* @param message The message to display.
*
*/
function displayErrorMessage(message:String):void;
/**
* The login logic remains on the presenter, this method notify
* the view to perform with login success.
*
*/
function loginSuccess():void;
/**
*
*Notify the view to clean the fields.
*/
function cleanFields():void;
}
}

LoginPresenter.as, Presenter  will contain a instance of this interface, and the view implementation.

package presenters{
import flash.events.MouseEvent;

import interfaces.ILoginDisplay;

import model.ILoginModel;
import model.LoginModel;
/**
* Presenter for the Login.mxml view.
* @author ronaldo
*
*/
public class LoginPresenter{

/**
*The view instance
*/
private var dislpay:ILoginDisplay = null;

/**
* Construct the presenter instance and bind to the local view interface.
* @param view The view instance.
*
*/
public function LoginPresenter(view:ILoginDisplay){
this.dislpay = view;
addListeners();
}

/**
*Apply the listeners
*
*/
private function addListeners():void{
dislpay.buttonClear().addEventListener(MouseEvent.CLICK, buttonClear_eventHandler);
dislpay.buttonLogin().addEventListener(MouseEvent.CLICK, buttonlogin_eventHandler);
}
/**
* Button login event handler.
* Validate de login and notify the view.
* @param event The click event.
*
*/
private function buttonlogin_eventHandler(event:MouseEvent):void{
var mod:ILoginModel  = dislpay.model();
if(mod.password && mod.password.length < 1 && mod.username && mod.username.length < 1){
dislpay.displayErrorMessage("Username and passwords cannot be blank!");
}
if(mod.username == 'admin' && mod.password == 'admin'){
dislpay.loginSuccess();
}else{
dislpay.displayErrorMessage("Wrong username and password.");
dislpay.cleanFields();
}
}
/**
* Button clear fields event handler,
* Notify the view when the button was pressed, and the view manage how to proceed.
* @param event The click event
*
*/
private function buttonClear_eventHandler(event:MouseEvent):void{
dislpay.cleanFields();
}

}

}

mvp.mxml, our Application.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" xmlns:view="view.*">
<mx:HBox verticalAlign="middle" horizontalAlign="center" width="100%" height="100%">
<view:Login  />
</mx:HBox>
</mx:Application>

ILoginModel.as, define the model interface.


package model{
public interface ILoginModel{
function get username():String;
function get password():String;
}
}
LoginModel.as, the model implementation.
package model{
public class LoginModel implements ILoginModel{

private var _password:String;
private var _username:String;

/**
*Generate new instance of LoginModel
* @param user The username
* @param pass The password
*
*/
public function LoginModel(user:String, pass:String){
this._password = pass;
this._username = user;
}

/**
*Return the username
* @return the username
*
*/

public function get username():String{
return _username;
}

/**
*Return the password.
* @return  the password.
*
*/
public function get password():String{
return _password;
}

}
}

Any question, send me an email to rrigoni@gmail.com.

The source code is available mvp

Regards.

Ronaldo.

Monday, November 8, 2010Server side Thumbnails Service

Short tip, Do you need to be able to generate thumbnails from a url on a server side without grafic interface(X)?

PS: Tested on Ubuntu and Debian distributions.

Download screenshot.tar file, and exec  the installCutyCapt.sh file.

After the installation process has been completed, exec the command on console.

$ shoot  http://www.google.com  screenshotGoogle.png

Enjoy.

Ronaldo.

Sorry, this post isn't available in English

Sorry, this post isn't available in English

Sorry, this post isn't available in English

[/sourcecode]
[/sourcecode]

Sorry, this post isn't available in English

Per copiare tutta la directory solo per fare lo stesso passo che usate per.

Salve galera.

Hoje vamos abordar uma forma simples de trabalhar com MVC no Flex com Eventos.

Primeiramente oque é MVC?

Com o aumento da complexidade das aplicações desenvolvidas torna-se fundamental a separação entre os dados (Model) e o layout (View). Esta forma permite que trabalhamos separadamente entre layout e manipulação de dados, tornando flexível alterações no layout sem afetar o Controller e vice-versa.

O MVC resolve um dos maiores problemas entre acoplamento das camadas de acesso a dados e lógica de negócio da aplicação e apresentação de dados ao usuário, pois introduz um componente entre as duas camadas (dados e interface) chamada Controller, o Design Patter MVC está mais ligado a arquitetura da aplicação do que um tipo de padrão de projeto, pois afeta a maneira de como a aplicação será organizada e estruturada.

O MVC não abrange a camada de acesso a dados, pois supõe-se que ela esteja encapsulada dentro do model.

OBS: Camadas dizem como separar os componentes, mas MVC diz como os componentes interagem entre si.

As camadas:

  • Model: A representação especifica da informação em que a aplicação trabalha. Por exemplo, cliente, fornecedor e produtos fazem parte do domínio de um sistema de PDV. Não confunda Model com outro nome da camada de dominio. Lógica de dominio apresenta traz maior sentido aos dados crus. Ex: Temos um cliente com um saldo de crédito X, e um histórico de compras e pagamentos, e criamos um método que percorre o histórico de compras do cliente e de pagamentos e nos devolve se podemos ou não aumentar o crédito do cliente, isso é chamada lógida de domínio, onde é agregado maior sentido aos dados propriamente ditos "crus".
  • View: A camada view é responsável por expor os dados a uma iteração com o usuário lendo do Model as informações necessárias.
  • Controller: Controller é onde ocorre todo o processamento, validações, manipulação de eventos e solicitações do usuário.

O diagrama representa as seguintes associações:

  • O controller conhece a view e o model.
  • A view conhece apenas o model.
modelviewcontrollerdiagram

Modelo MVC

Fluxo:

  1. Ocorre uma interação do usuário com a interface ( clique em um botão de listagem).
  2. O controller recebe a solicitação pois havia sido previamente definido em uma rotina a manipulação do evento no proprio controller.
  3. O controller acessa o model baseado na interação do usuário.
  4. A view utiliza o model para gerar a interface( na maioria dos casos exibir os dados ou requisições do usuário),  a view obtem os dados do model sem que ele tenha conhecimento da camada de apresentação.
  5. A view espera as próximas interações do usuário e o ciclo se inicia.

Bem vamos a parte prática:

Crie a seguinte estrutura de pacotes:

pacotes_exemplo_flex

Agora vamos criar os componentes MXML dentro do pacote view:


&amp;lt;?xml version=&amp;amp;quot;1.0&amp;amp;quot; encoding=&amp;amp;quot;utf-8&amp;amp;quot;?&amp;gt;
&amp;lt;mx:Panel title=&amp;amp;quot;Listagem&amp;amp;quot; creationComplete=&amp;amp;quot;init()&amp;amp;quot; xmlns:mx=&amp;amp;quot;http://www.adobe.com/2006/mxml&amp;amp;quot; width=&amp;amp;quot;400&amp;amp;quot; height=&amp;amp;quot;300&amp;amp;quot;&amp;gt;
&amp;lt;mx:List id=&amp;amp;quot;list&amp;amp;quot; width=&amp;amp;quot;100%&amp;amp;quot; dataProvider=&amp;amp;quot;{model.clients}&amp;amp;quot; labelField=&amp;amp;quot;name&amp;amp;quot; height=&amp;amp;quot;100%&amp;amp;quot; /&amp;gt;
&amp;lt;mx:ApplicationControlBar&amp;gt;
&amp;lt;mx:Button id=&amp;amp;quot;btList&amp;amp;quot; label=&amp;amp;quot;Listar&amp;amp;quot; /&amp;gt;
&amp;lt;mx:Button id=&amp;amp;quot;btClear&amp;amp;quot; label=&amp;amp;quot;Limpar&amp;amp;quot; /&amp;gt;
&amp;lt;/mx:ApplicationControlBar&amp;gt;
&amp;lt;mx:Script&amp;gt;
&amp;lt;![CDATA[
import br.com.ronaldorigoni.controller.ListagemController;
import br.com.ronaldorigoni.model.ListagemModel;
[Bindable]
public var model:ListagemModel = null;
public var controller:ListagemController = null;

private function init():void{
model = new ListagemModel();
controller = new ListagemController(this);
}
]]&amp;gt;
&amp;lt;/mx:Script&amp;gt;
&amp;lt;/mx:Panel&amp;gt;

Sublistagem.mlxml


&amp;lt;?xml version=&amp;amp;quot;1.0&amp;amp;quot; encoding=&amp;amp;quot;utf-8&amp;amp;quot;?&amp;gt;
&amp;lt;mx:Panel title=&amp;amp;quot;Sublistagem&amp;amp;quot; creationComplete=&amp;amp;quot;init()&amp;amp;quot; xmlns:mx=&amp;amp;quot;http://www.adobe.com/2006/mxml&amp;amp;quot; width=&amp;amp;quot;400&amp;amp;quot; height=&amp;amp;quot;300&amp;amp;quot;&amp;gt;
&amp;lt;mx:Form&amp;gt;
&amp;lt;mx:FormItem label=&amp;amp;quot;ID:&amp;amp;quot;&amp;gt;
&amp;lt;mx:TextInput editable=&amp;amp;quot;false&amp;amp;quot; id=&amp;amp;quot;clientId&amp;amp;quot; text=&amp;amp;quot;{model.client.id}&amp;amp;quot; /&amp;gt;
&amp;lt;/mx:FormItem&amp;gt;
&amp;lt;mx:FormItem label=&amp;amp;quot;Name&amp;amp;quot;&amp;gt;
&amp;lt;mx:TextInput editable=&amp;amp;quot;false&amp;amp;quot; id=&amp;amp;quot;clientName&amp;amp;quot; text=&amp;amp;quot;{model.client.name}&amp;amp;quot;/&amp;gt;
&amp;lt;/mx:FormItem&amp;gt;
&amp;lt;/mx:Form&amp;gt;
&amp;lt;mx:Script&amp;gt;
&amp;lt;![CDATA[
import br.com.ronaldorigoni.controller.SublistagemController;
import br.com.ronaldorigoni.model.SublistagemModel;
[Bindable]
public var model:SublistagemModel = null;
public var controller:SublistagemController = null;

private function init():void{
model = new SublistagemModel();
controller = new SublistagemController(this);
}
]]&amp;gt;
&amp;lt;/mx:Script&amp;gt;
&amp;lt;/mx:Panel&amp;gt;

Em seguida criaremos os controllers para cada componente da camada view:

ListagemController.as


package br.com.ronaldorigoni.controller{
import br.com.ronaldorigoni.event.ListagemEvento;
import br.com.ronaldorigoni.view.Listagem;
import br.com.ronaldorigoni.vo.Client;

import flash.events.MouseEvent;

import mx.collections.ArrayCollection;
import mx.events.ListEvent;

public class ListagemController{
// instancia da camada view para o controller poder manipulá-la
private var view:Listagem = null;
/**
* Contrutor do controller
* @param _view A instancia da view
*/
public function ListagemController(_view:Listagem){
// atribue a instancia da view a instancia local
this.view = _view;
// adiciona evento de clique para listar clientes
view.btList.addEventListener(MouseEvent.CLICK, listClients);
// adiciona evento para limpar a listagem de clientes
view.btClear.addEventListener(MouseEvent.CLICK, clearClients);
// adiciona evento de selecao de um cliente
this.view.list.addEventListener(ListEvent.ITEM_CLICK, dispatchEvent);
}
/**
* Invoca preenchimento do modelo de clientes.
* Lembrando que em uma aplicacao real isso deve ser
* feito por uma linguagem de servidor, geralmente com acesso
* a banco de dados.
*/
private function listClients(event:MouseEvent):void{
fillClients();
}

/**
* Limpa a lista de clientes.
*/
private function clearClients(event:MouseEvent):void{
view.model.clearModel();
}

/**
* Preenche a lista de clientes.
* Sendo quem em uma aplicação real estes dados devem ser alimentados
* por uma linguagem de servidor.         *
*/
private function fillClients():void{
for(var i:int = 0; i &amp;lt; 10; i ++){
var client:Client = new Client();
client.name = &amp;amp;quot;Client &amp;amp;quot;+i;
client.id = i;
view.model.addClient(client);
}
}

/**
* Dispara um evento para o componente pai da listagem, e este
* terá adicionado os listeners para este tipo de evento.
* Onde fará a invocação das funcoes.
*/
private function dispatchEvent(event:ListEvent):void{
// setando o cliente selecionado dentro do modelo de listagem
view.model.selected = view.list.selectedItem as Client;
var listagemEvento:ListagemEvento =    new ListagemEvento(ListagemEvento.EVENT_CLIENT_SELECTED);
// inserindo dentro do objeto de evento o cliente selecionado.
listagemEvento.sublistagemModel.client = view.model.selected;
// invoca o componente pai da view no caso a application
// para disparar um evento, sendo que nela esta definido um ouvinte para este
// tipo de evento, e a funcao será invocada, internamente no evento existe os dados
// que a visão necessita para exibir.
view.parent.dispatchEvent(listagemEvento);
}
}
}

SublistagemController.as


package br.com.ronaldorigoni.controller{
import br.com.ronaldorigoni.event.ListagemEvento;
import br.com.ronaldorigoni.view.Sublistagem;

import mx.controls.Alert;
public class SublistagemController{

/**
* Instancia da camada view
*/
private var view:Sublistagem = null;

/**
* Construtor do controller, recebe instancia da camada view para
* poder manipulá-la
*/
public function SublistagemController(_view:Sublistagem){
view = _view;
}
/**
* Manipula o evento de cliente selecionado.
*/
public function handleEventSelected(event:ListagemEvento):void{
// seta o titulo do painel de sublistagem
view.title = &amp;amp;quot;Sublistagem       Evento recebido:&amp;amp;quot;+event.type;
// seta o model da sublistagem
// quando o model é setado ele é automaticamente renderizado
// na view, pois está marcado com o [Bindable]
view.model = event.sublistagemModel;
}
}
}

Agora vamos criar as Classes de Modelo para os dois componentes visuais da camada View:

ListagemModel.as


package br.com.ronaldorigoni.model{
import br.com.ronaldorigoni.vo.Client;

import mx.collections.ArrayCollection;

public class ListagemModel{
/**
* Array de clientes que será exibido na listagem.
*/
private var _clients:ArrayCollection = new ArrayCollection();
private var _selected:Client;

public function ListagemModel(){
}
/**
* Defina o arrayCollection de clientes.
*/
public function set clients(clients:ArrayCollection):void{
this._clients = clients;
}
/**
* Retorna o arrayCollection de clientes
*/
public function get clients():ArrayCollection{
return _clients;
}
/**
* Limpa o array de clientes.
*/
public function clearModel():void{
this._clients.removeAll();
}
public function addClient(client:Client):void{
_clients.addItem(client);
}

public function set selected(selected:Client):void{
this._selected = selected;
}
public function get selected():Client{
return _selected;
}
}
}

E SublistagemModel.as


package br.com.ronaldorigoni.model{
import br.com.ronaldorigoni.vo.Client;

public class SublistagemModel{

/**
* Representa o cliente atualmente selecionado.
*/
private var _client:Client = new Client();

public function SublistagemModel(){
}

/**
* Seta o cliente atualmente selecionado.
*/
public function set client(client:Client):void{
this._client = client;
}
/**
* Recupera o cliente atual selecionado.
*/
public function get client():Client{
return _client;
}
}
}

Agora vamos criar a classe de Eventos que armazenará uma instância de SublistagemModel para notificar o controller de Sublistagem.

&amp;lt;/pre&amp;gt;
package br.com.ronaldorigoni.event{
import br.com.ronaldorigoni.model.SublistagemModel;

import flash.events.Event;
/**
* Classe de eventos para operacoes de listagem.
* Armazena o cliente atual selecionado.
*/
public class ListagemEvento extends Event{

/**
* Representa um evendo de cliente selecionado.
*/
public static const EVENT_CLIENT_SELECTED:String = &amp;amp;quot;clientSelected&amp;amp;quot;;

private var _sublistagemModel:SublistagemModel = new SublistagemModel();
public function ListagemEvento(tipo:String){
super(tipo,true);
}
/**
* Define o modelo selecionado a sublistagem
*/
public function set sublistagemModel(sublistagemModel:SublistagemModel):void{
this._sublistagemModel = sublistagemModel;
}
/**
* Recupera o modelo da sublistagem
*/
public function get sublistagemModel():SublistagemModel{
return _sublistagemModel;
}
}
}

Esta classe de evento mantem uma propriedade do tipo cliente que alimentará o model de Sublistagem.

Agora vamos criar a classe de VO Client.as, que armazena as informações de cada cliente.


package br.com.ronaldorigoni.vo{
/**
* VO de cliente.
*/
public class Client{

private var _id:uint;
private var _name:String;

public function Client(){
}
/**
* seta o nome do cliente
*/
public function set name(name:String):void{
this._name = name;
}
/**
* Seta o id do cliente;
*/
public function set id(id:uint):void{
this._id = id;
}
/**
* Retorna o atual nome do cliente
*/
public function get name():String{
return _name;
}
/**
* Retorna o id do cliente
*/
public function get id():uint{
return _id;
}
}
}

E por fim nosso código do nosso application ExemploMVC.mxml:


&amp;lt;?xml version=&amp;amp;quot;1.0&amp;amp;quot; encoding=&amp;amp;quot;utf-8&amp;amp;quot;?&amp;gt;
&amp;lt;mx:Application creationComplete=&amp;amp;quot;init()&amp;amp;quot; xmlns:mx=&amp;amp;quot;http://www.adobe.com/2006/mxml&amp;amp;quot; layout=&amp;amp;quot;horizontal&amp;amp;quot; xmlns:view=&amp;amp;quot;br.com.ronaldorigoni.view.*&amp;amp;quot;&amp;gt;
&amp;lt;mx:HBox&amp;gt;
&amp;lt;view:Listagem id=&amp;amp;quot;listagem&amp;amp;quot;/&amp;gt;
&amp;lt;view:Sublistagem id=&amp;amp;quot;sublistagem&amp;amp;quot;/&amp;gt;
&amp;lt;/mx:HBox&amp;gt;
&amp;lt;mx:Script&amp;gt;
&amp;lt;![CDATA[
import br.com.ronaldorigoni.event.ListagemEvento;
/**
* Funcao é disparada quando a aplicacao terminar de carregar.
*/
private function init():void{
/**
* Adiciona o listener de evento, para quando for disparado um
* evento deste tipo a funcao para manipular seja invocada.
*/
addEventListener(ListagemEvento.EVENT_CLIENT_SELECTED,
sublistagem.controller.handleEventSelected);
}
]]&amp;gt;
&amp;lt;/mx:Script&amp;gt;
&amp;lt;/mx:Application&amp;gt;

No application tem um listener adicionado para quando for disparado um evento do tipo "ListagemEvento.EVENT_CLIENT_SELECTED", que dispara a função "SublistagemConroller.handleEventSelected", que captura dentro do evento o cliente selecionado atravéz da propriedade "selected", e atualiza o modelo de sublistagem fazendo com que o cliente selecionado chege até a sublistagem sem que os componentes esteja acoplados.

Para testarmos o funcionamento rode a aplicação como Flex Application, clique sobre "Listar", onde o controller manipula este clique e lista os clientes, quando for clicado sobre um cliente na List da Listagem.mxml será criado uma instância de ListagemEvento e adicionado o Client selecionado dentro deste evento e é disparado através do componente pai de Listagem.xml o evento criado, como temos um listener para este tipo de evento em ExemploMVC.mxml ele dispara a função com este evento como um parâmetro, e la é feita a manipulação e atualização do model de Sublistagem.

A grande vantagem de se programar desta forma é que os componentes não ficam acoplados e não se conhecem em tempo de compilação. Já esta estrutura de MVC permite que possamos futuramente efetuar alterações em cada camada sem que outra camada seja afetada ou alterado o funcionamento. Ex, se precisarmos alterar o layout da aplicação o controller não precisa saber disso.

Com o uso da anotação de metadados "Bindable", nos modelos dentro da view, faz com que qualquer alteração pelo Controller sobre o model ela seja automaticamente exibida na camada view, e no caso inverso, quando o usuário altera algum dado apresentado na camada view, ele já é autimaticamente atualizado no model, sendo assim o controller tem total controle sobre a exibição e alteração dos dados.

Aparentemente com esta estrutura temos mais código, mas isso é relativamente incomparável quando for um sistema relativamente grande e que necessite de constante manutenção, pois será alterado apenas as partes que necessitam sem afetar as demais.

Todas as classes estão comentadas e explicadas, qualquer dúvida crítica ou sugestão me escreva ronaldo arroba ronaldorigoni.com.br

Segue link para download do projeto.

Abraços e até um próximo post.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes

© 2007 Ronaldo Rigoni | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress