Showing posts with label webforms. Show all posts
Showing posts with label webforms. Show all posts

Monday, April 13, 2009

Interfaces Web Forms herencia multiple

Interfaces en .NET son pocas veces usadas, por NO decir que jamas son mencionadas, en la implementacion de tu producto de software.

¿Que esta sucediendo? ¿Como te sentis al saber que NO estas utilizando todo el potencial de la tecnologia que tienes en tus manos, hablando estrictamente de C#, claro esta?

Pues VB.NET es un juguete para niños, mas nada :)

Lo que sucede es que NO le encuentras, muchas veces, razon de ser a la necesidad de definir una serie de metodos en una Interface que luego deberas implementar en cada Clase o Class que requiera implementarla.

Suena a realizar trabajo doble. A que NO ganas nada, en realidad, desde un limitado punto de vista.

Nada mas falso que la verdad :)

Las Interfaces tienen una valiosa utilidad. Aquella en la que ese comportamiento definido pueda ser utilizado por otras clases con la finalidad de brindar una valvula de escape al famoso, pero poco usado polimorfismo, en un lenguaje de programacion Orientado a Objetos, quimicamente NO puro, como el caso de los lenguajes de .NET ;)

Por ejemplo, la interfaz IEnumerable, que por cierto es la mas utilizada por el .NET Framework, radica su valiosa utilidad en brindarle la posibilidad a la clase que la implemente en que esta pueda ser iterable sobre el famoso foreach. Omnipresente estructura de control de flujo en el C#.

IEnumerable es de un nivel primario que casi forma parte de la esencia misma del lenguaje C#.NET.

Nosotros ya NO implementamos IEnumerable en nuestras propias clases, pues existen diferentes estructuras de datos, que la implementan y nos facilitan ya esa tarea. Solo nos limitamos a usarla, a sabiendas que existe una estructura de control que la recibe para hacer algo con ella, iterarla. Hablamos del curioso foreach.

He alli la clave para la existencia de Interfaces en el modelo de clases orientadas al producto de software que estamos diseñando.

La necesidad de la existencia de una Clase a la par que defines Interfaces para que las utilizen y hagan algo con ellas, el trabajo pesado. Es decir, para que estas sean parte de la implementacion de un comportamiento de orden superior por el cual fueron concebidos.

¿Se entiende? ¿Toy hablando piedras?

Un ejemplo por favor...!

Bueno, el mas trivial de todos se encuentra en la definicion de un Modelo de Clases e Interfaces para el manejo de Web Forms y Web User Controls en productos de software con conectividad a Base de Datos.

Los Web Forms asumen la gestion de las acciones realizadas por el usuario e implementan el comportamiento, como respuesta a dichas acciones.

Los Web User Controls realizan las tareas a un nivel especifico de detalle, hacen el trabajo laborioso: asignar valores a los TextBox, llenar un DropDownList, recolectar los valores seteados de su propia estructura en un Business Entity y entregarselos a quien lo solicita, and other stuffs :)

En ese sentido, son los Web User Controls candidatos ideales a implementar tus propias Interfaces, previamente definidas, con la intencion de centralizar ciertos comportamientos e implementarlos a nivel de Web Forms.

En concreto, tendras una clase UIPage que hereda de Page, facilitando cierta funcionalidad comunmente usada.

class UIPage : Page
{
protected override void OnLoad( EventArgs e )
{
if( ! this.IsPostBack )
{
this.FirstRequest();
}
}

protected virtual void FirstRequest() { }

protected override void OnInit( EventArgs e )
{
this.DefineHandlers();
this.DefineObjects();
}

protected virtual void DefineHandlers() { }
protected virtual void DefineObjects() { }

// Otras tantas funcionalidades
}

A la vez, tendras una clase UIPageForm que hereda de UIPage

class UIPageForm<TForm> : UIPage
where TForm : IForm
{
protected virtual TForm MyForm
{
get { throw new NotImplementedException(); }
}

// Otras tantas funcionalidades
}

Asi mismo, tendras una clase UIPageEdit que hereda de UIPageForm, centralizando el comportamiento para editar los elementos de ciertas entidades de negocio.

