¡Hola Mundo Microsoft Edition!

Este post no es sobre programación, ni sobre ningún producto de Microsoft como tal, pero está relacionado con Microsoft.

Después de más de 5 años trabajando con Plain Concepts he decidido dar el salto y cambiar de trabajo. Ha sido una decisión difícil porque Plain Concepts es un sitio donde me he sentido muy a gusto y donde más he tenido la oportunidad de aprender muchísimo.

Pero las cosas cambian y como no, los trabajos también. Así que a partir de ahora trabajo en Microsoft como Technical Evangelist de Windows Azure en el equipo de DPE. Es todo un placer y un honor para mí formar parte de esta gran familia que es Microsoft en España, y sobre todo de tener la oportunidad de compartir mi pasión por la tecnología de Microsoft.

Como os podréis imaginar, parte de mi trabajo consiste en hacer que las personas, las empresas y los desarrolladores utilicen y saquen provecho al máximo de todo lo que Microsoft tiene que ofrecer en tecnología.

Eso significa que este blog seguirá siendo técnico, sobre tecnología de Microsoft, no solo Windows Azure, si no cualquier tipo de tecnología.

Luis Guerrero.

3 formas diferentes de publicar una Web en Windows Azure.

Introducción

Windows Azure es una plataforma muy flexible en la que se pueden publicar y consumir todo tipo de aplicaciones. En este post se repasarán las 3 formas que hay de publicar un Web, viéndose las ventajas e inconvenientes de las tres formas.

Las tres maneras son: Máquinas virtuales (Infraestructura como servicio IaaS), Servicios de nube (Plataforma como servicio, PaaS) y Windows Azure Web Sites.

Maquinas Virtual, IaaS

Las máquinas virtuales, como su nombre indica es la capacidad que tiene Windows Azure de hostear una máquina virtual basada en ficheros vhdx. Desde el portal de Windows Azure se puede seleccionar nueva máquina virtual y seleccionar creación rápida. De esa manera se tiene una maquina encendida y funcionado.

Una vez generada la VM se puede instalar desde ahí, Internet Information Services y copiar nuestra aplicación Web en la máquina.

Ventajas

  • Se tiene toda la flexibilidad para instalar y administrar la máquina virtual desde escritorio remoto.
  • Se puede alojar la aplicación web en Linux.
  • La máquina virtual es persistente.

Inconvenientes

  • No hay mecanismo definido para publicar la web. Copiar los ficheros por escritorio remoto a mano o cualquier otro mecanismo.
  • Hay que configurar e instalar IIS y ASP.NET.
  • Se tiene que configurar y administrar el balanceo de carga a mano para el puerto de HTTP/HTTPS. Cuando se quieran agregar más maquinas se tiene que hacer a mano.
  • Las tareas de administración se tienen que repetir para todas las máquinas.
  • El usuario es responsable del mantenimiento del S.O.

Servicios en la nube (Plataforma como servicio)

Este fue el primer servicio con el que se lanzó Windows Azure. Permite empaquetar una aplicación Web, independientemente del tipo de lenguaje o runtime, y publicar esa aplicación en el número de máquinas que se hayan designado en el fichero de configuración.

En este método de publicar aplicaciones web, se genera una máquina virtual, se instala y configura IIS con los valores presentes en el fichero de configuración del servicio y se copia el código de la aplicación web al directorio de publicación de IIS.

El proceso es completamente automático y no requiere atención por el usuario en ningún momento. No tiene límite en cuanto al número de instancias de máquinas. (Cómo curiosidad decir que el máximo de máquinas que el autor ha configurado han sido 750, toda una pasada)

Ventajas

  • Flexibilidad para empaquetar y publicar la aplicación.
  • No requiere configuración por parte del usuario, está completamente automatizado.
  • Permite incrementar y reducir el número de instancias a petición del usuario, también de manera automática.
  • Tiene soporte para el autoscale de Windows Azure.
  • Permite configurar aplicaciones web en subdominios y subcarpetas como aplicaciones (en IIS).
  • Permite configurar todos los certificados SSL que se deseen.

Inconvenientes

  • Las máquinas donde se ejecutan no son persistentes. Hay que guardar todos los datos fuera de la máquina donde se ejecuta. Por ejemplo en un Windows Azure Storage.
  • Las peticiones Http al servicio no tienen afinidad, lo que significa que cualquier petición puede ir a cualquiera de las maquinas del servicio en la nube. Hay que configurar la sesión de ASP.NET para que se guarde en Sql Server, Windows Azure Cache Service o Windows Azure Storage.
  • Hay que ser capaz de empaquetar la aplicación web y todas sus dependencias de software.

Windows Azure Web Sites

Windows Azure Web Sites es otro servicio de Windows Azure enfocado a la publicación de aplicaciones Web pero, ¿Cuál es la diferencia con un servicio en la nube? Los servicios en la nube pueden contener, además de un rol de tipo Web, un rol de tipo de trabajo. Es decir un servicio en la nube es una colección de roles de trabajo y de web. Un rol de trabajo es como un servicio de Windows, pero que se ejecuta en Windows Azure y ejecuta tareas en segundo plano. Esas tareas pueden ser generar informes, consolidad información en la base de datos, envíos masivos de emails, cambio de resolución de imágenes y todo aquello que se tiene que hacer en nuestra aplicación web, pero que no se quiere que se impacte en el rendimiento del sitio web.

Volviendo al tema de Windows Azure Web Sites, este servicio ofrece tres modos de funcionamiento:

  • Gratis: los usuarios tienen hasta un máximo de 10 sitios web de manera gratuita, 1 Gb de espacio en disco y 165 megas de salida al día.
  • Compartida: hasta un máximo de 100 sitios web, que se pueden escalar hasta un máximo de 6 instancias, con 1 Gb de espacio en disco y el tráfico de salida se factura al precio estándar.
  • Estándar: hasta un máximo de 500 sitios web con una CPU dedicada, 10Gb de espacio en disco y el tráfico de salida se factura al precio estándar.

Con estas tres configuraciones se ofrecen diferentes maneras y precios de alojar tu aplicación web según los requerimientos del usuario.

Otra de las diferencias con respecto a los servicios en la nube, es la forma de publicar tu sitio web. WAWS ofrece varias maneras de publicar:

  • Microsoft Web Publishing: Permite publicar un proyecto de web directamente desde Visual Studio.
  • Team Foundation Service: se pude publicar directamente desde TFS Service, la solución en la nube de TFS. No funciona con TFS on premises.
  • Repositorio local de Git: puedes hacer push desde un repositorio local a uno online que representa tu sitio web.
  • Github es un servicio de hosting en Git de terceros.
  • Dropbox: te permite configurar una carpeta de la cuenta para publicar directamente en tu sitio web.
  • Bitbucket: otro servicio de hosting de Git como Github.
  • Codeplex: el servicio de hosting de proyectos de Microsoft.
  • External repository: un repositorio externo en una URL.

Otro de los aspectos que se pueden configurar en el servicio es la versión de .NET Framework en la que se ejecuta la Web, V3.5 o V4.5. Otro de los lenguajes que vienen instalados es PHP que se puede deshabilitar o cambiar la versión de 5.3 a 5.4.

Ventajas

  • Flexibilidad en cuanto al método de publicación de la web.
  • Perfecta para trabajo en equipo.
  • Rapidez, dar de alta un sitio web en Windows Azure son segundos y tener tu web online también.

Inconvenientes

  • No se puede acceder a la configuración de IIS, solamente la configuración que se ofrece desde el portal de administración de Windows Azure.
  • El número de instancias de los modos de compartido y estándar son máximo 6 y 10 respectivamente. En los servicios de la nube no hay límite.

Espero que el artículo haya sido de ayuda para decidirse con qué servicio de Windows Azure se ajusta mejor a las necesidades de cada proyecto.

Migrando una aplicación de Windows 8 a Windows 8.1. Caso práctico Reddit 8.1

Windows 8.1

El lanzamiento de Windows 8.1 es inminente y al ser una actualización gratuita del Sistema Operativo, será mucha gente, por lo decir la mayoría, la que se instale la actualización. Eso significa que si eres desarrollador de Aplicaciones de la Tienda de Windows, estarás muy ocupado ahora mismo migrando tu aplicación de Windows 8 a Windows 8.1.

