Controles en Silverlight 1.0 Parte I
November 6th, 2007
Introducción
Hoy les quiero mostrar una forma sencilla de hacer controles utilizando la tecnología Silverlight 1.0 (con una ayudita de JavaScript). Debido a la extensión del documento voy a dividir la creación del control en 2 partes, una que muestre la creación del XAML de presentación y otra que muestre la lógica Javascript del control.
Seguramente se preguntarán porque hacer controles en Silverlight 1.0 si en la versión 1.1 es sencillo hacer controles utilizando C#. Estas son, en mi opinión, las ventajas:
Ø Primero, Silverlight 1.1 requiere el Framework .Net versión 3.0 instalado en la máquina servidor además de un Servidor que soporte ASP.Net. Silverlight 1.0 en cambio requiere solamente el plug-in de Silverlight instalado en la/s máquina/s cliente para funcionar. Esto permite que las aplicaciones en Silverlight 1.0 puedan ejecutarse en muchas máquinas sin la necesidad de instalar componentes adicionales (salvo el plug-in, que es necesario también para la versión 1.1) ni de modificar toda la aplicación para cambiar el look&feel.
Ø Segundo, Silverlight 1.0 se corre enteramente del lado cliente. Esto trae una enorme mejora en la performance de la aplicación. Y si bien con Ajax se logra lo mismo, Silverlight permite realizar con mucho menos esfuerzo componentes gráficos muy interesantes.
Ø Tercero, si planean hacer desarrollos basados en la plataforma .Net en el futuro (y especialmente en la rama de Foundation), migrar el look&feel de su aplicación a Silverlight 1.0 es un excelente primer paso. Estos controles y los XAML pueden utilizarse sobre la aplicación actual y van a seguir siendo utilizables cuando se quiera migrar esa aplicación a nuevas tecnologías.
Ø Cuarto, es bueno hacer controles. Los controles son porciones de código reutilizables, encapsulan funcionalidades coherentes, tienen un comportamiento previsible y una larga lista de etcéteras.
Ø Quinto y no menos importante, provee todas las ventajas del entorno Silverlight-WPF (es multi-browser, se ejecuta puramente del lado del cliente, es multiplataforma ya que para funcionar interpreta el XAML mediante DLR y otra larga lista de etcéteras).
En suma, Silverlight 1.0 puede ser implementado en su actual aplicación, con un mínimo esfuerzo y sin necesidad de cambiar nada más que cambiar el look&feel de las páginas de su aplicación. Lo único que requiere es un plug-in ActiveX para funcionar instalado del lado cliente y el esfuerzo requerido para estos cambios va a ser una inversión para el crecimiento de su aplicación en un futuro no muy lejano.
Desarrollo del XAML
Elegí hacer un control que simule el efecto de una gota de agua. No encontré ejemplos de esta animación en Silverlight y por eso me pareció interesante. Voy a utilizar el Microsoft Expression Blend para hacer el xaml. Lo primero es generar un objeto elipse sin relleno de 12 pixels de width y 10 de heigth (aproximadamente, en el código de ejemplo está hecho a ojo), el borde se configura con un gradiente radial con 3 tonalidades, que empiece y termine con color blanco y en el medio con color negro. También configuramos el ancho de la línea con valor 0 y la opacidad en un 30%. Copiamos 2 veces el objeto elipse y llamamos a las copias ElipseAgua, ElipseAgua1 y ElipseAgua2. Asegurarse que los 3 objetos estén centrados en un mismo punto de inicio (setear las propiedades Canvas.Left y Canvas.Top con los mismos valores).
Figura 1: Vista del Expression Blend con los elipses generados.
Ahora hacemos una animación que modifique los Elipses para simular el movimiento que se produce cuando cae una gota de agua. Para eso generamos una nueva animación a la que llamamos TimelineAgua y elegimos en orden el ElipseAgua, ElipseAgua1 y ElipseAgua2. Grabamos en el ElipseAgua la posición actual al comienzo de la animación y luego grabamos a los 2 segundos un movimiento estirando el objeto hasta cubrir todo el ancho del Canvas (teniendo en cuenta que tenga una forma de elipse y siempre manteniéndose centrado con respecto al centro original). También configuramos en ese momento la propiedad ancho de línea a 0.5. Luego, 1 cuarto de segundo después, volvemos a grabar el ElipseAgua pero el único cambio que hacemos es volver el ancho de línea al valor 0. Repetimos la animación para el ElipseAgua1, pero comenzando la animación 1 segundo después que el caso anterior y terminando 1 segundo más tarde y teniendo en cuenta estirar el objeto en una proporción menor que el caso anterior. El cambio de la propiedad ancho de línea a 0 debe durar un poco menos que 1 cuarto de segundo. Repetimos la animación para el caso ElipseAgua2 cambiando nuevamente el momento del inicio de la animación y el momento de finalización. Debería verse una animación similar a la que se muestra en la figura 2.
Figura 2: Vista del Expression Blend mostrando el Timeline de Animación.
Nota: Para hacer las animaciones recordar tener presionada la opción de recording del timeline
Nota II: Recordar siempre mantener el mismo punto de centro para las 3 elipses
Nota III: Una vez finalizado el proceso de la animación hay que modificar el XAML agregando el parámetro x:Name a cada uno de los Storyboard de la misma. Silverlight requiere que cada elemento Storyboard tenga un nombre propio que lo identifique. En este caso llamamos a los otros 2 storyboard con los nombres TimelineAgua1 y TimelineAgua2.
Nota IV: Para hacer luego el control con el mismo XAML, vamos a achicar el tamaño del canvas a las medidas Width="174" y Height="68".
Para probar la animación cree una función start que vinculé con el evento MouseLeftButtonDown del Canvas. El código es muy sencillo:
function start(sender, args)
{
var elipse = sender.findName("ElipseAgua");
var elipse1 = sender.findName("ElipseAgua1");
var elipse2 = sender.findName("ElipseAgua2");
elipse["Canvas.Left"] = args.getPosition(null).x;
elipse["Canvas.Top"] = args.getPosition(null).y;
elipse1["Canvas.Left"] = args.getPosition(null).x;
elipse1["Canvas.Top"] = args.getPosition(null).y;
elipse2["Canvas.Left"] = args.getPosition(null).x;
elipse2["Canvas.Top"] = args.getPosition(null).y;
sender.findName("TimelineAgua").begin();
sender.findName("TimelineAgua1").begin();
sender.findName("TimelineAgua2").begin();
}