class UIPageEdit<TForm, TAtom> : UIPageForm<TForm>
where TForm : IFormEdit<TAtom>
where TAtom : ITransaction
{
protected virtual TAtom MyAtom
{
get { throw new NotImplementedException(); }
}

// Otras tantas funcionalidades
}

Ahora, imaginemos que se requiere un nuevo comportamiento, por el cual al editar un Business Entity sobre el Web Form, uno de los datos ingresados por el usuario, sobre la vista o View, deba ser unico. Permitiendo la posibilidad de validar aquello a traves de una opcion especial en el View. Asi como, tambien, estrictamente validable al elegir la opcion Save.

Entonces optaremos por una implementacion como esta:

class UIPageEditValidate<TForm, TAtom> :
UIPageEdit <TForm, TAtom>
where TForm : IFormEdit<TAtom>, IFormValidate
where TAtom : ITransaction
{
// Se varia el comportamiento base sobre este contexto
// para soportar el nuevo requerimiento.
}

Ahora, se requiere que ciertos Web Forms soporten el manejo de informacion historica por cada registro, permitiendo navegar sobre dicho historico a traves del mismo Web Form.

Este nuevo requerimiento podria aplicar tanto a Web Forms que soporten la funcionalidad de IFormValidate como a los que NO.

Aqui empieza nuestro problema, como consecuencia de la NO existencia de una herencia multiple de clases, como lo pregona aquella preciosa teoria de la POO, que tanto añoramos.

Si nuestro C# fuese un lenguaje Orientado a Objetos quimicamente puro, bastaria implementar la funcionalidad requerida sobre una clase llamada Navigator, llamada asi para efectos didacticos, y hacer que los Web Forms que requieran dicha funcionalidad la herenden, a la par que heredan su tradicional clase base, que en el mas alto nivel hereda de Page.

Pero NO es posible, entonces tendremos que buscar una forma de superar esta limitacion tecnica.

En esa busqueda, el uso correcto de Interfaces sera la solucion para este requerimiento, definiendo una interfaz INavigator y una clase llamada ConcreteNavigator.

Haciendo una simple analogia, con fines didacticos, INavigator seria como IEnumerable y a su vez ConcreteNavigator seria como la estructura de control foreach. Aquella que hace algo con la interfaz, que la utiliza en el contexto de la implementacion de un comportamiento de orden superior a la simple definicion de una Interfaz.

En ese sentido, tendriamos algo como esto para la interfaz INavigator:

public interface INavigator
{
event EventHandler PreviousClicked;
event EventHandler CurrentClicked;
event EventHandler NextClicked;

void StatusPrevious( Boolean enabled );
void StatusCurrent( Boolean enabled );
void StatusNext( Boolean enabled );
}

Por su parte la clase ConcreteNavigator tendria una implementacion como esta.

public class ConcreteNavigator<TForm, TAtom>
where TAtom : IHasHistory, IBuildHistory<TAtom>
where TForm : IFormEditHistory<TAtom>
{
StateBag _viewstate;
INavigator _navigator;
TForm _form;
TAtom _atom;

public ConcreteNavigator(
StateBag viewstate,
INavigator navigator,
TForm form,
TAtom atom )
{
this._viewstate = viewstate;
this._navigator = navigator;
this._form = form;
this._atom = atom;
}

public void DoPreviousClicked()
{
// Implementacion del comportamiento requerido
// usando los parametros entregados
// al instanciar ConcreteNavigator
// estando INavigator como parte de ellos.
}

public void DoNextClicked()
{
// Idem
}
public void DoCurrentClicked()
{
// Idem
}
}

Entonces se definiran dos nuevas clases para aquellos Web Forms que requieran utilizar el comportamiento plasmado en ConcreteNavigator.

Asi, tendremos las clase UIPageEditNavigator que hereda de UIPageEdit utilizando una instancia de ConcreteNavigator.