En esta serie de artículos se van a detallar los diferentes pasos de migración de las aplicaciones. Y para tener un ejemplo concreto se hablará de una aplicación concreta Reddit8. Un cliente de Reddit para Windows 8 que ya está en la Store y que he procedido a migrar a Windows 8.1. Con este pretexto pretendo enseñar los diferentes desafíos que he tenido a nivel personal durante la migración y como la aplicación ha evolucionado.

TfsService

La primera recomendación antes de empezar con la migración es la de tener un control de código fuente. Esto es así porque cuando se empiece la migración, el proceso es irreversible y no se podrá volver a la versión anterior de Windows. Teniendo esto en cuenta se extrae que las aplicaciones de Windows 8.1 no son compatibles con Windows 8. Pero eso no significa que no se quiera seguir dando soporte a los clientes que tengan Windows 8.

Pon un control de código fuente en tú vida.

Es por eso que el primer paso antes de nada, es asegurarse de que se está trabajando con un control de código fuente. Yo recomiendo usar Team Foundation Service (http://tfs.visualstudio.com/) porque se integra perfectamente con Visual Studio, incluye gestión de elementos de trabajo, está en la nube y accesible desde cualquier sitio, permite trabajar con equipos de manera ágil y planificar los procesos de desarrollo.

Y lo mejor de todo esto, es que se puede usar de manera gratuita hasta 5 usuarios simplemente teniendo una cuenta de Microsoft.

image

Branch

Lo primero que hay que hacer, después de tener el código fuente en TfsService es crear una rama dentro del proyecto en el que se esté trabajando. En el ejemplo que se está siguiendo en el artículo, se pasó de llamar la carpeta Reddit8 a llamarse Reddit8.1 y ser una rama de la carpeta antes mencionada.

Migrando a Windows 8.1

La primera vez que se abre un proyecto de Windows 8 en Visual Studio 2013 Preview, VS avisa de que la solución es un proyecto de Windows 8 y que si se desea migrar a Windows 8.1. Si se elige la opción de sí, se migran todos los proyectos que tengan la solución a Windows 8.1 siendo este proceso irreversible.

En el caso de querer hacer el proceso de migración en otro momento lo único que hay que hacer es ir a las propiedades del proyecto, y en la pestaña de aplicación aparece un desplegable con la opción de cambiar la plataforma.

image

Errores de compilación

Una vez que se ha realizado el proceso de cambiar la plataforma de destino de todos los proyectos, 4 en Reddit8, se compila el proyecto y aparecen los primeros errores.

image

Que se pueden resumir en errores del framework de publicidad de Microsoft, que no es la versión correcta, y el resto de errores, son a la hora de actualizar las Tiles secundarios de la aplicación. Esto es comprensible, porque una de las características nuevas de Windows 8.1 es justamente que se han cambiado el tamaño de los Tiles.

Estos errores son fáciles de corregir. El primero simplemente es bajarse la versión para Windows 8.1 del SDK de Advertising de Microsoft de aquí. (http://msdn.microsoft.com/en-us/library/dn283993(v=msads.10).aspx)

Y el resto de errores no son tan inmediatos de corregir, porque la forma en la que se generar los Tiles secundarios ha cambiado, así que no es simplemente corregir los errores sino que hay que añadir nueva funcionalidad.

API Obsoletas

Después de corregir esos errores de los Tiles secundarios aparecen una lista de errores más grande con API que se han hecho obsoletas en Windows 8.1 y que hay otra manera de consumirlas. Se resumen brevemente en:

  • ScrollViewer. Ya no tiene métodos específicos para cambiar el desplazamiento vertical y horizontal, sino que se han integrado todos en un método llamado ChangeView que te permite cambiar desplazamiento vertical, horizontal y valor de zoom.
  • ItemsControls. Ya no se puede usar la propiedad ItemContainerGenerator que permite obtener el contenedor de una colección de elementos a partir de índice o del valor. Simplemente se ha de eliminar el texto de ItemContainerGenerator, ya que, por ejemplo, ContainerFromIndex forma parte de la clase ItemsControls y no de la clase ItemContainerGenenator.
  • ApplicationView.Value. Si se estaba usando la plantilla de código que venía con Visual Studio que incluía una clase llamada LayoutAwarePage, se ha simplificado el desarrollo de este componente.
  • DataPackage. El método SetUri, se ha cambiado por SetWebLink o SetApplicationLink.

Cambio en experiencia de usuario

Las novedades de Windows8.1 no solo están en el código sino también en los controles nuevos que permiten acercase mucho más a la experiencia de usuario de Windows8 de manera más sencilla.

A continuación se verán algunos de los cambios de funcionalidad que se han podido mejorar con controles y funcionalidad específica de Windows 8.1.

HomePage

La página principal de la aplicación ha pasado de tener un GridView con dos categorías, frontpage y subreddits.

image

A esto otro.

image

El cambio ha sido a mucho mejor. Se ha utilizado el nuevo control Hub, que soporta zoom semántico y organizar la información en secciones para mostrarlas todas seguidas.

En el caso concreto de Reddit8 se muestra una imagen que pertenece a la frontpage, que en la medida de lo posible se intenta que no se repita y el usuario siempre pueda ver contenido directamente.

El Hub control es una colección de HubSections, incluyendo una cabecera de todo el control. Cada una de las secciones incluye una cabecera y un contenido que es establecido a través de una plantilla. Así de esta manera podemos personalizar tanto el contenido como el aspecto de cada una de las secciones.

clip_image011Otro cambio incluido en la app, es la nueva barra de búsqueda que tiene la misma funcionalidad que el charm de búsqueda de Windows 8. Además se pueden seguir haciendo sugerencias de contenido conforme el usuario escribe. De esta manera ya no es necesario activar la capacidad de busqueda, ya que los dos son incopatibles entre sí.

Vista de Subreddit

Esta es la vista antes del cambio. Un GridView exactamente igual que en la página principal, pero que además mostraba información sobre el subreddit.

image

Esta es la manera en la que se muestra el contenido ahora:

image

Se ha vuelto a utilizar el mismo tipo de layout que en la página principal, pero esta vez el título de Hub es el nombre del subreddit (/r/windows8). Se ha eliminado la información del subreddit por considerarse superflua y se ha puesto en la barra de comandos.

image

En la barra de comandos de abajo aparecen, utilizando la nueva CommandBar, los comandos que son principales y secundarios. Los principales son refrescar, subscribirse al subreddit (si has iniciado sesión) agregarlos a favoritos para la navegación de la barra de arriba. En el caso de los comandos secundarios está anclar al inicio e información. Este último utiliza el controls Flyout para mostrar contenido de manera mucho más cómoda y directa que antes.

El desarrollo de la barra de comandos, se ha simplificado y se ha hecho más acorde con el estilo de aplicaciones de Windows 8.

Live tiles

Otra de las novedades de Windows 8.1 es el cambio de tamaño de los Tiles en el menú de inicio. Ahora cuando un usuario quiere anclar un elemento puede elegir el tamaño en el que quiere anclarlo.

clip_image018

Eso significa que se puede tener tiles en todos los tamaños, incluso cuando el usuario los crea.

clip_image019

Conclusión

Windows 8.1 es una mejora muy sustancial de las API que Microsoft ofrece a los desarrolladores. Ahora es más fácil que nunca adecuar nuestras aplicaciones de Windows 8 con la estética de Modern UI. Y en el caso concreto de Reddit8 la mejora, desde mi punto de vista, es más que notable. Ahora el contenido es el centro del diseño y de la funcionalidad.

Si quieres probar la nueva funcionalidad de este cliente de Reddit, puedes ir aquí para descargarte las betas que hay públicas.

http://www.reddit.com/r/reddit8/

Usando un ContentControl como hijo de un Control SemanticZoom en XAML+C# de Windows 8

El Zoom Semantico de Windows 8 permite que los usuarios puedan hacer zoom sobre el contenido de una aplicación. Esto no significa que el Zoom esté centrado en pixeles como en normal, sino que el zoom está centrado en contenido. Veamos un ejemplo de esto:

SemanticZoom

El zoom semántico permite tener dos vistas de un mismo contenido. Una vista de zoom cercano y otra vista de zoom lejano, ambas se puede intercambiar a través de interacciones de la plataforma como teclado, ratón y táctil. El código XAML correspondiente a este control sería este:

<SemanticZoom>
<SemanticZoom.ZoomedInView>
<GridView />
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView />
</SemanticZoom.ZoomedOutView>
</SemanticZoom>

Este control tiene dos propiedades, ZoomedInView y ZommedOutView que son de tipo ISemanticZoomInformation. Esta interfaz es que permite la comunicación con el sistema de Zoom semántico para así poder cambiar la vista en cualquier momento que el usuario lo desee. Los dos únicos controles que implementan esa interfaz son el GridView y el ListView.

clip_image002

clip_image004

Aquí se puede ver un ejemplo de aplicación con dos vistas en zoom semántico.

Pero, ¿qué pasa si quiero usar otro control que no sea un GridView como hijo del Zoom Semántico?

SemanticZoomHost

Este control permite tener cualquier árbol de objetos en xaml y que a su vez pueda ser hijo de cualquiera de las dos propiedades del SematicZoom. Esta clase implementa la interfaz ISemanticZoomInformation, pero permite que la información de zoom se transadle a uno de sus hijos.

image

De esta manera se puede no tener un GridView únicamente sino que se puede tener cualquier tipo control y dentro de ese árbol un control GridView.

Ejemplo

<SemanticZoom>
<SemanticZoom.ZoomedInView>
<controls:SemanticZoomHost>
<StackPanel>
<TextBlock Text="Titulo"></TextBlock>
<GridView controls:SemanticZoomHost.SemanticZoomHost="true"></GridView>
</StackPanel>
</controls:SemanticZoomHost>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView />
</SemanticZoom.ZoomedOutView>
</SemanticZoom>

¿Cómo está implementado SemanticZoomHost?

La idea que hay detrás es muy sencilla, una clase que herede de ContentControl, para que así pueda tener hijos en el árbol visual, y que implemente la interfaz ISemanticZoomInformation. La clase en sí no hace nada con la implementación de la interfaz, es decir, que simplemente traslada las llamadas al control que realmente implementa ISemanticZoomInformation. Es por eso que el control SemanticZoomHost tiene una propiedad de tipo ISematicZoomInformation que será el control que manejará el zoom semántico.

public ISemanticZoomInformation ISemanticZoomInformationHost
{
get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); }
set { SetValue(ISemanticZoomInformationHostProperty, value); }
}

