martes, 21 de febrero de 2012

Controles extendidos



Controles personalizados es uno de los temas más interesantes y desafiantes de la programación en ASP .NET, todo esto debido a la complejidad y versatilidad que se tiene para poder crearlos.

Podemos dividir los controles personalizados en las siguientes categorías (las pondré en inglés dado que la mayor referencia de información la encontrarán en ese idioma):

1. Web controls.    También llamados "from the scratch", debemos crear cada pedazo de código HTML que conforme a
nuestro control.
2. Composite controls.    Conformado a partir de la agrupación de controles existentes.
3. Inherited controls (extended controls).    Se le agregan o modifican las características funcionales o visuales de controles que ya existen.
4. Templated controls.    Permiten la personalización de los componentes que los conforman... a mi forma de ver, estos son los más complejos.
5. Web user controls.    Recomendados para controles que sólo se utilizarán en un proyecto.

Esta entrada está dedicada a explorar de una forma simple los controles compuestos (composite controls) a través de la creación de un control que nos ayudará a cargar un archivo al servidor sin tener que esperar a que el usuario dé clic en un botón o link en especial para poder enviar el archivo al servidor.

Los controles compuestos son fáciles de identificar dado que heredan de la clase CompositeControl
(System.Web.UI.WebControls)

Vamos a crear un proyecto de tipo "Biblioteca de clases" en Visual Studio y lo llamaremos "MyControls", allí agregamos una clase que llamaremos "ImmediateUploader".

Hacemos referencia al ensamblado System.Web y agregamos las siguientes líneas a nuestro archivo ImmediateUploader.cs:

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

Ahora haremos que nuestra clase ImmediateUploader herede de CompositeControl:

public class ImmediateUploader : CompositeControl

Recordemos que los controles compuestos son aquellos que están formados a partir de controles ya existentes, es decir que toman las características y ventajas que tienen y los agrupan de tal forma de exponer la funcionalidad deseada.

En nuestro caso queremos mostrar un control donde el usuario pueda elegir un archivo de su computadora e inmediatamente después de darle clic al botón "Abrir" u "Ok" comience la carga del archivo.

De primera vista es fácil identificar que necesitaremos un control FileUpload pero para enviar el post de la página con el archivo seleccionado utilizaremos un botón oculto el cual disparará el evento deseado.

En los controles personalizados existe un método llamado CreateChildControls el cual es llamado cuando se necesita que el control genere la jerarquía de controles que lo conformarán.    Dado que CompositeControl viene vacío, aprovecharemos este método para agregar los controles FileUpload y Button:

protected override void CreateChildControls()
{
  FileUpload fu = new FileUpload();
  fu.Width = this.Width;
  fu.ID = "FileUpload";
 
  Button upload = new Button();
  upload.Style.Add(HtmlTextWriterStyle.Display, "none");
  upload.Click += new EventHandler(upload_Click);
  upload.ID = "Upload";
 
  Controls.Add(fu);
  Controls.Add(upload);
 
  fu.Attributes.Add("onchange", "raiseUploadClick_" + this.ClientID + "('" + upload.ClientID + "')");
}

Notemos lo siguiente:

1. Estamos ocultando el botón al asignarle a su estilo un "display" un valor de "none".
2. Estamos agregando el manejador del evento Click del botón para enterarnos del momento en que se realiza el POST por parte del navegador.
3. Estamos agregando un atributo "onchange" a nuestro control FileUpload para que nos avise a través de una función javascript que el usuario seleccionó un archivo.

Para avisarle a la página (donde se estará utilizando nuestro control) que se ha elegido un archivo, nuestro control lanzará un evento llamado FileUploaded.    Para lograrlo escribiremos el siguiente código:

public delegate void FileUploadedHandler(object sender, HttpPostedFile postedFile);
public event FileUploadedHandler FileUploaded;
void OnFileUploaded(HttpPostedFile postedFile)
{
  FileUploadedHandler handler = FileUploaded;
  if (handler != null)
    handler(this, postedFile);
}
void upload_Click(object sender, EventArgs e)
{
  if ((FindControl("FileUpload") as FileUpload).HasFile)
  {
    OnFileUploaded((FindControl("FileUpload") as FileUpload).PostedFile);
  }
}

Por último, vamos a modificar la forma en que nuestro control realizará el Render de su estructura:

protected override void Render(HtmlTextWriter writer)
{
  base.Render(writer);
  StringBuilder sb = new StringBuilder();
  sb.Append("<script type=\"text/javascript\">");
  sb.Append("function raiseUploadClick_" + this.ClientID + "(b){");
  sb.Append("document.getElementById(b).click()");
  sb.Append("}");
  sb.Append("</script>");
  writer.Write(sb.ToString());
}

La llamada base.Render hace que cada uno de los controles que se encuentran en la propiedad Controls de nuestro control compuesto sea traducido al HTML que le corresponde y escrito en el HtmlTextWriter.

Podemos ver que después de la llamada a base.Render estamos escribiendo una función de javascript para que se lance el post de nuestra página a través de la llamada del evento click del botón oculto.

Compilemos nuestro proyecto (Shift + Ctrl + B) y para probarlo haremos los siguiente:

1. Agregaremos un sitio web a nuestra solución (File - Add - New Web Site)
2. Abrimos en modo diseño nuestra página Default.aspx
3. Desplegamos la barra de herramientas y vamos a ver que aparece un control llamado ImmediateUploader, lo arrastramos a nuestra página.

Ejecutemos nuestro sitio web y veremos que al elegir un archivo y abrirlo se dispara el POST de la página.

Cerremos el navegador y finalicemos nuestro ejercicio cachando el evento y agregando metadatos a nuestro control.

Abrimos a nuestra clase ImmediateUploader.cs y agregamos el atributo "DefaultEvent" a nuestra clase de la siguiente forma:

[DefaultEvent("FileUploaded")]
public class ImmediateUploader : CompositeControl

Y también agregaremos un atributo "Description" a nuestro evento de forma tal que el programador que utilice nuestro control entienda para qué sirve o cuándo se dispara nuestro evento:

[Description("Raised when end user select a file from its computer")]
public event FileUploadedHandler FileUploaded;

Compilamos de nuevo el proyecto (Shift + Ctrl + B) y abrimos "Default.aspx" en vista diseño para dar doble clic sobre nuestro control ImmediateUploader.    Veremos que nos crea un procedimiento (Default.aspx.cs) que estará ejecutándose cuando el evento FileUploaded de
nuestro control se haya disparado:

protected void ImmediateUploader1_FileUploaded(object sender, HttpPostedFile postedFile)
{
}

¿Qué podemos hacer con postedFile?, depende del objetivo del control en la página, eso queda a su criterio dependiendo de las necesidades u objetivos de su página.

No hay comentarios.:

Publicar un comentario