public class UIPageEditNavigator<TForm, TAtom> :
UIPageEdit<TForm, TAtom>
where TAtom : ITransaction, IHasHistory, IBuildHistory<TAtom>
where TForm : IFormEditHistory<TAtom>
{
private ConcreteNavigator<TForm, TAtom> _concrete;

protected virtual INavigator MyNatigator
{
get { throw new NotImplementedException(); }
}

protected override void DefineObjects()
{
this._concrete = new ConcreteNavigator<TForm, TAtom>(
this.ViewState,
this.MyNavigator,
this.MyForm,
this.MyAtom );
}

private void MyNavigator_PreviousClicked( Object sender, EventArgs e )
{
this._concrete.DoPreviousClicked();
}

// Otras tanta funcionalidades que se ajustan con
// lo soportado por ConcreteNavigator
}

De la misma forma, definimos la clase UIPageEditValidateNavigator que hereda de la clase UIPageEditValidate utilizando una instancia de ConcreteNavigator

public class UIPageEditValidateNavigator<TForm, TAtom> :
UIPageEditValidate<TForm, TAtom>
where TAtom : ITransaction, IHasHistory, IBuildHistory<TAtom>
where TForm : IFormEditHistory<TAtom>, IFormValidate
{
private ConcreteNavigator<TForm, TAtom> _concrete;

protected virtual INavigator MyNatigator
{
get { throw new NotImplementedException(); }
}

protected override void DefineObjects()
{
this._concrete = new ConcreteNavigator<TForm, TAtom>(
this.ViewState,
this.MyNavigator,
this.MyForm,
this.MyAtom );
}

private void MyNavigator_PreviousClicked( Object sender, EventArgs e )
{
this._concrete.DoPreviousClicked();
}

// Otras tanta funcionalidades que se ajustan con
// lo soportado por ConcreteNavigator
}


De esta manera, se supera la limitacion tecnica de la NO herencia multiple de clases inexistente en los lenguajes de .NET, como C#.

La clase ConcreteNavigator hace el trabajo pesado de una funcionalidad que luego es posible reflejar en diferentes clases, evitando el COPY & PASTE.

Al NO lograr este nivel de abstraccion, estaremos con seguridad, dejando de utilizar todas las bondades que la tecnologia nos brinda, hablamos de la POO.

Nuestro producto de software muy probablemente este disgregado de codigo, como consecuencia irresponsable del COPY & PASTE por everywhere.

Haciendo de este dolorasemente mantenible y vergonzosamente presentable :)

Tuesday, August 24, 2004

Handling Events and Delegates

Una de las cosas mas interesantes que trajo consigo la llegada de ASP.NET, aya por el año 2002, fue la posibilidad de encapsular logica e interfaz de usuario a traves de los ahora muy conocidos Web User Controls.

Esto nos ha permitido reutilizar dichas funcionalidades a lo largo de nuestro projecto web, permitiendo reducir algunas lineas de codigo, haciendo mas sencillo y flexible los posteriores mantenimientos.

Sin embargo, la manera profesional de trabajar con los Web User Controls consiste en definir eventos, a partir de los cuales se envian mensajes hacia la entidad que los contenga, que generalmente son Pages u otros Web User Controls.

La intencion esta en que las paginas ASP.NET, asuman el rol de gestores de los eventos de los diferentes Web User Controls que contengan dentro de el.

Les alcanzo un pequeño ejemplo que podria mostrar lo sencillo que es definir un evento para un Web User Control, haciendo uso de un Delegate que define cierta firma a la que se ha de ajustar el evento que deseamos publicar.


Archivo .ASCX


<%@ Control Language="c#" AutoEventWireup="false" Codebehind="SearchEngine.ascx.cs" Inherits="DemoWebAppCSharp.Event.SearchEngine"%>
<asp:TextBox id="_text" runat="server"></asp:TextBox>
<asp:Button id="_go" runat="server" Text="Go"></asp:Button>

--- End File ---

Este archivo define como se ha de estructurar la interfaz del usuario para este Web User Control en particular.

Algo tan simple como un TextBox y un Button para un sencillo buscador.


Archivo .ASCX.CS