public static readonly DependencyProperty ISemanticZoomInformationHostProperty =
DependencyProperty.Register(
"ISemanticZoomInformationHost",
typeof(ISemanticZoomInformation),
typeof(SemanticZoomHost),
new PropertyMetadata(null));

Attached Property

Esa propiedad tiene que ser establecida de manera automática, es decir, que de alguna manera el sistema tiene que buscar que control del árbol visual es el encargado de establecer esa propiedad. Es en este punto donde las Attached property entran en acción para que el propio control SemanticZoomHost defina una attached property llamada SemanticZoomHost de tipo booleana, que será utilizada para marcar que control en el árbol visual es el encargado de ser el receptor de la información del zoom semántico.

public static bool GetSemanticZoomHost(DependencyObject obj)
{
return (bool)obj.GetValue(SemanticZoomHostProperty);
}

public static void SetSemanticZoomHost(DependencyObject obj, bool value)
{
obj.SetValue(SemanticZoomHostProperty, value);
}

public static readonly DependencyProperty SemanticZoomHostProperty =
DependencyProperty.RegisterAttached(
"SemanticZoomHost",
typeof(bool),
typeof(SemanticZoomHost),
new PropertyMetadata(false));

Buscando ese control en el árbol visual

La última tarea que tiene la clase es buscar los controles en el árbol visual que tengan esa propiedad a true y establecer esa propiedad. Como el control es del tipo ContentControl, tiene el método OnApplyTemplate, pero que en este caso no se puede usar porque el control en sí se ha cargado pero no todos los hijos, así que hay que usar el evento Loaded para saber cuándo se ha terminado de cargar todo el árbol visual de todos los hijos.

void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e)
{
List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this);
foreach (var item in all)
{
if (SemanticZoomHost.GetSemanticZoomHost(item))
{
ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item;
semanticZoom.IsActiveView = isActiveView;
semanticZoom.IsZoomedInView = isZoomedView;
semanticZoom.SemanticZoomOwner = owner;

this.ISemanticZoomInformationHost = semanticZoom;
break;
}
}
}

El resto de la implementación son los métodos de la interfaz ISemanticZoomInformation.

Implementación

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace SematicZoomDemo.Controls
{
public class SemanticZoomHost : ContentControl, ISemanticZoomInformation
{
public SemanticZoomHost()
{
Loaded += OnSemanticZoomHostLoaded;
}

void OnSemanticZoomHostLoaded(object sender, RoutedEventArgs e)
{
List<DependencyObject> all = ControlTreeHelper.GetAllControlsByType<DependencyObject>(this);
foreach (var item in all)
{
if (SemanticZoomHost.GetSemanticZoomHost(item))
{
ISemanticZoomInformation semanticZoom = (ISemanticZoomInformation)item;
semanticZoom.IsActiveView = isActiveView;
semanticZoom.IsZoomedInView = isZoomedView;
semanticZoom.SemanticZoomOwner = owner;

this.ISemanticZoomInformationHost = semanticZoom;
break;
}
}
}

public void CompleteViewChange()
{
ISemanticZoomInformationHost.CompleteViewChange();
}

public void CompleteViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination)
{
ISemanticZoomInformationHost.CompleteViewChangeFrom(source, destination);
}

public void CompleteViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination)
{
ISemanticZoomInformationHost.CompleteViewChangeTo(source, destination);
}

public void InitializeViewChange()
{
ISemanticZoomInformationHost.InitializeViewChange();
}

public void MakeVisible(SemanticZoomLocation item)
{
ISemanticZoomInformationHost.MakeVisible(item);
}


public void StartViewChangeFrom(SemanticZoomLocation source, SemanticZoomLocation destination)
{
ISemanticZoomInformationHost.StartViewChangeFrom(source, destination);
}

public void StartViewChangeTo(SemanticZoomLocation source, SemanticZoomLocation destination)
{
ISemanticZoomInformationHost.StartViewChangeTo(source, destination);
}

public bool IsActiveView
{
get
{
return ISemanticZoomInformationHost.IsActiveView;
}
set
{
if (ISemanticZoomInformationHost == null)
{
isActiveView = value;
}
else
{
ISemanticZoomInformationHost.IsActiveView = value;
}
}
}

public bool IsZoomedInView
{
get
{
return ISemanticZoomInformationHost.IsZoomedInView;
}
set
{
if (ISemanticZoomInformationHost == null)
{
isZoomedView = value;
}
else
{
ISemanticZoomInformationHost.IsZoomedInView = value;
}
}
}

public SemanticZoom SemanticZoomOwner
{
get
{
return ISemanticZoomInformationHost.SemanticZoomOwner;
}
set
{
if (ISemanticZoomInformationHost == null)
{
owner = value;
}
else
{
ISemanticZoomInformationHost.SemanticZoomOwner = value;
}
}
}

private SemanticZoom owner;
private bool isZoomedView;
private bool isActiveView;

public ISemanticZoomInformation ISemanticZoomInformationHost
{
get { return (ISemanticZoomInformation)GetValue(ISemanticZoomInformationHostProperty); }
set { SetValue(ISemanticZoomInformationHostProperty, value); }
}

public static readonly DependencyProperty ISemanticZoomInformationHostProperty =
DependencyProperty.Register(
"ISemanticZoomInformationHost",
typeof(ISemanticZoomInformation),
typeof(SemanticZoomHost),
new PropertyMetadata(null));

public static bool GetSemanticZoomHost(DependencyObject obj)
{
return (bool)obj.GetValue(SemanticZoomHostProperty);
}

public static void SetSemanticZoomHost(DependencyObject obj, bool value)
{
obj.SetValue(SemanticZoomHostProperty, value);
}

public static readonly DependencyProperty SemanticZoomHostProperty =
DependencyProperty.RegisterAttached(
"SemanticZoomHost",
typeof(bool),
typeof(SemanticZoomHost),
new PropertyMetadata(false));
}
}

 

Conclusión

En SematicZoom es un control muy útil a la hora de hacer zoom semántico en una aplicación de Windows 8, pero desde mi punto de vista es una limitación que solamente se pueda establecer un ListView o un GridView. En muchas ocasiones los layouts que se puede obtener son muy complejos.

ControlTreeHelper

Como podéis ver en el código se utiliza una clase llamada ControlTreeHelper. Esta clase fue diseñada para usarse en WPF (Windows Presentation Foundation), ¡qué tiempos aquellos!.

La clase contiene métodos que nos permite recorrer el árbol visual de manera cómoda, paso a comentar algunos de esos métodos.

  • List<T> GetAllControlsByType<T>(DependencyObject root): Obtiene todos los controles hijos filtrados por el tipo T y los devuelve en una lista de T.
  • T FindUniqueParentControl<T>(DependencyObject leaf): Busca un padre directo en el arbol visual de tipo T.
  • T FindNameInVisualTree<T>(DependencyObject root, string name): Busca un elemento en el arbol visual de tipo T con el nombre pasado por parametro.

El código de ejemplo se puede descargar de aquí.

Luis Guerrero.

Optimizaciones de JavaScript utilizadas en el proyecto de Prometheus

Introducción

Para los que no lo sepáis, próximamente se estrena la película Prometheus en Estados Unidos y desde Plain Concepts hemos desarrollado el training center del sitio web. Se puede acceder desde este enlace: http://www.projectprometheus.com/trainingcenter/. El proyecto ha estado financiado por Microsoft, más concretamente por el equipo de Internet Explorer, así que como página web que es, se ha desarrollado utilizando las últimas tecnologías web: HTML5 + CSS3.

Training center

El centro de entrenamiento es un sitio web donde los candidatos al proyecto Prometheus, de la empresa Weyland, puede probar su valía. El entrenamiento cuenta con 5 pruebas (juegos) que el recluta tiene que completar en un tiempo determinado. Una vez superadas las cinco pruebas el recluta puede formar parte de Weyland Industries. Los cinco juegos han sido desarrollados por gente de Plain Concepts:

Cada uno de los cuales ha desarrollado uno de los juegos del centro de entrenamiento. En mi caso he desarrollado el cubo de rubick en 2 y 3 dimensiones.

clip_image001

Aquí se puede ver una captura del juego en Internet Explorer llamado Prefrontal Cortex.

HTML5 / Javascript

Todos los juegos han sido desarrollados en HTML5 utilizando JavaScript para la parte de programación, en mi caso he utilizado Canvas para dibujar los cubos. Eso significa que todos los juegos funcionan perfectamente en todos los navegadores modernos, incluyendo Google Chrome, Firefox, Safari, Opera y Internet Explorer 9 y 10.

Para el desarrollo de los juegos se creo un motor en JavaScript que nos permitiera dibujar en un Canvas la geometría de los modelos de los cubos. Este motor no utiliza WebGL para renderizar los cubos, porque Internet Explorer no tiene soporte (además de en el resto es experimental), por lo que se opto por hacer un motor grafico completo desde cero. Es decir, todo el pipeline de grafica se tiene que hacer en JavaScript, esto significa, entre otras cosas, que tenemos que emular por software como funciona una tarjeta gráfica y eso normalmente es más lento que el propio hardware. Así que el desafío de implementar un pipeline gráfico por software es mayor ya que tiene que tener un rendimiento aceptable.

JavaScript 101

JavaScript es un lenguaje en el que existen varios tipos de datos básicos con los que podemos trabajar.

  • Object
  • Array
  • Number
  • String
  • Boolean

Los objetos son la forma más común a la hora de trabajar en JavaScript y se pueden utilizar de muchas maneras.

La forma más sencilla de crear un objeto es:

   1: var myObject = {}

A partir de ahí se pueden ir agregando propiedades al objeto sin ningún tipo de restricción. No son propiedades como las que se puede estar acostumbrado en C#, sino que el tipo object se comporta como una especie de diccionario de pares nombre valor.

Se pueden crear propiedades de la siguiente manera a un objeto previamente definido.

   1: myObject.name = ‘Luis’;

   2: myObject.number = 42;

Así el objeto pasará a tener dos propiedades, una llamada ‘name’ con el valor ‘Luis’ y otra llamada ‘number’ con el valor ‘42’.

También se puede acceder a esas propiedades como si de un diccionario se tratase. Las dos formas son igual de válidas y correctas.

   1: var name = myObject[‘name’];

Después de la ejecución de esta línea de código lo que se establece en la variable name es el valor de ‘Luis’, previamente establecido.

Con esta nueva forma de acceder a las propiedades no solo se pueden leer valores almacenados en un objeto sino que también se pueden guardar.

myObject[‘currentDate’] = new Date();

Vector3

Como ejemplo de objeto se va a definir Vector3; un vector de 3 dimensiones.

var vector3 = {x: 1, y: 1, z: 1}

¿Cuál es el problema con este Vector3?

El rendimiento. Como se ha dicho antes todos los objetos en JavaScript se comportan como un diccionario de pares nombre / valor, así que en cada una de las operaciones en las que se tenga que leer o escribir el valor de x, y o z, el runtime de JavaScript tiene que comprobar que la propiedad existe o no y luego leerla o almacenarla. Todo esto lleva tiempo. Es como si se programase todo el acceso a propiedades y campos en .NET utilizando únicamente la API de Reflexion (System.Reflection).

En el caso del motor de 3D en JavaScript Vector2, Vector3, Vector4, Color y Matrix son tipos que se están usando constantemente para dibujar la geometría de los cubos, así que esos tipos fueron los primero en ser optimizados.

La solución por la que se opto fue eliminar la definición de los tipos, es decir, que por ejemplo Vector2, Vector3, Vector4 y Color pasaron a ser un array de 2, 3, 4 y 4 posiciones respectivamente. Así que por convención lo que se hizo fue que la posición dentro del array representaba una coordenada de las dimensiones del vector.

  • X: array[0]
  • Y: array[1]
  • Z: array[2]
  • W: arrat[3]

En el caso de Matrix que se tenía M11, M12…M21,M22..M31..M44 pasaron a ser también las posiciones de un array.

Veamos como se ha cambiado la multiplicación de matrices, uno de los cuellos de botella, en cuanto a rendimiento se refiere.

Antes

   1: function Multiply(matrix1, matrix2) {

   2:     var matrix = new Matrix();

   3:     matrix.M11 = (((matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21)) + (matrix1.M13 * matrix2.M31)) + (matrix1.M14 * matrix2.M41);

   4:     matrix.M12 = (((matrix1.M11 * matrix2.M12) + (matrix1.M12 * matrix2.M22)) + (matrix1.M13 * matrix2.M32)) + (matrix1.M14 * matrix2.M42);

   5:     matrix.M13 = (((matrix1.M11 * matrix2.M13) + (matrix1.M12 * matrix2.M23)) + (matrix1.M13 * matrix2.M33)) + (matrix1.M14 * matrix2.M43);

   6:     matrix.M14 = (((matrix1.M11 * matrix2.M14) + (matrix1.M12 * matrix2.M24)) + (matrix1.M13 * matrix2.M34)) + (matrix1.M14 * matrix2.M44);

   7:     matrix.M21 = (((matrix1.M21 * matrix2.M11) + (matrix1.M22 * matrix2.M21)) + (matrix1.M23 * matrix2.M31)) + (matrix1.M24 * matrix2.M41);

   8:     matrix.M22 = (((matrix1.M21 * matrix2.M12) + (matrix1.M22 * matrix2.M22)) + (matrix1.M23 * matrix2.M32)) + (matrix1.M24 * matrix2.M42);

   9:     matrix.M23 = (((matrix1.M21 * matrix2.M13) + (matrix1.M22 * matrix2.M23)) + (matrix1.M23 * matrix2.M33)) + (matrix1.M24 * matrix2.M43);

  10:     matrix.M24 = (((matrix1.M21 * matrix2.M14) + (matrix1.M22 * matrix2.M24)) + (matrix1.M23 * matrix2.M34)) + (matrix1.M24 * matrix2.M44);

  11:     matrix.M31 = (((matrix1.M31 * matrix2.M11) + (matrix1.M32 * matrix2.M21)) + (matrix1.M33 * matrix2.M31)) + (matrix1.M34 * matrix2.M41);

  12:     matrix.M32 = (((matrix1.M31 * matrix2.M12) + (matrix1.M32 * matrix2.M22)) + (matrix1.M33 * matrix2.M32)) + (matrix1.M34 * matrix2.M42);

  13:     matrix.M33 = (((matrix1.M31 * matrix2.M13) + (matrix1.M32 * matrix2.M23)) + (matrix1.M33 * matrix2.M33)) + (matrix1.M34 * matrix2.M43);

  14:     matrix.M34 = (((matrix1.M31 * matrix2.M14) + (matrix1.M32 * matrix2.M24)) + (matrix1.M33 * matrix2.M34)) + (matrix1.M34 * matrix2.M44);

  15:     matrix.M41 = (((matrix1.M41 * matrix2.M11) + (matrix1.M42 * matrix2.M21)) + (matrix1.M43 * matrix2.M31)) + (matrix1.M44 * matrix2.M41);

  16:     matrix.M42 = (((matrix1.M41 * matrix2.M12) + (matrix1.M42 * matrix2.M22)) + (matrix1.M43 * matrix2.M32)) + (matrix1.M44 * matrix2.M42);

  17:     matrix.M43 = (((matrix1.M41 * matrix2.M13) + (matrix1.M42 * matrix2.M23)) + (matrix1.M43 * matrix2.M33)) + (matrix1.M44 * matrix2.M43);

  18:     matrix.M44 = (((matrix1.M41 * matrix2.M14) + (matrix1.M42 * matrix2.M24)) + (matrix1.M43 * matrix2.M34)) + (matrix1.M44 * matrix2.M44);

  19:     return matrix;

  20: }