namespace DemoWebAppCSharp.Event
{
using System;
using UserInterface;

/// <summary>
/// Definimos el delegate que define la firma con la que publicaremos el evento
/// </summary>
public delegate void ClickedEventHandler( Object sender, ClickedEventArgs e );

/// <summary>
/// Este Web User Control hereda de un clase que permite encapsular
/// cierta funcionalidad similar para todos los controles de
/// nuestro projecto web
/// </summary>
public class SearchEngine : CustomControl
{
protected System.Web.UI.WebControls.TextBox _text;
protected System.Web.UI.WebControls.Button _go;

/// <summary>
/// Publicamos el evento Clicked del tipo ClickedEventHandler
/// el delegate que habiamos definido lineas arriba
/// </summary>
public event ClickedEventHandler Clicked;

/// <summary>
/// Es requisito sobre-escribir este metodo para todo Web User Control
/// que diga decente :)
/// </summary>
/// <param name="e">Uhmmm... obvio!!!</param>
protected override void OnInit( EventArgs e )
{
base.OnInit( e );

// Enganchamos el metodo SearchEngine_Load al evento Load del control
this.Load += new EventHandler(SearchEngine_Load);

// Enganchamos el metodo Go_Click al evento Click del Button
this._go.Click += new EventHandler( this.Go_Click );
}

private void SearchEngine_Load(object sender, System.EventArgs e)
{
}

/// <summary>
/// Este metodo estara enganchado al evento Click del Button
/// que se muestra en la interfaz del usuario del Web User Control
/// y soltara el evento propio de este control para dicha accion Clicked.
/// </summary>
/// <param name="sender">Una instancia de quien ocasiona el evento</param>
/// <param name="e">Informacion del evento</param>
private void Go_Click(
Object sender,
EventArgs e )
{
// Lanzamos el evento Clicked. Alguien lo gestionara y sino no hay problema
if( this.Clicked != null )
this.Clicked( this, new ClickedEventArgs( this._text.Text ) );
}
}

/// <summary>
/// Esta clase encapsula cierta informacion particular para el evento Clicked
/// del Web User Control que estamos implementando SearchEngine.
/// Lo unico que enviara es el texto que fue ingresado en el TextBox del
/// control para que se realize la accion esperada del usuario.
/// </summary>
public class ClickedEventArgs : EventArgs
{
private String _text;

public ClickedEventArgs( String Text )
{
this._text = Text;
}

public String Text
{
get { return this._text; }
set { this._text = value; }
}
}
}

--- End File ---

Aqui implementamos la manera en como nuestro Web User Control ha de interactuar con el usuario. En funcion a las opciones que permitamos, realizaremos ciertas acciones a traves de Eventos. En este caso particular, al hacer 'click' sobre el Button lanzaremos el evento Clicked propio de este Web User Control.

Para ello tenemos que engancharnos al evento Click del Button y dentro de su implementacion disparar nuestro propio evento, Clicked.

Debido a que tenemos la intencion de enviar cierta informacion a traves del evento. La manera de llevarlo a cabo es usando el definiendo una clases que herede de EventArgs. En nuestro caso particular ClickedEventArgs. El cual solo cuenta con una propiedad llamada Text, donde colocaremos el dato que haya ingresado el usuario sobre el TextBox del Web User Control.

El evento que publicaremos debe ajustarse al Delegate que hemos definido, ClickedEventHandler. Este delegate se ajusta a cierta firma, propia para este evento.

No hubiera sido necesario definir este Delegate si no hubieramos visto conveniente una firma particular para el evento o enviar informacion a traves de el.

En ese caso, hubiera sido suficiente definir el evento Clicked del tipo EventHandler, el delegate por default. De ese modo, ya no seria necesario enviar informacion adicional como parte del evento. Entonces tendrias que usar EventArgs.Empty en el momento de lanzar el evento Clicked.



Archivo .ASPX



<%@ Register TagPrefix="Custom" TagName="SearchEngine" Src="SearchEngine.ascx" %>
<%@ Page language="c#" Codebehind="Holder.aspx.cs" AutoEventWireup="false" Inherits="DemoWebAppCSharp.Event.Holder" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>Holder</title>
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<table>
<tr>
<td>
<Custom:SearchEngine id="_engine" runat="server"></Custom:SearchEngine></td>
</tr>
<tr>
<td>
<asp:TextBox id="_show" runat="server"></asp:TextBox>
</td>
</tr>
</table>
</form>
</body>
</HTML>

--- End File ---

Este archivo contiene el Web User Control y un TextBox.

La intencion con propositos de testeo, es la de pintar el texto ingresado por el TextBox del Web User Control sobre el TextBox de la Page.