La multiplicación simplemente accedía a cada uno de los índices de la matriz, los multiplicaba y luego los asignada de vuelta a la matriz de resultado. Como hemos dicho antes, esto implica leer una gran cantidad de propiedades durante el dibujado de un frame de la escena.

Ahora

   1: function Multiply(matrix1, matrix2) {

   2:     var matrix = new Matrix();

   3:     var position = matrix.position;

   4:     var position1 = matrix1.position;

   5:     var position2 = matrix2.position;

   6:     position[0] = (((position1[0] * position2[0]) + (position1[1] * position2[4])) + (position1[2] * position2[8])) + (position1[3] * position2[12]);

   7:     position[1] = (((position1[0] * position2[1]) + (position1[1] * position2[5])) + (position1[2] * position2[9])) + (position1[3] * position2[13]);

   8:     position[2] = (((position1[0] * position2[2]) + (position1[1] * position2[6])) + (position1[2] * position2[10])) + (position1[3] * position2[14]);

   9:     position[3] = (((position1[0] * position2[3]) + (position1[1] * position2[7])) + (position1[2] * position2[11])) + (position1[3] * position2[15]);

  10:     position[4] = (((position1[4] * position2[0]) + (position1[5] * position2[4])) + (position1[6] * position2[8])) + (position1[7] * position2[12]);

  11:     position[5] = (((position1[4] * position2[1]) + (position1[5] * position2[5])) + (position1[6] * position2[9])) + (position1[7] * position2[13]);

  12:     position[6] = (((position1[4] * position2[2]) + (position1[5] * position2[6])) + (position1[6] * position2[10])) + (position1[7] * position2[14]);

  13:     position[7] = (((position1[4] * position2[3]) + (position1[5] * position2[7])) + (position1[6] * position2[11])) + (position1[7] * position2[15]);

  14:     position[8] = (((position1[8] * position2[0]) + (position1[9] * position2[4])) + (position1[10] * position2[8])) + (position1[11] * position2[12]);

  15:     position[9] = (((position1[8] * position2[1]) + (position1[9] * position2[5])) + (position1[10] * position2[9])) + (position1[11] * position2[13]);

  16:     position[10] = (((position1[8] * position2[2]) + (position1[9] * position2[6])) + (position1[10] * position2[10])) + (position1[11] * position2[14]);

  17:     position[11] = (((position1[8] * position2[3]) + (position1[9] * position2[7])) + (position1[10] * position2[11])) + (position1[11] * position2[15]);

  18:     position[12] = (((position1[12] * position2[0]) + (position1[13] * position2[4])) + (position1[14] * position2[8])) + (position1[15] * position2[12]);

  19:     position[13] = (((position1[12] * position2[1]) + (position1[13] * position2[5])) + (position1[14] * position2[9])) + (position1[15] * position2[13]);

  20:     position[14] = (((position1[12] * position2[2]) + (position1[13] * position2[6])) + (position1[14] * position2[10])) + (position1[15] * position2[14]);

  21:     position[15] = (((position1[12] * position2[3]) + (position1[13] * position2[7])) + (position1[14] * position2[11])) + (position1[15] * position2[15]);

  22:     return matrix;

  23: }

Lo primero que se observa es que el código pasa a ser más críptico que el anterior, es decir, que ahora únicamente se tiene son los diferentes índices de position dentro de tres arrays, que representan las tres matrices con las que se esta trabajando en este momento.

Así que se ha pasado de,

matrix.M11 = (((matrix1.M11 * matrix2.M11) + (matrix1.M12 * matrix2.M21)) + 

(matrix1.M13 * matrix2.M31)) + (matrix1.M14 * matrix2.M41);

a esto:

position[0] = (((position1[0] * position2[0]) + (position1[1] * position2[4])) + 

(position1[2] * position2[8])) + (position1[3] * position2[12]);

Ya que ahora todas las posiciones de la matriz están almacenadas en un array de 16 posiciones lo que se tiene que hacer si se quiere acceder al valor M11 es acceder a la posición 0 de array, en el caso del valor M31 a la posición 8 de array y así sucesivamente.

Otras optimizaciones

Tamaño de los arrays

Si se tiene un array que tiene una propiedad length por la que se quiere iterar para realizar una acción por cada uno de los elementos del array, es recomendable no poner directamente el valor de myArray.length para comprobar si se ha llegado al final de array, sino guardar el tamaño del array en una variable y usar esta variable.

   1: var myArray = new Array();

   2:  

   3: for (var i = 0; i < myArray.length; i++) {

   4:     myArray[i] = i;

   5: }

   6:  

   7: var length = myArray.length;

   8: for (var i = 0; i < length; i++) {

   9:     myArray[i] = i;

  10: }

Cachear variables

Si durante la ejecución de un método se tiene variables que vamos a usar y estas variables son propiedades de un objeto, es mejor definirlas como variables en el ámbito del método que no referenciarlas desde el objeto original.

   1: var myObject =

   2:     {

   3:     name: 'luis',

   4:     company: {

   5:         name: 'PlainConcepts',

   6:         location: 'address'

   7:         }

   8:     };

   9:  

  10: var companyAddress = myObject.company.location;

  11: var company = myObject.company;

  12: companyAddress = company.location;

Espero que estas notas sobre optimización de JavaScript os sean útiles.

Luis Guerrero.

[Windows 8] Navegando en una aplicación HTML/JavaScript

Con la aparición de Windows 8 Consumer Preview Microsoft ha presentado Visual Studio 2012 para desarrollar aplicaciones Metro. En este artículo hablaremos de cómo se produce la navegación de contenido en una aplicación de Metro hecha en HTML / JavaScript.

Navegando en HTML

Cuando creamos una aplicación en HTML tradicional, el método de navegación es incluir un enlace <a> para poder empezar a navegar. Lo malo que tiene este método es que durante un breve periodo de tiempo la ventana del navegador permanecerá en blanco a la espera del contenido del nuevo HTML al que se está navegando. Esto puede resultar muy molesto en algunas ocasiones y aunque se tenga una conexión a Internet muy rápida puede parecer que la página parpadea por un instante.

Para este tipo de problemas se invento AJAX, que permite modificar selectivamente una sección de una página sin que se refresque la página entera. Esta funcionalidad es ideal para bajar datos de internet y luego “conectar” esos datos con el árbol de objetos en HTML.

Aplicaciones Metro con HTML

Sabiendo estas dos cosas tenemos que hacer que nuestras aplicaciones Metro no naveguen hasta un HTML nuevo, sino que tengamos un mecanismo para poder cargar y descargar contenido del DOM para simular la navegación. Gracias a las nuevas API’s que Microsoft ha incluido en WinJS (la parte JavaScript de WinRT) podemos hacer esto de manera muy sencilla.

Empezaremos echando un vistazo a la plantilla de nuevo proyecto de aplicación Metro para HMTL / JavaScript.

Como podemos ver en la captura del menú de nuevo proyecto de Visual Studio 11, tenemos varias plantillas de diferentes tipos de proyecto. Ahora mismo nos centraremos en el tipo de proyecto “Navigation Application”. Llamaremos a nuestra aplicación NavigationDemo.

Como se puede ver en esta captura de pantalla, estos son los ficheros que se crean por defecto en esta plantilla. Ahora vamos a proceder a ver como se produce la navegación.

Primera aplicación Metro

La aplicación que vamos a desarrollar es muy sencilla. Simplemente navega de una pieza de contenido a otra, pero utilizando las APIs que tenemos en WinRT para hacer la navegación.

La primera página Default.html

La primera página que se ejecuta en una aplicación Metro es default.html, así que ese es nuestro punto de entrada, para poder empezar a entender la navegación en Metro.

Si nos fijamos en el código fuente de default.html, tenemos lo siguiente:

   1: <!DOCTYPE html>

   2: <html>

   3: <head>

   4:     <meta charset="utf-8">

   5:     <title>NavigationDemo</title>

   6:  

   7:     <!-- WinJS references -->

   8:     <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">

   9:     <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
   1:  

   2:     <script src="//Microsoft.WinJS.0.6/js/ui.js">

   1: </script>

   2:  

   3:     <!-- NavigationDemo references -->

   4:     <link href="/css/default.css" rel="stylesheet">

   5:     <script src="/js/default.js">

   1: </script>

   2:     <script src="/js/navigator.js">

</script>

  10: </head>

  11: <body>

  12:     <div id="contenthost" 

  13:          data-win-control="NavigationDemo.PageControlNavigator" 

  14:          data-win-options="{home: '/html/homePage.html'}"></div>

  15:     <!-- <div id="appbar" data-win-control="WinJS.UI.AppBar">

  16:         <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmd', label:'Command', icon:'placeholder'}"></button>

  17:     </div> -->

  18: </body>

  19: </html>

Vemos que es HTML normal, de toda la vida, solamente que en la cabecera del documento tenemos unas referencias de unos ficheros JavaScript y hojas de estilos un poco especiales. Me refiero a esto:

   1: <script src="//Microsoft.WinJS.0.6/js/base.js"></script>

Si nos fijamos, con esta acción estamos referenciando la API de WinJS, que es la librería de JavaScript para el desarrollo de aplicaciones Metro.

Un poco más abajo tenemos dos ficheros JS y un fichero CSS referenciados. Estos ficheros forman parte de la lógica de la aplicación.

La cosa cambia cuando nos vamos al cuerpo de nuestra página html, donde vemos que tenemos únicamente un div, pero con unos atributos que no habíamos visto antes:

   1: <div id="contenthost"

   2:     data-win-control="NavigationDemo.PageControlNavigator"

   3:     data-win-options="{home: '/html/homePage.html'}"></div>

Tenemos por un lado el identificador del control contenthost que según el nombre podemos adivinar que será el hueco donde después pondremos el contenido por el que estemos navegando.

También vemos que el siguiente atributo, data-win-control, tiene como valor algo que parece una clase. Si recordáis el nombre del proyecto (NavigationDemo) vemos el valor del atributo es NavigationDemo.PageControlNavigator, lo que me indica que la clase se llama PageControlNavigator. La primera reacción a este atributo es pensar que, al estar en JS no tenemos clases, pero podemos saber por XAML que esto se parece mucho a nuestra MainPage.

El último atributo que nos queda por investigar es, data-win-options. Si nos fijamos en el contenido del mismo, vemos que es un objeto de JS que tiene una propiedad llamada home con un valor que es una ruta html relativa del proyecto. Todo parece indicar que es la primera página que se va a cargar, como efectivamente podemos comprobar si abrimos el fichero homePage.html, que mostrará justamente la primera captura de pantalla que hemos visto antes.

Pero, ¿Cómo se ha realizado esta navegación? Es el momento de entrar a hablar sobre JavaScript.

JavaScript

El primer fichero que vamos a abrir es el js/default.js, que es el punto de entrada de la aplicación.

Un detalle a destacar sobre los ficheros de JavaScript es que todos están envueltos en una función sin nombre, que se ejecuta tan pronto como se define.

   1: (function () {

   2: })();

El motivo de proceder de esta forma es aislar el ámbito global de JavaScript y así evitar problemas de colisión de nombres de objetos y funciones. No definir todas las variables en el espacio de nombre global es una buena práctica.

Lo siguiente que nos sorprende es que tenemos una sentencia que no habíamos visto antes (“use strict”), que indica al compilador y al runtime que hagan una verificación estricta de tipos, como si se tratase de un lenguaje fuertemente tipado. Esto, por supuesto, se realiza cuando se ejecuta la aplicación, pues no hay proceso de compilación como tal.

Después de esto el fichero default.js simplemente establece dos callbacks para el evento “onactivated” y “oncheckpoint” y luego llama a WinJS.Application.start();

Navegación

La verdadera magia del código no reside en este fichero default.js, sino en el fichero navigator.js. Veamos cómo está definido.

Lo primero que nos encontramos en la definición del fichero es la función sin nombre que envuelve todo el código. A partir de ahora la obviaremos para no ser redundantes.

En el cuerpo de la definición de la función vemos como se definen una serie de variables que son accesos directos a propiedades del código, como por ejemplo:

  • Windows.UI.ViewManagement.ApplicationView
  • Windows.Graphics.Display.DisplayProperties
  • WinJS.Navigation
  • WinJS.UI
  • WinJS.Utilities

Todo lo que empiece por WinJS, está definido en el fichero base.js, que forma parte de la referencia de “Microsoft Windows Library for JavaScript SDK” que acompaña al proyecto. El fichero base.js está perfectamente formateado y documentado, así que podremos consultar como está hecho pero no podremos modificarlo desde esta localización.

Las variables que empiezan por Windows hacen referencia a la API de WinRT, a la que podemos acceder desde JavaScript. De este modo, cuando nos referimos a Windows.Graphics.Display.DisplayProperties estamos hablando de una clase que podremos utilizar con C# y C++ (los otros dos lenguajes permitidos en WinRT).

A continuación, nos encontramos con dos de las funcionalidades que mejorarán la calidad del código que generemos en JavaScript: espacios de nombres y Classes. Veamos de qué se trata antes de continuar.

WinJS.Namespace

Este objeto, como su nombre indica, nos permite definir espacios de nombres para su uso dentro de una aplicación Metro. ¿A qué nos referimos con espacios de nombre?¿A un espacio de nombres tradicional? Los programadores de C# podemos pensar que estamos definiendo un espacio de nombre de la misma manera que lo definimos en este lenguaje, pero desde luego no estamos en un entorno de .NET. Lo que realmente estamos haciendo es definir una serie de objetos que después vamos a poder utilizar con una nomenclatura de espacio de nombres. Es decir, que vamos a poder hacer definiciones de objetos de forma parecida a como hace Microsoft con, por ejemplo, WinJS.Navigation, que también forma parte de espacio de nombres de WinJS.

A la hora de definir un espacio de nombres en JavaScript, utilizando WinJS emplearemos una de las dos funciones que vienen en WinJS: define o defineWithParent.

Define

Si queremos definir un segmento del espacio de nombres de nuestra aplicación, en nuestro caso NavigationDemo.PageControlNavigator, tendremos que declarar la base del espacio de nombres como todos los segmentos menos el último, en este caso, NavigationDemo, pero podríamos tener espacios de nombres más grandes.

Una vez definido el string del espacio de nombres base como primer parámetro, el segundo parámetro de la función ha de ser un objeto que contenga el último segmento del espacio de nombres correspondiente a la clase en cuestión.

El ejemplo quedaría así:

   1: WinJS.Namespace.define("DemoNamespace", {

   2:     Class1: {},

   3:     Class2: {}

   4: });

De esta manera podemos tener organizado nuestro código dentro de nuestra aplicación por espacio de nombres. Realmente no son espacios de nombres en el sentido tradicional del lenguaje C# (al que más estamos acostumbrados), sino que son objetos definidos de esa manera para dar sensación de jerarquía cuando se utilizan.

Otra función que podemos utilizar es defineWithParent que nos permite extender un espacio de nombres ya existente.

Una vez comprendido esto, el siguiente paso es definir clases para empezar a escribir la funcionalidad de nuestro código.

WinJS.Class

La definición de clases es otro aspecto importante de la programación en JavaScript. Como bien es sabido, no hay clases como tales en JavaScript, pero en WinJS podemos hacer que una función tenga el aspecto de una clase.

Según Microsoft las clases en WinJS tienen tres características:

  • Constructor: es una función que nos permite inicializar la clase en cuestión, nosotros no somos responsables de devolver this en la definición porque WinJS lo hace automáticamente por nosotros.
  • Métodos de instancia: es un objeto que contiene los métodos de instancia que vamos a utilizar en la definición de la clase. No hay descriptores de visibilidad en JavaScript así que todos lo métodos son públicos.
  • Métodos estáticos: son métodos que se pueden utilizar sin necesidad de crear una instancia de la clase directamente escribiendo el nombre de la clase.

Así es como quedaría la definición de una clase con WinJS.Class.

   1: WinJS.Class.define(

   2:     function (argum1) { },

   3:     {},

   4:     {}

   5: );

Herencia y mixing