Archivo .ASPX.CS



namespace DemoWebAppCSharp.Event
{
using System;
using UserInterface;

/// <summary>
/// Esta es la pagina que contendra el Web User Control que hemos implementado
/// Por cierto, hereda de una pagina que define cierta funcionalidad similar
/// para todas las paginas de nuestro proyecto web.
/// </summary>
public class Holder : CustomPage
{
protected System.Web.UI.WebControls.TextBox _show;
protected SearchEngine _engine;

/// <summary>
/// Del mismo modo, que para el Web User Control es requisito para
/// toda Page que se diga decente.
/// </summary>
/// <param name="e"></param>
protected override void OnInit( EventArgs e )
{
base.OnInit( e );

// Enganchamos el metodo Holder_Load al evento Load de la page
this.Load += new EventHandler(Holder_Load);

// Enganchamos el metodo Engine_Clicked al evento Clicked de Web User Control
this._engine.Clicked += new ClickedEventHandler( this.Engine_Clicked );
}

private void Holder_Load(object sender, System.EventArgs e)
{
}

/// <summary>
/// Este es el metodo que se enganchara al evento Clicked del Web User Control
/// y que dentro de su implementacion realizara la tarea que creemos
/// conveniente. Pero dicha accion estara dentro de la jurisdiccion de la Page.
/// </summary>
/// <param name="sender">La instancia de quien genera el evento</param>
/// <param name="e">Informacion propia de este evento</param>
private void Engine_Clicked(
Object sender,
ClickedEventArgs e )
{
// Algo tan sencillo como pintar el texto ingresado por el control sobre otro TextBox
this._show.Text = e.Text;
}
}
}

--- End File ---

Para llevar a cabo dicho testeo, tenemos que subscribirmos al evento Clicked del Web User Control. De esa manera, coger la informacion que trae consigo el evento para poder realizar una accion. En este caso, ClickedEventArgs nos brinda el dato que el usuario hubiera ingresado sobre el TextBox del Web User Control. El cual nosotros cogeremos para poder colocarlo sobre el TextBox de la Page.

Thursday, July 01, 2004

Dynamic WebForms in ASP.NET

Muchas veces, segun los requerimientos del cliente, se da la necesidad de poder construir -en ciertos casos- formularios webs y grillas de datos de manera dinamica, a partir de cierta logica para definir la estrucura que se ha de presentar a traves de la interfaz del usuario.

Les muestro un ejemplo que puede darles las primeras pautas de como poder llevar a cabo estas tareas.

Archivo .ASPX

<form id="SimpleDynamic" method="post" runat="server">
<asp:PlaceHolder id="_holder" runat="server"></asp:PlaceHolder><br/>
<asp:Button id="_submit" runat="server" Text="Submit"></asp:Button><br/>
<asp:Label id="_post" runat="server"></asp:Label><br/>
<asp:Label id="_message" runat="server"></asp:Label><br/>
</form>

--- End File ---


Notese el uso del control PlaceHolder, que permitira alojar en el diferentes tipos de controles en tiempo de ejecucion, para su posterior presentacion e interaccion.