A la hora de definir clases también es posible definir herencia de clases. Como hasta ahora esto no es herencia tradicional como la entendemos en C#, sino que simplemente se define como una mezcla de los métodos que han definido en las clase base más los métodos de la clase hija. Para ello utilizaremos el método WinJS.Class.derive.

La última de las opciones es una mezcla (mix), que consiste en coger dos objetos que no tienen ninguna relación y hacer una unión de los dos en una nueva definición de clase.

Navigator.js

Ahora ha llegado el momento de hablar sobre la clase más importante de todo el proyecto, navigator.js. Como vimos anteriormente, esta clase se utilizaba en el fichero default.html para hacer la navegación de esa página hasta homePage.html. Veamos ahora cómo se realiza esa navegación.

Definición de los objetos más usados

Al principio del fichero podemos ver que se definen una serie de propiedades con objetos que vamos a utilizar durante el desarrollo.

   1: var appView = Windows.UI.ViewManagement.ApplicationView;

   2: var displayProps = Windows.Graphics.Display.DisplayProperties;

   3: var nav = WinJS.Navigation;

   4: var ui = WinJS.UI;

   5: var utils = WinJS.Utilities;

Una vez definidos estos objetos lo siguiente que nos encontramos es directamente la definición de clase.

   1: (function () {

   2:     "use strict";

   3:  

   4:     var appView = Windows.UI.ViewManagement.ApplicationView;

   5:     var displayProps = Windows.Graphics.Display.DisplayProperties;

   6:     var nav = WinJS.Navigation;

   7:     var ui = WinJS.UI;

   8:     var utils = WinJS.Utilities;

   9:     

  10:     WinJS.Namespace.define("NavigationDemo", {

  11:         PageControlNavigator: WinJS.Class.define(

  12:         // Define the constructor function for the PageControlNavigator.

  13:             function (element, options) {

  14:                 this.element = element || document.createElement("div");

  15:                 this.element.appendChild(this._createPageElement());

  16:  

  17:                 this.home = options.home;

  18:  

  19:                 nav.onnavigated = this._navigated.bind(this);

  20:                 appView.getForCurrentView().onviewstatechanged = this._viewstatechanged.bind(this);

  21:  

  22:                 document.body.onkeyup = this._keyupHandler.bind(this);

  23:                 document.body.onkeypress = this._keypressHandler.bind(this);

  24:                 nav.navigate(this.home);

  25:             }, {

  26:                 // This function creates a new container for each page.

  27:                 _createPageElement: function () {

  28:                     var element = document.createElement("div");

  29:                     element.style.width = "100%";

  30:                     element.style.height = "100%";

  31:                     return element;

  32:                 },

  33:  

  34:                 // This function responds to keypresses to only navigate when

  35:                 // the backspace key is not used elsewhere.

  36:                 _keypressHandler: function (eventObject) {

  37:                     if (eventObject.key === "Backspace")

  38:                         nav.back();

  39:                 },

  40:  

  41:                 // This function responds to keyup to enable keyboard navigation.

  42:                 _keyupHandler: function (eventObject) {

  43:                     if ((eventObject.key === "Left" && eventObject.altKey) || (eventObject.key === "BrowserBack")) {

  44:                         nav.back();

  45:                     } else if ((eventObject.key === "Right" && eventObject.altKey) || (eventObject.key === "BrowserForward")) {

  46:                         nav.forward();

  47:                     }

  48:                 },

  49:  

  50:                 // This function responds to navigation by adding new pages

  51:                 // to the DOM.

  52:                 _navigated: function (eventObject) {

  53:                     var newElement = this._createPageElement();

  54:                     var parentedComplete;

  55:                     var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

  56:  

  57:                     var that = this;

  58:                     WinJS.UI.Pages.render(eventObject.detail.location, newElement, eventObject.detail.state, parented).

  59:                         then(function (control) {

  60:                             that.element.appendChild(newElement);

  61:                             that.element.removeChild(that.pageElement);

  62:                             parentedComplete();

  63:                             document.body.focus();

  64:                             that.navigated();

  65:                         });

  66:                 },

  67:  

  68:                 // This function is called by _viewstatechanged in order to

  69:                 // pass events to the page.

  70:                 _updateLayout: {

  71:                     get: function () { return (this.pageControl && this.pageControl.updateLayout) || function () { }; }

  72:                 },

  73:  

  74:                 _viewstatechanged: function (eventObject) {

  75:                     (this._updateLayout.bind(this.pageControl))(this.pageElement, eventObject.viewState);

  76:                 },

  77:  

  78:                 // This function updates application controls once a navigation

  79:                 // has completed.

  80:                 navigated: function () {

  81:                     // Do application specific on-navigated work here

  82:                     var backButton = this.pageElement.querySelector("header[role=banner] .win-backbutton");

  83:                     if (backButton) {

  84:                         backButton.onclick = function () { nav.back(); };

  85:  

  86:                         if (nav.canGoBack) {

  87:                             backButton.removeAttribute("disabled");

  88:                         }

  89:                         else {

  90:                             backButton.setAttribute("disabled", "disabled");

  91:                         }

  92:                     }

  93:                 },

  94:  

  95:                 // This is the PageControlNavigator object.

  96:                 pageControl: {

  97:                     get: function () { return this.pageElement && this.pageElement.winControl; }

  98:                 },

  99:  

 100:                 // This is the root element of the current page.

 101:                 pageElement: {

 102:                     get: function () { return this.element.firstElementChild; }

 103:                 }

 104:             }

 105:         ),

 106:  

 107:         // This function navigates to the home page which is defined when the

 108:         // control is created.

 109:         navigateHome: function () {

 110:             var home = document.querySelector("#contenthost").winControl.home;

 111:             var loc = nav.location;

 112:             if (loc !== "" && loc !== home) {

 113:                 nav.navigate(home);

 114:             }

 115:         },

 116:     });

 117: })();

Constructor

En el constructor de la clase se realizan varias tareas para definir la navegación.

   1: function (element, options) {

   2:     this.element = element || document.createElement("div");

   3:     this.element.appendChild(this._createPageElement());

   4:  

   5:     this.home = options.home;

   6:  

   7:     nav.onnavigated = this._navigated.bind(this);

   8:     appView.getForCurrentView().onviewstatechanged = this._viewstatechanged.bind(this);

   9:  

  10:     document.body.onkeyup = this._keyupHandler.bind(this);

  11:     document.body.onkeypress = this._keypressHandler.bind(this);

  12:     nav.navigate(this.home);

  13: }

Vemos que la función tiene dos parámetros, element y options, que son justamente el elemento host de la navegación, en este caso un div, y un objeto con las opciones, respectivamente. Si recordamos la definición del html, había un atributo que se llamaba data-win-options, cuyo valor es “{home: ‘/html/homePage.html’}” que justamente es el objeto en el que, por convención, se especifica la página de inicio.

Una vez que se tiene la referencia del elemento host, en caso de que element sea undefined, se crea un div nuevo. A continuación guardaremos el valor de options.home en una propiedad llamada home en nuestra clase. El siguiente paso es suscribirse al evento onnavigated del objeto nav que, si recordamos de la definición de variables del principio, es WinJS.Navigation.

No obstante, si nos fijamos en esa línea de código, “nav.onnavigated = this._navigated.bind(this);” veremos que no se asigna directamente el valor de “this._navigated” a “nav.onnavigated”, sino que se obtiene la referencia de la función this._navigated y se llama al método bind pasándole como parámetro this. Esto se hace así porque tenemos que recordar que en JavaScript el contexto de this no se guarda en la llamada así que cuando se ejecute el método _navigated como resultado de la navegación, this en ese momento no será el mismo this que estamos usando en el constructor del objeto. Por tanto, con esta línea de código lo que pretendemos es guardar el contexto de this y luego utilizarlo cuando se llame a la función _navigated. Este comportamiento (bind) está definido en WinJS.

Un poco más abajo en la definición del constructor podemos ver como se llama al método nav.navigate(this.home), que ejecuta la navegación en sí.

Método de instancia

Ahora viene la parte donde se hace el trabajo de la navegación en sí: obtener el html de la página de destino y adjuntarlo al DOM de la página principal. Todo ello se hace en el método _navigated.

   1: _navigated: function (eventObject) {

   2:     var newElement = this._createPageElement();

   3:     var parentedComplete;

   4:     var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

   5:  

   6:     var that = this;

   7:     WinJS.UI.Pages.render(eventObject.detail.location, newElement, eventObject.detail.state, parented).

   8:         then(function (control) {

   9:             that.element.appendChild(newElement);

  10:             that.element.removeChild(that.pageElement);

  11:             parentedComplete();

  12:             document.body.focus();

  13:             that.navigated();

  14:         });

  15: }

Aquí se llama al método WinJS.UI.Pages.render que se encarga de capturar el html definido en la página de destino y cargar los ficheros que se han definido en la cabecera, JavaScript y CSS, quedándose únicamente con el contenido de la etiqueta <body>, normalmente un div. Una vez que se ha completado el proceso y usando otra funcionalidad principal de WinJS, las promesas (promises), se ejecutará el método que hay definido después del then. A grosso modo. Las promesas se utilizan en WinJS para enlazar métodos asíncronos para su ejecución, aunque su definición es mucho más extensa y queda fuera del ámbito de este artículo.

Cuando el código html ya está limpio y listo para ser procesado se llama a ese método definido en el then. Sin embargo, antes de esto debemos observar que en la línea anterior se hace algo un poco raro a primera vista: var that = this, que es definir una variable que se llama that asignándole el contenido de this, lo que está relacionado con lo que hemos comentado antes de que en JavaScript los contextos no se guardan entre llamadas. De esta forma, como en la función anónima definida en el then vamos a usar esta referencia tenemos que guardarla antes.

Ya tenemos todo lo que necesitamos para poder añadir al DOM el nuevo control y quitar el anterior, hacer foco en el body recién creado y llamar al método that.navigated() que comprueba si se tiene que mostrar el botón de atrás en la interfaz de usuario de la aplicación.

Navegación

Como hemos visto, la navegación de la aplicación se define íntegramente en este método _navigated, es que el responsable de añadir el control (que WinJS formatea por nosotros) y añadirlo al DOM, quitando previamente el anterior que pudiera existir. WinJS es el framework en el que definimos las clasesy los espacios de nombres, así como el encargado de cargar las páginas html, los scripts de JavaScript y las hojas de estilos.

¿Cómo se genera una página en WinJS?

Otro aspecto importante del desarrollo de aplicaciones de Windows 8 son las propias páginas en sí. Hasta ahora hemos visto solamente cómo se realiza la navegación en una aplicación JavaScript de Windows 8, pero no hemos visto como se hacen las páginas a las cuales se navega. Para eso Visual Studio tiene una plantilla en el menú de nuevo elemento:

Ese elemento es Page Control, que nos genera una página HTML con las referencias de WinJS y un fichero JavaScript y CSS.

HTML

El HTML que genera la plantilla no tiene nada especial, teniendo en cuenta que hemos visto ya como referenciar WinJS en HTML.

   1: <!DOCTYPE html>

   2: <html>

   3: <head>

   4:     <meta charset="utf-8">

   5:     <title>pagecontrol</title>

   6:  

   7:     <!-- WinJS references -->

   8:     <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">

   9:     <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
   1:  

   2:     <script src="//Microsoft.WinJS.0.6/js/ui.js">

   1: </script>

   2:     

   3:     <link href="pagecontrol.css" rel="stylesheet">

   4:     <script src="pagecontrol.js">

</script>

  10: </head>

  11: <body>

  12:     <div class="pagecontrol fragment">

  13:         <header aria-label="Header content" role="banner">

  14:             <button class="win-backbutton" aria-label="Back" disabled></button>

  15:             <h1 class="titlearea win-type-ellipsis">

  16:                 <span class="pagetitle">Welcome to Second Page!</span>

  17:             </h1>

  18:         </header>

  19:         <section aria-label="Main content" role="main">

  20:             <p>Content goes here.</p>

  21:         </section>

  22:     </div>

  23: </body>

  24: </html>

El contenido del Body es un div con la clase pagecontrol que identifica que ese es el contenido de la página a navegar.

JavaScript

   1: (function () {

   2:     "use strict";

   3:  

   4:     // This function is called whenever a user navigates to this page. It

   5:     // populates the page elements with the app's data.

   6:     function ready(element, options) {

   7:         // TODO: Initialize the fragment here.

   8:         var div = element;

   9:     }

  10:  

  11:     function updateLayout(element, viewState) {

  12:         // TODO: Respond to changes in viewState.

  13:     }

  14:  

  15:     WinJS.UI.Pages.define("/SecondPage/pagecontrol.html", {

  16:         ready: ready,

  17:         updateLayout: updateLayout

  18:     });

  19: })();

El fichero JavaScript sigue el estándar de envolver todo el código en una función anónima para así aislar el contexto global, definiendo a continuación la página internamente. Para ello se llama a la función WinJS.UI.Pages.define, a la que pasamos como primer parámetro un string con la ruta de la página actual y luego un objeto con los eventos a los que nos queremos suscribir, en el caso que nos ocupa ready y updateLayout. La función ready tiene los mismos parámetros que tenía la clase que definimos en navigator.js, siendo element el elemento div del contenido que queremos mostrar y options las opciones de la navegación, que es el segundo parámetro de WinJS.Navigator.navigate.

Conclusiones

Los desarrolladores de Silverlight estamos acostumbrados a una navegación basada en Páginas XAML, que el NavigationService se encarga de cargar y descargar conforme vamos interactuando con nuestra aplicación. En HTML tenemos el mismo concepto de navegación de página, pero vimos al principio que el inconveniente que tiene este modo de trabajar es que, en algún momento, la página se dejará de renderizar, haciendo que nuestra aplicación no tenga el aspecto de una aplicación tradicional de escritorio.

Para solucionar este problema se ha optado por una navegación basada en añadir y quitar contenido al DOM de la aplicación. El único problema que tiene esta metodología, es que, por ejemplo, los ficheros CSS que se carguen en memoria se podrán descargar, con lo que podemos encontrarnos con comportamientos no deseados. Por ejemplo, si definimos una clase de CSS con el mismo nombre en dos ficheros, al cargar el primero todo se verá correctamente. Si luego navegamos a una página, y en esa otra página cargamos otro CSS que sobreescribe la clase de la que estamos hablando, todo se seguirá mostrando correctamente. Pero si ahora volvemos hacia atrás el elemento que originalmente utilizaba esta misma clase de CSS, ahora se ve de manera diferente por este segundo fichero CSS. Por tanto, es muy importante hacer nombres de clases únicos para CSS, y si se repiten tener claro el porqué.

El desarrollo de HTML tiene un hándicap muy grande que es la falta de controles como en XAML. Esto hace que casi todo el contenido se tenga que repetir y no se pueda encapsular aspecto y funcionalidad de manera cómoda para el desarrollador. De todos modos Microsoft ha hecho un esfuerzo muy grande con WinJS para que el desarrollo de nuestras aplicaciones Metro con HTML sea lo más cómodo del mundo.

Él código de ejemplo lo puedes descargar de aquí.

Material de la charla de Computación paralela en Windows de la CodeMotion

Como viene siendo habitual aquí tenéis el material de la charla sobre computación paralela del pasado sábado día 24 de marzo.

clip_image002

El código de ejemplo lo podéis descargar de aquí: http://bit.ly/TPLCodeMotion

Y ya sabéis nada de dejar los try/catch vacíos.
¡Espero que disfrutéis de todos los cores del mundo!

Saludos. Luis.

San Valentín se Baila

Este domingo en la plaza de
Callao de Madrid habrá un evento de El Corte Inglés, Microsoft y Xbox 360 para
celebrar el día de los enamorados. En este evento Plain Concepts presentará dos
aplicaciones para Windows Phone 7 y Surface 2 para que las parejas de
enamorados puedan hacerse fotografías con el teléfono móvil (un Nokia Lumia) y
después componer una tarjeta de felicitación en un Surface 2.

Os invitamos a todos a que os paséis este domingo por
la mañana por Callao con vuestras parejas, para celebrar San Valentín y disfrutar
de estas aplicaciones.

Windows Phone 7

Las imágenes se suben a Azure y luego desde la aplicación de Surface 2 se pueden componer.

Surface 2

12 Horas de Visual Studio – Calidad de Software y patrones de diseño en Windows Phone 7.5

Hoy es el evento de 12 Horas de Visual Studio de Microsoft y Globbtv, podeis ver el evento en directo aquí http://www.globbtv.com/vstudio12horas/

Este es el material que voy a utilizar sobre mi charla sobre “Calidad de Software y patrones de diseño de Windows Phone 7.5”

clip_image002

El código de ejemplo os lo podéis descargar de aquí, http://bit.ly/12HorasVSWindowsPhone