Archivo .CS

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DemoWebAppCSharp.Custom
{
/// <summary>
/// Descripción breve de Dynamic.
/// </summary>
public class SimpleDynamic : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button _submit;
protected System.Web.UI.WebControls.Label _message;
protected System.Web.UI.WebControls.Label _post;
protected System.Web.UI.WebControls.PlaceHolder _holder;

private void Page_Load(object sender, System.EventArgs e)
{
if( ! this.IsPostBack )
{
// Llenando el DropDownList de datos
this.FillList();
}
}

private void DefineForm()
{
TextBox Name = new TextBox();
Name.ID = "_name";

DropDownList List = new DropDownList();
List.ID = "_list";
List.AutoPostBack = true;

// Enganchando una funcionalidad al evento del DropDownList
List.SelectedIndexChanged += new EventHandler( this.List_SelectedIndexChanged );

// Colocando los controles en el PlaceHolder
this._holder.Controls.Add( Name );
this._holder.Controls.Add( List );
}

private void FillList()
{
// Solicitando el DropDownList al PlaceHolder
DropDownList List = (DropDownList)this._holder.FindControl( "_list" );

List.Items.Add( "-- Elija una opción --" );
List.Items.Add( "Simple Geek");
List.Items.Add( "Spoutlet" );
List.Items.Add( "Frabriq" );
}

// Funcion que se enganchara al evento correspondiente del DropDownList
private void List_SelectedIndexChanged( Object sender, EventArgs e )
{
DropDownList List = (DropDownList)sender;
this._post.Text = "Item selected: " + List.SelectedValue;
}

#region Código generado por el Diseñador de Web Forms
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Método necesario para admitir el Diseñador. No se puede modificar
/// el contenido del método con el editor de código.
/// </summary>
private void InitializeComponent()
{
this._submit.Click += new System.EventHandler(this.Submit_Click);
this.Load += new System.EventHandler(this.Page_Load);

this.DefineForm();
}
#endregion

// Posteando la informacion
private void Submit_Click(object sender, System.EventArgs e)
{
TextBox Name = (TextBox)this._holder.FindControl( "_name" );
DropDownList List = (DropDownList)this._holder.FindControl( "_list" );

this._message.Text = Name.Text + " : " + List.SelectedValue;
}
}
}

--- End File ---

Explicando, se contruira un TextBox y un DropDownList que esta seteado como AutoPostBack. De esta manera, cada vez que el usuario seleccione un item en la lista la pagina se posteara inmediatamente.

Revisando el metodo DefineForm. En este punto se muestra algo interesante, que es el enganche de un metodo hacia el evento de un control que se crea on-fly, es decir en tiempo de ejecucion.

Este metodo -el DefineForm- es llamado desde el InitializeComponent, que permite ejecutar operaciones sobre el metodo sobre-escrito OnInit. Que por cierto, es el lugar adecuado para poder colocar elementos dinamicos en la pagina.

Vale decir, que por el hecho de ser llamado desde dicho evento -el Init- la construccion dinamica de los elementos se realiza, por cada peticion de la pagina. Pues este evento siempre es solicitado por cada PostBack, al igual que el evento Load.

Es trabajo del modelo de objetos de ASP.NET, mantener el estado de los controles en la pagina por cada posterior PostBack.

Se remarca tambien, que los itemes adheridos al DropDownList, solo se realiza una sola vez, y esta operacion se lleva a cabo dentro del evento Load, validando el IsPostBack.

Se quiere dar a entender de esta manera, que la estructura del formulario es independiente de los datos que se plasmaran en dicha estructura. El evento Init es el lugar correcto para construir estructuras dinamicas y el Load para llenar datos en esas estructuras.

El metodo List_SelectedIndexChanged, solo hace un casting del objeto sender. Pues es justamente en esta variable que se encuentra una referencia al control que provoco el posting de la pagina, es decir el DropDownList.

Finalmente, el metodo Submit_Click, solicita al PlaceHolder, los controles que se adherieron al el, para presentar los valores que el usuario haya ingresado o seleccionado.

En conclusion, la intencion es poder construir cualquier estructura en el webform, a partir de una metadata, que brinde toda la informacion necesaria para la presentacion de dicha estructura y los datos que han de colocarse en el.

Les recomiendo descargar una interesante herramienta que he constuido, y que justamente parte de lo aqui mostrado es el 'core' de dicho producto.

http://groups.msn.com/mugperu/general.msnw?action=get_message&mview=1&ID_Message=11466

En dicho lugar, podran encontrar mayor informacion de como instalar el software y como trabajar con el.

Comparto tambien estos lugares de donde podran encontran mayor informacion sobre los eventos asociados a una pagina, el orden en el que son llamados y las actividades que se realizan en cada uno de ellos.

The ASP.NET Page Object Model
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspnet-pageobjectmodel.asp

The ASP.NET Page Life Cycle
http://www.15seconds.com/issue/020102.htm

Al igual que esto, la construccion de un DataGrid dinamico en funcion a las columnas y al contenido en cada DataGridItem, sean estos datos u otros controles, tambien es posible realizarlo, de esta forma.

Javier Luna
-- Software Architect and blogger too ;)
-- Movil: (51-1) 9-731-7187

"Indigo: Ports, Messages, Channels and more..."