[FORENSIC] StorageFile vs IsolatedStorage

Hola!

Durante el pasado Codemotion 2014 celebrado en Madrid, Sergio Navarro me comentó que tenía un grave problema de rendimiento en una aplicación Windows Store. Sergio usa en sus aplicaciones una estupenda base de datos NoSQL multi plataforma llamada SiaqoDb. Es una base de datos estupenda, que facilita el almacenamiento de datos en aplicaciones destinadas a múltiples plataformas.

El problema

El problema en cuestión es que SiaqoDb tenía un rendimiento muy pobre en las lecturas en las aplicaciones para Windows 8.1, usando WinRT, en comparación con las aplicaciones Windows Phone usando Silverlight. En concreto estimaron que el cambio de rendimiento era de cerca de 40 veces más lento en WinRT.

La gente de SiaqoDb incluso le enviaron un ejemplo en C#, implementando la interfaz de archivos que usan internamente, para demostrar que el problema venía del lado del sistema. Si tenéis curiosidad por reproducir el problema, aquí tenéis el código original. La mayor peculiaridad que tiene, es que no leen el archivo completo de una sola vez, lo hacen en pequeñas partes. En el ejemplo, 5000 lecturas.

La verdad es que me picó mucho la curiosidad, pero en un primer momento pensé que la diferencia de rendimiento se podría deber a un tema puramente de hardware. Los smartphones llevan memoria solida y quizás si comparas su rendimiento con un disco duro normal, la lectura se puede resentir. Pero, ¿40 veces? Es una gran diferencia.

Por otro lado, Juan Manuel Montero también estaba dandole un vistazo al código y parecio que resolvio el problema, cambiando la forma en la que se leía el archivo. Pasó de leerlo por trozos a leerlo de una sola vez. Esto resolvía el problema de velocidad, pero no era aplicable en el contexto de SiaqoDb, que necesita realizar la lectura por partes.

Así que más intrigado todavía, me puse a darle un vistazo al código. Empecé por ejecutar el código original, para descartar el hardware, ejecutándolo en un PC de escritorio con disco duro magnético y en una Surface Pro 3 con disco SSD, los resultados en WinRT fueron igual de malos: unos 6.4 segundos en leer un archivo de 5Mb. A continuación probé la ejecución en un smartphone, en concreto en mi Lumia 1520. Resultado: 0.3 segundos en leer el mismo archivo de 5Mb.

Silverlight

Estaba muy claro que algo pasaba con las lecturas en WinRT, veamos el código original del método de lectura para Windows Phone (Silverlight):

Read - Silverlight
public virtual int Read(long pos, byte[] buf)
{
    file.Seek(pos, SeekOrigin.Begin);
    return file.Read(buf, 0, buf.Length);
}

Simple y efectivo :) el objeto file es de tipo IsolatedStorageFileStream. Simplemente nos movemos a la posición deseada dentro del archivo y leemos la información dentro de un array de bytes.

WinRT

Veamos ahora el método ReadAsync de WinRT:

ReadAsync - WinRT
public virtual async Task<int> ReadAsync(long pos, byte[] buf)
{
    fileStream.Seek((ulong)pos);
    if (buf.Length > 0)
    {
        var buffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(buf);
        IBuffer rd = await fileStream.ReadAsync(buffer, (uint)buf.Length, InputStreamOptions.None);
        rd.CopyTo(buf);
    }
    return (int)buf.Length;
}

Mmm, sencillo, pero podemos empezar a ver la causa del problema, ¿Lo puedes ver? En este caso, el código es asíncrono como todas las APIs de acceso a disco de WinRT. Necesitamos crear una instancia de IBuffer, tipo que usa el método ReadAsync de la clase FileRandomAccessStream. A continuación, ser hace una copia del IBuffer resultante del método ReadAsync al array de bytes para devolver su tamaño.

Desde luego realizamos muchas más operaciones, creamos objetos, hacemos copias… ¿Cuanto tiempo nos penalizan estas acciones? Midiéndolas, de una forma muy burda, podemos ver lo siguiente:

  • El método CreateFromByteArray de la clase CryptographicBuffer, consume en total 0.6 segundos. Esto por sí solo ya es el doble que la lectura completa del archivo en Silverlight.
  • El método CopyTo de IBuffer, consume otros 0.3 segundos.

En total, solo en convertir los datos que necesitamos, hemos invertido casi 1 segundo, tres veces el tiempo necesario para toda la operación en Silverlight. Hasta 6.4 segundos, 5.4 segundos los consume el método ReadAsync de FileRandomAccessStream.

La solución

Aquí entramos en un terreno interesante. Está claro que la forma de trabajar con async/await es mucho más costosa que en un código secuencial y síncrono como tenemos en Silverlight. El método ReadAsync internamente, además de realizar la operación de lectura propiamente dicha, devuelve el progreso de la operación. Todo esto conlleva una sobrecarga en el tiempo de ejecución.

¿Y porqué si leemos el archivo de una sola vez, el rendimiento mejora?

Para verlo claro, veamos cuantos milisegundos tarda en ejecutar la lectura Silverlight. Silverlight tarda 75 milisegundos en realizar los 5000 lecturas. El problema que tenemos, en mi opinión, es que la operación de lectura individual es tan rápida, que la sobrecarga de cada lectura introducida por la infrastructura de async/await es notoria. Cuando leemos el archivo de una sola vez, el rendimiento mejora porque esa sobre carga solo se produce una vez.

Cuando el equipo de ingeniería detrás de WinRT planeo las APIs asíncronas, tuvo en mente un objetivo claro: Cualquier operación que tardase, o pudiese tardar más de 30 milisegundos, sería asíncrona. Pero este no es nuestro caso. ¿Y si nos deshacemos de la asíncronía en WinRT, que pasa? Pero, ¿Como lo hacemos si las APIs de disco de WinRT son todas asíncronas?

El objeto FileRandomAccessStream tiene un método extensor llamado AsStream, que nos devuelve la instancia de Stream que usa internamente para trabajar. y este objeto Stream contiene los métodos Read y Write síncronos que usamos tradicionalmente en Silverlight. En el constructor de nuestra clase de archivo, vamos a guardarnos la referencia a esa Stream y a continuación, podemos reescribir el método ReadAsync, de forma síncrona, así:

Read - WinRT
public virtual int Read(long pos, byte[] buf)
{
    syncfileStream.Seek(pos, SeekOrigin.Begin);
    if (buf.Length > 0)
    {
        return syncfileStream.Read(buf, 0, buf.Length);
    }
    return 0;
}

Usando la instancia de Stream que nos guardamos en el constructor, simplemente realizamos un Seek para posicionarnos en el lugar de la Stream que queremos leer y a continuación leemos el archivo. Tiempo total de lectura usando este método síncrono: 0.25 segundos. ¡Eso ya está mejor! Ya nos movemos en los mismos tiempos que con Silverlight, básicamente porque estamos usando el mismo código que en Windows Phone 8.0, por lo que el resultado no puede  ser muy diferente.

Conclusión

Y hasta aquí este pequeño análisis, después de hablar con Sergio y con la gente de SiaqoDb, han probado la solución y la van a implementar en su código base, han realizado la certificación de una aplicación usando esta solución y ha pasado a la store sin problemas.

Además de darme la oportunidad de escribir este pequeño artículo, esta situación enseña lo importante de la comunidad. Entre todos, hemos llegado a resolver un problema latente, que podría impedir que algunas aplicaciones tuviesen el rendimiento deseado. Así que es una victoria de todos los que creemos en compartir un poco de nuestro tiempo y conocimientos para ayudar a los demas.

Un saludo y HappyCoding!

 

[UNIVERSAL] Win2D, Gráficos acelerados por hardware (2 de 2)

Hola a todos!

Hace unas semanas publiqué la primera entrada sobre Win2D, usando la versión 0.0.5 de Win2D. Hoy está disponible ya una nueva versión, la 0.0.7 con muchas mejoras, bugs arreglados y nuevas características. Si quieres ver una lista detallada de los cambios, la tienes en el blog del equipo. Si no has visitado mi primer artículo, ve a por él para saber como descargar y compilar Win2D.

En este artículo quiero centrarme en los efectos, que no pudimos ver en la entrada anterior. Para darle algo de continuidad, voy a usar el mismo ejemplo de la nave espacial de la primera parte y mejorarla con algunos efectos.

Win2D incluye muchos efectos que podemos aplicar a cualquier cosa dibujada por Win2D. Esto, aunque pueda parecer trivial, es muy importante: Normalmente las librerías de efectos nos permiten aplicar estos a una imagen. Con Win2D podremos aplicarlos a geometría, texto, imágenes e incluso a otros efectos, pudiendo producir resultados increibles encadenando varios efectos. Además, el número de efectos disponible está bastante bién:

  • ArithmeticComposite
  • Atlas
  • Blend
  • Border
  • Brightness
  • ColorMatrix
  • ColorSource
  • Composite
  • Crop
  • DirectionalBlur
  • DisplacementMap
  • DistantDiffuse
  • DistantSpecular
  • DpiCompensation
  • GammaTransfer
  • GaussianBlur
  • HueRotation
  • LinearTransfer
  • LuminanceToAlpha
  • Morphology
  • OpacityMetadata
  • PointDiffuse
  • PointSpecular
  • Premultiply
  • Saturation
  • Scale
  • Shadow
  • SpotDiffuse
  • SpotSpecular
  • Tile
  • Transform2D
  • Transform3D
  • Turbulence
  • UnPremultiply

Como podemos ver, una buena cantidad de efectos. Además la posibilidad de aplicarlos de forma encadenada hace que el número de combinaciones y resultados distintos sea enorme. Pero lo mejor, sin duda, es lo sencillo que resulta usarlos.

Aprovechándonos de todos estos efectos, la apariencia final que le daremos a nuestro ejemplo será algo parecido a esto:

image

Hemos añadido una barra inferior con llamas de colores extraños, para simular una explosión que nos persigue y al mismo tiempo hemos aplicado varios efectos a los planetas de forma que ahora aparecen difuminados y con ciertas partes más iluminadas que otras.

Empecemos por el principio. ¿Como podemos aplicar un efecto, por ejemplo de difuminado, a un círculo o un rectángulo? En primer lugar necesitamos crear una supercifie de dibujo compatible con los efectos, por lo que crearemos una instancia de CanvasRenderTarget en la que dibujaremos la forma que necesitemos:

var drawSurface = new CanvasRenderTarget(baseCanvas, 600f, 800f);
using (var ds = drawSurface.CreateDrawingSession())
{
    ds.Clear(Color.FromArgb(0, 0, 0, 0));
    var circleColor = new CanvasRadialGradientBrush(baseCanvas, Colors.White, Colors.DarkBlue);
    circleColor.Center = new Vector2() { X = 300f, Y = 400f };
    circleColor.RadiusX = 200f;
    circleColor.RadiusY = 200f;
    ds.FillCircle(new Vector2() { X = 300f, Y = 400f }, 200f, circleColor);
}

De la misma forma que vimos ya en el primer artículo, creamos un degradado y pintamos nuestro círculo con él. Pero este código no se está dibujando en pantalla, estamos creando una imagen en la superficie de dibujo de tipo CanvasRenderTarget, que debemos dibujar en la sesión del CanvasControl:

using (var drawSession = args.DrawingSession)
{
    drawSession.Clear(Colors.LightGray);

    var drawSurface = new CanvasRenderTarget(baseCanvas, 600f, 800f);
    using (var ds = drawSurface.CreateDrawingSession())
    {
        ds.Clear(Color.FromArgb(0, 0, 0, 0));
        var circleColor = new CanvasRadialGradientBrush(baseCanvas, Colors.White, Colors.DarkBlue);
        circleColor.Center = new Vector2() { X = 300f, Y = 400f };
        circleColor.RadiusX = 200f;
        circleColor.RadiusY = 200f;
        ds.FillCircle(new Vector2() { X = 300f, Y = 400f }, 200f, circleColor);
    }

    drawSession.DrawImage(drawSurface);
}

Ahora si se dibujará nuestro círculo:

image

Pero no tiene nada especial ¿Donde están esos efectos? Vamos a empezar con ellos, creando una nueva instancia de la clase DirectionalBlurEffect que podemos encontrar en el namespace Microsoft.Graphics.Canvas.Effects:

var directionalBlur = new DirectionalBlurEffect()
{
    BorderMode = EffectBorderMode.Soft,
    Angle = 90,
    BlurAmount = 40,
    Source = drawSurface
};

Las tres prímeras propiedades definen parámetros del efecto: Ángulo de difuminación, cantidad y como tratar los bordes de la imagen generada. El cuarto es el interesante. Todo efecto en Win2D, bueno casi todos, tiene la propiedad Source, donde podemos indicar un ICanvasImage como fuente de datos sobre la que aplicar el efecto. En este caso, es el CanvasRenderTarget donde creamos nuestro círculo anteriormente. Ahora solo tenemos que dibujar el efecto, en vez del CanvasRenderTarget y voila!:

drawSession.DrawImage(directionalBlur, 100f, 100f);

El resultado es nuestro mismo círculo, pero con un difuminado direccional aplicado:

image

Ahora ya hemos visto como aplicar un efecto a nuestros objetos. Con cualquier tipo, ya sea texto, círculos, rectángulos, imágenes… con todo tenemos que hacer la misma operación para aplicar un efecto. Pero lo realmente interesante es que podemos aplicar más de uno. Por ejemplo, podemos añadir un efecto de  transformación de color, usando una matriz de 4x4, a nuestro círculo difuminado. Simplemente tenemos que indicar en la propiedad Source el nuevo efecto:

var directionalBlur = new DirectionalBlurEffect()
{
    BorderMode = EffectBorderMode.Soft,
    Angle = 90,
    BlurAmount = 40,
    Source = new ColorMatrixEffect()
    {
        Source = drawSurface,
        ColorMatrix = new Matrix5x4()
        {
            M11 = 0f, M12 = 0f, M13 = 0f, M14 = 0f,
            M21 = 0f, M22 = 0f, M23 = 0f, M24 = 0f,
            M31 = 1f, M32 = 0f, M33 = 1f, M34 = 0f,
            M41 = 0f, M42 = 0f, M43 = 0f, M44 = 0f,
            M51 = 1f, M52 = 1f, M53 = 0.4f, M54 = 1f
        }
    }
};

Así podemos encadenar tantos efectos como queramos. Los resultados dependerán de nuestra habilidad:

image

Si no necesitamos animar el efecto, lo mejor es que lo creemos de antemano, al preparar los objetos. Calcular y aplicar efectos es costoso aunque esté acelerado por hardware. Debemos recordar que estamos tratando además con Tablets y Smartphones, con una capacidad gráfica mucho más reducida que un PC de escritorio o una consola. En nuestro ejemplo, he añadido a la clase Planet una propiedad de tipo ICanvasImage llamada ImageToDraw y una propiedad bool llamada IsPlanet. Si IsPlanet es true, almaceno en ImageToDraw la superficie de dibujo con los efectos aplicados, lista para ser dibujada:

private ICanvasImage CreateDrawableImageWithEffects(Planet item)
{
    var myBitmap = new CanvasRenderTarget(baseCanvas, (item.BodyRadius * 4.0f) + 50.0f, (item.BodyRadius * 4.0f) + 50.0f);
    using (var ds = myBitmap.CreateDrawingSession())
    {
        ds.Clear(Color.FromArgb(0, 0, 0, 0));
        ds.FillCircle(new Vector2()
                        {
                            X = ((item.BodyRadius * 4.0f) + 50.0f) / 2.0f,
                            Y = ((item.BodyRadius * 4.0f) + 50.0f) / 2.0f
                        },
                        item.BodyRadius, item.BodyColor);
    }

    var blurEffect = new GaussianBlurEffect()
    {
        BlurAmount = 30f,
        BorderMode = EffectBorderMode.Soft,
        Source = new GammaTransferEffect()
        {
            Source = myBitmap,
            AlphaAmplitude = 1f,
            RedAmplitude = 3f,
            BlueAmplitude = 15f,
            GreenAmplitude = 2f,
            RedExponent = 5f,
            GreenExponent = 5f,
            BlueExponent = 10f,
            AlphaExponent = 6f
        }
    };

    return blurEffect;
}

Un caso aparte es el del fuego inferior, que está animado. En este caso, la animación se consigue mediante una matríz de 3x2 para la traslación y otra para el escalado. Lo que vamos a hacer es precrear todo el efecto antes de dibujar, sin indicar las matrices:

private void CreateFlameEffect()
{
    this.morphology = new MorphologyEffect()
    {
        Mode = MorphologyEffectMode.Dilate,
        Width = 12,
        Height = 1
    };

    var colorize = new ColorMatrixEffect()
    {
        Source = new GaussianBlurEffect()
        {
            Source = this.morphology,
            BlurAmount = 3f
        },
        ColorMatrix = new Matrix5x4()
        {
            M11 = 0f, M12 = 0f, M13 = 0f, M14 = 0f,
            M21 = 0f, M22 = 0f, M23 = 0f, M24 = 0f,
            M31 = 1f, M32 = 0f, M33 = 1f, M34 = 0f,
            M41 = 1f, M42 = 1f, M43 = 0f, M44 = 1f,
            M51 = 1f, M52 = -0.5f, M53 = 0f, M54 = 0.5f
        }
    };

    //Generate perlin noise field
    this.flameAnimation = new Transform2DEffect()
    {
        Source = new BorderEffect()
        {
            Source = new TurbulenceEffect()
            {
                Frequency = new Vector2() { X = 0.1f, Y = 0.1f },
                Size = new Vector2() { X = 1500, Y = 80f }
            },
            ExtendX = CanvasEdgeBehavior.Mirror,
            ExtendY = CanvasEdgeBehavior.Mirror
        }
    };

    //Create a displacement map.
    this.flamePosition = new Transform2DEffect()
    {
        Source = new DisplacementMapEffect()
        {
            Source = colorize,
            Displacement = this.flameAnimation,
            Amount = 40f
        }
    };
}

En este código podemos ver varios efectos en uso: Morphology, GaussianBlur, ColorMatrix, Transform2D y DisplacementMap. Todavía usaremos otro más, llamado Composite, que nos permitirá combinar dos efectos sin usar su propiedad Source.

En el momento de dibujar, crearemos el objeto al que aplicar los efectos, estableceremos las matrices de traslación y escalado y crearemos el efecto Composite:

//Draw fire!
//Create the object bitmap to render fire over.
using (this.flameRenderBase = new CanvasRenderTarget(baseCanvas, new Size(Math.Ceiling(baseCanvas.ActualWidth), 50)))
{
    using (var ds = this.flameRenderBase.CreateDrawingSession())
    {
        ds.Clear(Color.FromArgb(255, 0, 255, 0));
        ds.FillRectangle(new Rect(0, 0, Math.Ceiling(baseCanvas.ActualWidth), 50), Color.FromArgb(255, 0, 0, 255));
    }

    this.flameAnimation.TransformMatrix = Matrix3x2.CreateTranslation(this.seed / -1, this.seed);
    this.seed -= 3;
    this.flamePosition.TransformMatrix = Matrix3x2.CreateScale(new Vector2(2.1f, 2.5f),
                                                                new Vector2((float)baseCanvas.ActualWidth / 2, 50f));

    this.composite = new CompositeEffect()
    {
        Inputs = { this.flamePosition, flameRenderBase }
    };

    this.morphology.Source = this.flameRenderBase;

    drawSession.DrawImage(this.composite, (float)0, (float)baseCanvas.ActualHeight - 30);
}

Y listo, obtendremos un efecto de fuego animado. Con la variable seed controlamos el movimiento. Al hacerla negativa, verticalmente parecerá que el fuego se mueve hacia arriba, mientras que al volverla positiva, parecerá moverse horizontalmente de izquierda a derecha.

Esto es todo por ahora! Lo más interesante de Win2D es que este pequeño ejemplo que hemos hecho, que dista mucho de ser un juego completo por supuesto, comparte todo el código entre Windows y Windows Phone. Además nos aporta una nueva serie de características gráficas para hacer nuestras aplicaciones XAML mucho más ricas visualmente.

Como siempre, aquí puedes descargarte el proyecto de ejemplo actualizado. Espero que te sea de utilidad.

Un saludo y Happy Coding!

[Windows Phone 8.1 TIP] Gestionar la barra de sistema

Hola a todos!

Existe un tema tan ignorado en el desarrollo de aplicaciones para Windows Phone, por parte de los propios desarrolladores, que incluso parece que se trate de algún tipo de tabú social auto impuesto. Hablo de la barra del sistema. esa pequeña franja de apenas 20 píxeles de alto, donde Windows Phone muestra la hora, la cobertura y otras notificaciones.

Generalizando, que nunca es bueno, encontramos dos formas típicas de gestionar la barra de sistema. Existen desarrolladores que simplemente no la gestionan. Ese espacio no es suyo, no les importa y no se molestan ni siquiera en mirarlo. Luego están los “acaparadores”, los que quieren cada píxel de la pantalla para ellos mismos. Estos le prestan atención a la barra del sistema el tiempo suficiente para comprobar que su código ha hecho efectivamente su trabajo: ocultarla para siempre.

Personalmente creo que ni unos ni otros tienen razón. Al menos no siempre. Como usuario, tener la barra de sistema con sus notificaciones y la hora, me conserva en la aplicación activa. Si tengo que salir de la aplicación solo por querer ver la hora o si tengo conexión WiFi, quizás no vuelva a entrar. Por otro lado, ver la barra de sistema con fondo negro y letras blancas, o viceversa, pegada a una app muy bien diseñada… es algo chocante… que hace deslucir toda la aplicación.

Y digo que es una pena, por lo extremadamente sencillo que es gestionarla. En Windows Phone 8.1 existe una clase llamada StatusBar. En ella encontraremos un método estático, GetForCurrentView, que nos devolverá la instancia de StatusBar para la ventana actual, nuestra aplicación.

Una vez hecho esto, tenemos propiedades como BackgroundColor y ForegroundColor que nos permiten definir el color de fondo y de letra de nuestra barra de sistema, pudiendo adaptarla a los colores usados en la aplicación:

var statusBar = StatusBar.GetForCurrentView();
statusBar.BackgroundColor = (Color)Resources["BackgroundColor"];
statusBar.ForegroundColor = (Color)Resources["ForegroundColor"];

De esta forma integramos la propia barra en el diseño de la aplicación y hacemos que de la impresión de que forma parte de la misma:

image

Con solo tres líneas de código integramos la barra perfectamente. Pero además StatusBar incluye una propiedad ProgressIndicator, que nos permite mostrar una barra de progreso indeterminada en la cabecera, así como un mensaje.

También tenemos a nuestra disposición métodos para mostrar y ocultar la barra de estado: ShowAsync y HideAsync.

Y nada más por hoy… No seais desarrolladores perezosos y prestad atención a estos detalles jejeje.

Un saludo y Happy Coding!

[UNIVERSAL] Win2D, Gráficos acelerados por hardware (1 de 2)

spaceship

En Septiembre, Microsoft liberó la primera versión de una librería gráfica para WinRT, llamada Win2D. Se trata de un wrapper sobre Direct2D, compatible con aplicaciones universales. Lo mejor de esto es que nos ofrece una forma muy sencilla de acceder a la potencia de DirectX para dibujar gráficos en dos dimensiones, sin tener que irnos a desarrollar código C++.

El desarrollo de Win2D está en progreso y el equipo va liberando versiones según terminan sprints, por lo que tenemos que tener en cuenta que puede haber fallos, bugs y que no está totalmente implementado Direct2D en este momento. No obstante empieza a ser una herramienta muy interesante y con la que podemos hacer cosas relativamente “resultonas” de forma muy sencilla.

Es importante destacar los tres principios de diseño sobre los que el equipo de Win2D trabaja:

  1. Todo lo que se pueda hacer con Win2D, debe poder hacerse con Direct2D. No se incluirán APis extras en Win2D. De esta forma simplemente se expone Direct2D a más desarrolladores.
  2. Crear una superficie de API que imite a Direct2D de forma que portar código sea más sencillo.
  3. Compartir el código y los progresos con la comunidad de desarrolladores.

Una vez que hemos visto un poco sobre qué es Win2D… ¿Para qué sirve? Podríamos pensar que una libraría 2D en pleno 2014 es algo con una utilidad limitada, pero nada más lejos de la realidad. Precisamente su punto fuerte es que solo es 2D, sencillo y antiguo 2D. Nos quitamos del medio la complejidad del 3D, el consumo de recursos del 3D y nos beneficiamos de una forma de dibujar gráficos bidiménsionales con aceleración por hardware. Podrías usar Win2D para un juego, pero también para hacer un increible fondo de pantalla animado y con aceleración para tu próxima aplicación. Incluso Win2D incluye algunos filtros, muy fáciles de implementar, para imágenes.

Vamos a comenzar por el principio.. Donde está el paquete de NuGet?

Where is my NuGet?

Podemos buscar el paquete de NuGet de Win2D hasta la saciedad y lo cierto es que no lo encontraremos. Actualmente el equipo de Win2D publica todo su código en un repositorio de GitHub, con lo que para usar esta librería tendremos que compilarnos nosotros mismos el paquete. Aunque tranquilos, porque es realmente sencillo de hacer.

Desde Visual Studio, en el panel “Team Explorer”,  vamos a la opción “Connect to team projects” (El icono en forma de enchufe en la parte superior). En la parte inferior encontraremos “Local Git Repositories” y entre otras opciones, una llamada “Clone”. Al presionarla nos pedirá la ruta del repositorio que queremos clonar. En la página del repositorio de GitHub de Win2D podemos encontrar la url para clonarlo, que es la siguiente:

https://github.com/Microsoft/Win2D.git

Simplemente introducimos esta URL en la primera caja de texto y seleccionamos el directorio local donde queramos guardar el repositorio local y presionamos el botón “Clone”:

image

Una vez terminado de clonar, tenemos que abrir una consola de símbolo de sistema de desarrollador (Developer command prompt), navegar al directorio donde hemos descargado el repositorio y ejecutar el archivo build.cmd.

build process

Tendremos que tener un poco de paciencia, porque el proceso tarda un poco en completarse. Al terminar es posible que nos indique que falta NuGet.exe y una línea de comandos a ejecutar para descargarlo. Lo descargamos, vamos al directorio build\nuget y ejecutamos el archivo build-nupkg.cmd y voila! en el directorio bin ya tendremos nuestros dos paquetes NuGet de Win2D.

image

Solo tenemos que irnos ya a las opciones de Visual Studio en el menú “Tools” y en opciones de NuGet agregar la ruta de los paquetes NuGet de Win2D como una nueva fuente de paquetes, de forma que podamos referenciarlos en nuestro proyecto.

Tenemos dos paquetes a nuestra disposición: Win2D y Win2D debug. El segundo permite que depuremos la propia librería Win2D. En este artículo uso la librería normal.

Superficie de dibujo

Una ves que hemos añadido el paquete NuGet a un proyecto universal, podemos empezar a usarlo. La gran baza de Win2D es la sencillez para integrarlo con XAML. Simplemente debemos añadir un nuevo namespace en la página donde queramos usar Win2D apuntando a Microsoft.Graphics.Canvas y añadir a la página el control CanvasControl contenido en ese namespace:

<Page
    x:Class="Win2DSampleUniversal.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Win2DSampleUniversal"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:win2d="using:Microsoft.Graphics.Canvas"
    mc:Ignorable="d">

    <Grid>
        <win2d:CanvasControl x:Name="baseCanvas"/>
    </Grid>
</Page>

Ya tenemos una superficie sobre la que dibujar. Para poder mostrar gráficos, debemos usar una sesión de dibujo, representada por el objeto CanvasDrawingSession. Este objeto se obtiene de la propiedad DrawingSession de CanvasDrawEventArgs, en el evento Draw del CanvasControl, por lo que tendremos que manejarlo para poder dibujar.

Podríamos crear un código como el siguiente para el evento Draw:

private void baseCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    using (var drawSession = args.DrawingSession)
    {
        drawSession.Clear(Colors.Blue);

        Vector2 center = new Vector2()
        {
            X = (float)sender.ActualWidth / 2.0f,
            Y = (float)sender.ActualHeight / 2.0f
        };
        float radius = 150;

        drawSession.FillCircle(center, radius, Colors.LightGreen);
    }
}

Como CanvasDrawingSession implementa IDisposable y además su ciclo de vida es el mismo que el de la ejecución del evento, una vez terminado el evento se pierde la sesión, usamos un bloque using para obtener la instancia de CanvasDrawEventArgs.

Una vez hecho esto, CanvasDrawingSession expone una serie de métodos que nos permiten dibujar diferentes formas como círculos, elipses, texto, rectángulos, líneas o imágenes. También nos permite borrar el contenido de la pantalla para empezar a dibujar de nuevo.

En el código anterior, primero borramos el contenido del canvas estableciendo el fondo azul, a continuación creamos una instancia de la clase Vector2 para definir el centro de un circulo, una variable float para definir el radio y usamos el método FillCircle para dibujarlo, indicando en el último parámetro el color del que queremos rellenarlo. El resultado es el siguiente:

image

Nada espectacular hasta ahora. Además el contenido es estático, el evento Draw solo se invoca una vez al comienzo de la aplicación o si redimensionamos la ventana que contiene la aplicación WinRT. En realidad lo que ocurre es que el método Draw se ejecuta cuando se invalida la superficie del control CanvasControl. Podríamos crear un DispatcherTimer que simplemente la invalidase cada X milisegundos. Si hacemos los cálculos, veremos que para conseguir una tasa de refresco de 60 cuadros por segundo, debemos ejecutar el DispatcherTimer cada 33 milisegundos, más o menos. Por lo que, simplemente en el evento Tick del DispatcherTimer, vamos a invalidar el redibujado. Primero inicializamos el timer en el método OnNavigatedTo de nuestra página:

protected override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    this.random = new Random();
    this.timer = new DispatcherTimer();
    this.timer.Interval = new TimeSpan(0, 0, 0, 0, 30);
    this.timer.Tick += timer_Tick;
    this.timer.Start();
}

En el evento Tick, debemos parar el timer, para evitar que cualquier operación que se alargue más de 30 milisegundos haga que se ejecute varias veces sin llegar a dibujar. A continuación llamamos al método Invalidate de nuestro CanvasControl y se volverá a lanzar el evento Draw:

private void timer_Tick(object sender, object e)
{
    this.timer.Stop();
    baseCanvas.Invalidate();
}

Pero… ¿No vuelves a arrancar el DispatcherTimer al terminar el evento Tick? En realidad… lo deberíamos arrancar de nuevo cuando haya terminado de dibujar, y ya no tengamos nada más que hacer. Esto es tan solo una forma de hacerlo y cada uno debe experimentar con la cadencia de tiempo, con la forma de parar y arrancar el timer… para adaptarlo a lo que necesitemos en cada desarrollo.

Bien, ya podemos invalidar y redibujar cada XX milisegundos pero… ¿De qué nos vale? En el método Draw solo dibujamos un círculo una y otra vez. Pero no se trata de un objeto al que podamos acceder desde fuera… es una simple llamada al método FillCircle. Vamos a complicar un poco más el ejemplo. Como me gusta mucho la ciencia ficción, sobre todo la que trata sobre el espacio… vamos a hacer una vista del espacio con planetas. En primer lugar, ¿Como podemos definir un planeta? Quizás podríamos necesitar saber su posición con respecto a nuestro punto de vista, el radio central para definir su tamaño, el radio de sus anillos (si tiene claro…) El color de su cuerpo y el color de sus anillos. Incluso su aceleración, pues normalmente se trata de cuerpos celestes en movimiento. Así que podríamos desgranarlo en una clase más o menos como esta:

public class Planet
{
    public Vector2 Position { get; set; }

    public float BodyRadius { get; set; }

    public float RingsRadius { get; set; }

    public CanvasRadialGradientBrush BodyColor { get; set; }

    public Color RingColor { get; set; }

    public float Acceleration { get; set; }
}

Podría funcionar… Como podemos ver, el color del cuerpo del planeta se define con una propiedad del tipo CanvasRadialGradientBrush. Este nuevo tipo de brocha, nos permite crear un degradado radial, en ves de uno lineal. Los que hayáis trabajado en WPF, abrazaros y llorad de alegría. Estoy con vosotros.

Para crear un degradado CanvasRadialGradientBrush, al crear la instancia necesitaremos pasarle cierta información:

  • El CanvasControl donde vamos a pintarlo.
  • El color inicial (central)
  • El color final (borde exterior)

Además, una vez hecho esto, necesitaremos indicarle tres propiedades más: Center, RadiusX y RadiusY:

var gradient = new CanvasRadialGradientBrush(baseCanvas, Colors.Yellow, Colors.Orange);
gradient.Center = new Vector2() { X = 10, Y = 10 };
gradient.RadiusX = 40.0f;
gradient.RadiusY = 40.0f;

Es muy importante que el centro sea el mismo que el del objeto donde lo queremos usar. Las coordenadas que indicamos aquí, no son relativas al objeto donde lo apliquemos, son relativas a la pantalla. Es muy importante esto, o podemos darnos cabezazos contra la mesa pensando en porqué no se pinta el degradado correctamente.

Bien, ahora que sabemos como crear el degradado, vamos a crear un método llamado CreatePlanet, al que le pasemos ciertos valores y cree una instancia de un objeto de tipo Planet y lo guarde en una lista:

private void CreatePlanet(CanvasRadialGradientBrush bodyColor, float bodyRadius, float acceleration)
{
    var planet = new Planet();
    Vector2 center = new Vector2()
    {
        X = (float)this.random.Next(0, (int)baseCanvas.ActualWidth),
        Y = (float)this.random.Next(0, (int)baseCanvas.ActualHeight)
    };

    planet.Position = center;
    planet.BodyRadius = bodyRadius;
    planet.BodyColor = bodyColor;
    planet.BodyColor.Center = planet.Position;
    planet.BodyColor.RadiusX = planet.BodyRadius;
    planet.BodyColor.RadiusY = planet.BodyRadius;
    planet.RingColor = planet.BodyColor.Stops.Last().Color;
    planet.RingsRadius = this.random.Next((int)planet.BodyRadius, (int)planet.BodyRadius * 2);
    planet.Acceleration = acceleration;
    this.planets.Add(planet);
}

Este código simplemente rellena las propiedades que hemos indicado anteriormente del planeta. Como podemos ver, a la propiedad BodyColor (CanvasRadialGradientBrush) le pasamos los mismos valores que a la clase Planet para el centro y el radio X e Y.

Pero Yeray, esto es una clase normal y corriente, no es un objeto especial de DirectX super chulo y emocionante… Tranquilos a todo llegaremos. Para lo que nos va servir esta clase es para definir que hay que pintar, de una forma sencilla. Por ejemplo, podemos crear 125 instancias de la clase Planet, muy pequeñas y con un degradado radial de blanco a transparente, con este código:

for (int i = 0; i < 125; i++)
{
    CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.White, Colors.Transparent),
                 this.random.Next(5, 10), (float)this.random.NextDouble());
}

El radio será de entre 5 y 10 píxeles, y la aceleración un valor aleatorio entre 0 y 1. En nuestro evento Draw, podemos escribir ahora código que recorra la lista de planetas y usar el método FillCircle para dibujar cada uno:

private void baseCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    using (var drawSession = args.DrawingSession)
    {
        drawSession.Clear(Colors.Black);

        foreach (Planet item in this.planets)
        {
            drawSession.FillCircle(item.Position, item.BodyRadius, item.BodyColor);
            drawSession.DrawCircle(item.Position, item.RingsRadius, item.RingColor);
        }
    }

    if (this.timer != null)
        this.timer.Start();
}

Usamos el método FillCiircle para crear el círculo y rellenarlo con el color de cuerpo (El degradado radial que creamos anteriormente) e indicamos la posición y el radio. A continuación usamos el método DrawCircle, para dibujar exactamente igual un círculo, pero en esta ocasión sin relleno, solo dibujando el borde con el color de anillo y la misma posición. Si ejecutamos este código en el simulador de Windows Store, obtendremos algo parecido a esto:

image

Simplemente con un bucle ya hemos dibujado algo parecido a un campo de estrellas… con algo de imaginación al menos. Ahora, vamos a aprovecharnos del DispacherTimer para agregar algo de lógica relacionada con la propiedad Acceleration que hemos incluido en la clase Planet. A ver que os parece:

private void timer_Tick(object sender, object e)
{
    this.timer.Stop();

    foreach (Planet planet in this.planets)
    {
        planet.Position = new Vector2()
        {
            X = planet.Position.X,
            Y = planet.Position.Y < baseCanvas.ActualHeight ? planet.Position.Y + planet.Acceleration : -60
        };
        planet.BodyColor.Center = planet.Position;
    }

    baseCanvas.Invalidate();
}

Con el código anterior, antes de invalidar el CanvasControl, recorremos la lista de planetas y creamos una nueva instancia de Vector2, dejamos la posición X en el mismo punto en el que se encuentra. Con la posición Y, comprobamos si es menor que el area vertical de la pantalla. Si lo es, a la posición Y actual le añadimos la aceleración de la instancia de planeta. Si es mayor, reseteamos la posición a un valor negativo, fuera de la pantalla para que continue avanzando.

Con esto, conseguimos un movimiento vertical descendente de los círculos. Pero además como no todos los planetas tienen la misma aceleración, obtenemos el efecto de profundidad.

Ahora, con el código que hemos creado, podemos seguir creando más planetas, más grandes, con diferentes colores y con más aceleración de forma que obtengamos una mayor sensación de profundidad:

private async Task CreateObjects()
{
    for (int i = 0; i < 125; i++)
    {
        CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.White, Colors.Transparent),
                     this.random.Next(5, 10), (float)this.random.NextDouble());
    }

    for (int i = 0; i < 7; i++)
    {
        CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.DarkGoldenrod, Colors.Maroon),
                     this.random.Next(20, 30), (float)this.random.Next(1, 2));
    }

    for (int i = 0; i < 4; i++)
    {
        CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.Goldenrod, Colors.DarkOrange),
                     this.random.Next(40, 50), (float)this.random.Next(3, 4));
    }

    for (int i = 0; i < 3; i++)
    {
        CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.Yellow, Colors.Orange),
                     this.random.Next(65, 75), (float)this.random.Next(6, 7));
    }
}

Simplemente añadiendo estos nuevos planetas, nuestro código los dibujará, de una forma parecida a la siguiente:

image

Esto ya parece otra cosa… además, como hemos añadido diferentes aceleraciones en cada capa, desde más lenta a más rápida, obtenemos un mejor efecto de profundidad. Lo se, no es que sea precisamente lo último en tecnología gráfica, pero ilustra lo que os quiero contar jeje.

Ahora que tenemos un campo de estrellas en su lugar, nos falta ¡una nave espacial! y cual que mejor que un X-Wing.

Afortunadamente Win2D incluye métodos y objetos para trabajar con imágenes de forma muy sencilla. Uno de esos objetos es el CanvasBitmap, que nos permite cargar una imagen almacenada en un archivo en el proyecto:

var img = await CanvasBitmap.LoadAsync(baseCanvas, @"Assets\Spaceship.png");

Usando el método estático LoadAsync y pasándo como parámetro nuestro CanvasControl y la ruta relativa de la imagen en el proyecto, tenemos creado un objeto CanvasBitmap.

Una vez creado el objeto CanvasBitmap, en nuestro método Draw lo podemos mostrar usando el método DrawImage con tres parámetros:

  • el CanvasBitmap a dibujar
  • una instancia de Rect (Windows.Foundation.Rect) para definir la posición (X, Y) y el tamaño de destino.
  • una instancia de Rect  para definir la posición (X, Y) y el tamaño dentro de la imagen original, que queremos pintar.

El que el segundo Rect nos permita definir un fragmento de la imagen, es especialmente útil para trabajar con sprites.  Pero no todo va a ser tan facil. Al igual que con la clase Planet, vamos a definir una clase Spaceship que identifque a nuestra nave espacial. Esta vez es un poco más sencilla: una posición, un desplazamiento negativo y una imagen a dibujar:

public class Spaceship
{
    public Vector2 Position { get; set; }

    public Vector2 NegativeDisplacement { get; set; }

    public CanvasBitmap Image { get; set; }
}

La posición y la imagen, están claras. Pero, ¿Desplazamiento negativo? Bueno, ha sido una idea por darle un poco más de lógica a la demo. ¿Y si al mover la nave en cualquier dirección, aplicamos la misma cantidad de movimiento a las estrellas y planetas, pero en la dirección contraria? Le damos un poco más de interactividad a la lógica.

Primero lo primero, en el mismo método que creaba los planetas, vamos a crear la instancia de la clase Spaceship que vamos a usar:

this.spaceship = new Spaceship();
this.spaceship.Image = await CanvasBitmap.LoadAsync(baseCanvas, @"Assets\Spaceship.png");
this.spaceship.Position = new Vector2()
{
    X = (float)(baseCanvas.ActualWidth / 2) - 60,
    Y = (float)(baseCanvas.ActualHeight / 1.5) - 55
};

Simplemente le asignamos el CanvasBitmap y posicionamos la imagen en el centro de la pantalla. Para ello, calculamos el centro y le restamos la mitad del tamaño con el que queremos dibujar la nave.

En nuestro evento Draw, tras dibujar los planetas, vamos a añadir código para dibujar la nave:

if (this.spaceship != null && this.spaceship.Image != null)
{
    drawSession.DrawImage(this.spaceship.Image,
                          new Rect(this.spaceship.Position.X, this.spaceship.Position.Y, 120, 110),
                          new Rect(0, 0, 1952, 1857));
}

Como dijimos anteriormente, el primer Rect indica el destino y el segundo indica el pedazo de la imagen que queremos mostrar. En este caso toda la imagen, que como veréis no me he molestado en disminuir desde una resolución mastodóntica… pensando en hacer algunos juegos de ampliar y reducir la imagen, para dar efecto de elevación… Pero eso para otro tutorial.

image

Ya solo nos queda una cosa: Poder interactuar con la nave moviéndola al pulsar sobre ella. Para ello, vamos a definir la propiedad ManipulationMode del CanvasControl y manejar los eventos: ManipulationStarted, ManipulationDelta y ManipulationCompleted:

<win2d:CanvasControl x:Name="baseCanvas" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                     Draw="baseCanvas_Draw"
                     ManipulationMode="All"
                     ManipulationStarted="baseCanvas_ManipulationStarted"
                     ManipulationCompleted="baseCanvas_ManipulationCompleted"
                     ManipulationDelta="baseCanvas_ManipulationDelta"
                     Margin="-50"/>

Como solo queremos manipular la “nave” cuando el usuario pulse sobre ella y la arrastre, en el evento ManipulationStarted, comprobamos si el punto sobre el que empieza la manipulación está contenido en el area de la imagen, definida por la posición + el tamaño. Si está contenida, señalaremos una variable boolean indicando que efectivamente estamos manipulando la nave:

void baseCanvas_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
    if ((e.Position.X >= this.spaceship.Position.X && e.Position.X <= this.spaceship.Position.X + 120) &&
        (e.Position.Y >= this.spaceship.Position.Y && e.Position.Y <= this.spaceship.Position.Y + 110))
    {
        this.movingSpaceship = true;
    }
}

Simplemente comprobamos si la posición X, Y de ManipulationStartedRoutedEventArgs, está dentro del area de la nave. Una vez hecho esto, es en el evento ManipulationDelta donde recibiremos la información mientras el usuario arrastra la imagen, y donde modificaremos su posición:

 

void baseCanvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    if (this.movingSpaceship)
    {
        this.spaceship.Position = new Vector2()
        {
            X = (float)(this.spaceship.Position.X + e.Delta.Translation.X),
            Y = (float)(this.spaceship.Position.Y + e.Delta.Translation.Y)
        };

        this.spaceship.NegativeDisplacement = new Vector2()
        {
            X = (float)e.Delta.Translation.X / -1,
            Y = (float)e.Delta.Translation.Y / -1
        };
    }
}

 

Primero comprobamos si estamos manipulando la nave. A continuación, usamos la propiedad Delta de ManipulationDeltaRoutedEventArgs para obtener la cantidad de traslación realizada desde la última vez que se lanzó el evento. Normalmente el valor es bastante pequeño y relativo al punto donde hayamos tocado. Si desplazamos el puntero/dedo hacia abajo o hacia la derecha, tendremos un valor positivo. Si lo desplazamos hacia arriba o izquierda, un valor negativo. A continuación, cogemos ese mismo valor y obtenemos su inverso (si es 3.2, obtenemos –3.2) y lo guardamos en el Vector2 NegativeDisplacement. Un poco más adelante lo vamos a usar en nuestra lógica.

Por último, el evento ManipulationCompleted nos informa de que el usuario ha dejado de actuar sobre el objeto. En ese momento, podemos establecer la variable boolean a false de nuevo, ya no existe manipulación y reseteamos el NegativeDisplacement a 0, pues ya no hay movimiento:

void baseCanvas_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
    this.movingSpaceship = false;
    this.spaceship.NegativeDisplacement = new Vector2() { X = 0, Y = 0 };
}

Como podemos ver, en ninguno de estos eventos invalidamos el CanvasControl. No lo necesitamos, nuestro DispatcherTimer sigue activo y lo que vamos a hacer es añadir la lógica que tenga en cuenta el NegativeDisplacement a la hora de calcular el movimiento de los objetos:

 

private void timer_Tick(object sender, object e)
{
    this.timer.Stop();

    foreach (Planet planet in this.planets)
    {
        planet.Position = new Vector2()
        {
            X = planet.Position.X < baseCanvas.ActualWidth ?
                planet.Position.X + this.spaceship.NegativeDisplacement.X :
                (float)this.random.Next(0, (int)baseCanvas.ActualWidth),

            Y = planet.Position.Y < baseCanvas.ActualHeight ?
                planet.Position.Y + planet.Acceleration + this.spaceship.NegativeDisplacement.Y :
                -60
        };

        planet.BodyColor.Center = planet.Position;
    }

    baseCanvas.Invalidate();
}

 

En primer lugar, tanto en el valor X como Y, comprobamos si estamos dentro de la pantalla. Si estamos dentro, en el eje X le sumamos a la posición actual el valor de X de NegativeDisplacement de la nave. En el eje Y a la posición ya le sumamos el valor de aceleración del propio planeta, para crear la sensación de movimiento. Ahora además le añadimos el valor de Y de NegativeDisplacement. Si estamos fuera de la pantalla o a punto de salir de ella, en el eje X volvemos a calcular un valor aleatorio de X para repintar el planeta en otra parte de la pantalla. En el eje Y simplemente lo pintamos al principio de la pantalla, unos píxeles fuera, para que al animarlo vaya entrando en la misma.

Como podemos ver, no hacemos nada con la nave. En el evento ManipulationDelta ya hemos actualizado el Vector2 Position, con lo que el propio evento Draw la dibujará en la posición adecuada.

Y con esto, si ejecutamos tanto en Windows Store como Phone, podremos mover la nave en todas direcciones y veremos como las estrellas se desplazan en la dirección inversa.

Lo único que nos queda por ver es… como se integra con el resto del XAML. La verdad es que no es ningún misterio, tan solo tenemos que poner el XAML que queramos mostrar, a continuación del CanvasControl y funcionará automáticamente. Por ejemplo, por completar la imitación de un juego de naves, yo he añadido un TextBlock y una ProgressBar que muestran la “energía” restante de la nave:

<Grid>
    <win2d:CanvasControl x:Name="baseCanvas" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                         Draw="baseCanvas_Draw"
                         ManipulationMode="All"
                         ManipulationStarted="baseCanvas_ManipulationStarted"
                         ManipulationCompleted="baseCanvas_ManipulationCompleted"
                         ManipulationDelta="baseCanvas_ManipulationDelta"
                         Margin="-50"/>

    <StackPanel VerticalAlignment="Top" HorizontalAlignment="Stretch" IsHitTestVisible="False">
        <TextBlock Text="Energy:" FontSize="24" FontWeight="Bold" Foreground="Cyan" Margin="24,24,0,0"/>
        <ProgressBar x:Name="EnergyBar" Minimum="0" Maximum="100" Value="100" Background="Transparent"
                     Margin="24,12,24,0" Height="12">
            <ProgressBar.Foreground>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                    <GradientStop Offset="0" Color="Red"/>
                    <GradientStop Offset=".5" Color="Yellow"/>
                    <GradientStop Offset="1" Color="Green"/>
                </LinearGradientBrush>
            </ProgressBar.Foreground>
        </ProgressBar>
    </StackPanel>
</Grid>

Simplemente colocamos el XAML como con cualquier otra página. El resultado final de todo el trabajo que hemos hecho, una app universal, compartiendo todo el código, tanto para Windows como para Windows Phone:

image

Y llegamos al final! Creo que hemos dado un buen repaso por encima de las capacidades básicas de Win2D, como conseguirlo y como poder empezar a usarlo. Quedan más cosas que puede hacer, como aplicar filtros a imágenes, pero eso se va a quedar para el siguiente artículo.

Como siempre, aquí podéis descargaros el ejemplo usado en este artículo. Espero que lo disfrutéis tanto como yo lo he hecho escribiéndolo.

Un saludo y Happy Coding!

 

[AppStudio] Aplicaciones universales a un click de ratón

Hola a todos!

Hace ya casi un año que escribí sobre AppStudio por última vez. Mucho ha llovido desde entonces y AppStudio a cambiado mucho así que, ahora que está de moda en el cine, quiero hacer un “reboot” del artículo original.

En este año que ha pasado, AppStudio ha crecido y cambiado mucho:

Hace un año…

Hace unos minutos…

appstudioOld

appstudioNew

La filosofía sigue siendo la misma, pero el diseño ha evolucionado, al igual que la usabilidad. Ahora puedes generar apps Windows también, incluso cuenta con un “simulador” Windows adicional al de Windows Phone, en el que ver la preview de tus apps. Porque además, AppStudio soporta ahora apps universales:

image

Todo esto y muchas más novedades son lo que me han decidido a reescribir ese artículo original, para repasarlas todas y ver lo que nos puede ofrecer una herramienta que cada vez es más potente.

Vamos a empezar por el principio…

Ahora podemos entrar a AppStudio desde AppStudio.Windows.com. Una vez dentro, tenemos un menú en la parte superior de la pantalla con varias opciones. Las más interesantes ahora: “My projects”, “Start new” y “Sample apps”. Start new nos lleva directamente a crear una nueva app, al igual que el botón “Start new project” que tenemos en la parte superior derecha de la pantalla.

My projects

Esta opción nos lleva a nuestra área personal, donde podremos ver los proyectos que hemos inciado, los hayamos terminado o no, y nos ofrece opciones para manejar cada proyecto:

image

Directamente desde esta pantalla podremos editar un proyecto ya existente con el botón “Edit” de cada uno, generar el código fuente y el paquete de publicación compilado con el botón “Generate” o eliminarlo si no deseamos conservarlo, usando el botón “Delete”.

Sample apps

En el menú “Sample apps” encontraremos dos aplicaciones universales creadas por el equipo de AppStudio:

image

Por cada app, podremos descargarnos su código fuente generado con AppStudio, de forma que tengamos una referencia del tipo de código que genera, como se estructura y como funciona. También vamos a poder ver las aplicaciones en funcionamiento, tanto para Windows Store como para Windows Phone, puesto que ambas aplicaciones de ejemplo están publicadas en las tiendas de la plataforma. Es una forma muy sencilla de ver de que es capaz AppStudio y como lo hace internamente. Si no te las has descargado, hazlo! además he de reconocer que la de Windows App Studio es bastante resultona gráficamente. Kudos para el equipo!

Start new o Start new project

Y llegamos a la parte interesante! con la opción de menú “Start new” o el botón “Start new project” se carga la pantalla de plantillas. Aquí encontraremos tres secciones:

  • Primero las plantillas más usadas: Estas son las plantillas destacadas, que más suelen usarse. Nos permiten crear nuestro proyecto tanto para Windows Phone como para Windows Store.
  • Plantillas web apps: Nos permite crear una aplicación a partir de un sitio web existente. Por ahora solo funciona para Windows Phone.
  • Más plantillas: Son iguales que las primeras que vimos, simplemente más tipos y diseños para que no partas de 0 en tu proyecto.

image

Es importante darnos cuenta que la primera de todas las plantillas, “Empty App” nos permite crear una aplicación totalmente vacía con la que comenzar. Es la que vamos a usar en este artículo, así que vamos a por ella. Pinchamos en “Empty App” y nos mostrará un popup con el preview de la plantilla.

En este caso está totalmente vacía, ya que se trata de una aplicación totalmente en blanco:

image

Presionando el botón “Create” pasamos a la página central de la creación de una aplicación en App Studio:

image

Aquí podemos definir toda nuestra aplicación a golpe de ratón. Lo mejor es que, además, todo lo que vayamos cambiando se irá actualizando en el simulador que tenemos a la izquierda, con lo que podremos ver una preview en tiempo real de como va a quedar nuestra app. Tenemos que tener en cuenta que no es un emulador de Windows Phone, es una simulación, no creo que sea 100% exacta, pero a un 90% seguro que coincide. Pero hay que tenerlo en cuenta.

Lo primero que necesitamos es cambiar el nombre de nuestra aplicación. En este caso yo quiero hacer una app que muestre los vídeos de youtube que tenemos en WPSUG, por lo que la app se llamará WPSUG. Original eh? Podemos cambiar el título escribiendo en la caja de texto que está justo sobre el emulador, donde pone “App Title”.

Ahora vamos a ponerle algo de contenido. Al lado derecho del simulador tenemos 6 cajas punteadas. en cada una de ella podemos añadir una sección. Justo debajo tenemos los tipos de secciones:

  • Rss: El contenido de un RSS que tengamos disponible.
  • Html: Contenido html estático que podemos incluir en la aplicación.
  • Youtube: Una busqueda, un usuario o una lista de reproducción.
  • Flickr: Una busqueda de flickr.
  • Bing: Una busqueda de Bing.
  • Facebook: Una página de facebook.

Debajo de estas, disponemos de dos secciones más, en la categoría avanzada:

  • Menú: Si no tenemos suficiente con las secciones iniciales, limitadas a 6, podemos usar un menú para crear más subsecciones.
  • Colección: Nos permite crear una colección de datos, ya sean estáticos (dentro de la app) o dinámicos (en la nube). En ambos casos puedes definir las columnas que tendrá la colección y el tipo de cada una de ellas.

En nuestro caso, vamos a empezar con una sección HTML, que será la primera que verá el usuario al entrar. En esta sección vamos a explicar un poco que es WPSUG y la finalidad de la aplicación. Al presionar sobre la opción de Html, se abre un popup donde podemos indicar el título de la sección y el contenido HTML:

image

Una vez que estemos contentos con el texto solo tenemos que presionar “Confirm” para terminar de añadir la sección.

A continuación vamos a crear una sección de YouTube, donde mostraremos los videos del usuario “WPSUG”. Este tipo de sección solo nos pide que indiquemos el título, el tipo de busqueda y el término. En nuestro caso, el título será  “Webcasts”, el tipo de busqueda será por usuario y el término será “WPSUG”, como se puede ver en la siguiente pantalla:

image

Presionamos el botón “Confirm”, se añade la nueva sección y ya podremos interactuar con el simulador y ver los datos reflejados. Incluso si pinchamos en el botón verde bajo el teléfono que pone “Windows preview” podremos ver una previsualización, e interactuar con ella, de nuestra app en Tablet.

image

Y ya está, estos eran los datos que queríamos mostrar. Ahora lo que necesitamos es personalizar la aplicación. En primer lugar, podemos presionar en el link “Edit” de cada una de las secciones que hemos creado. Acuerdate de guardar primero con el botón “Save” que tienes en la parte superior derecha, para no perder nada. Al presionar “Edit” veremos la configuración de la página de detalle de cada elemento, en el caso de la sección de tipo YouTube. podremos elegir como se muestra la lista: con o sin imágen, solo título, imágen grande, solo imágenes, bloques con imagen de fondo y texto superpuesto… hay un montón de combinaciones posibles:

image

Cuando tengamos el diseño que queremos, solo tendremos que guardar con el botón “Save” y volver atrás con el botón atrás que hay al lado del nombre de la sección.

Ahora, otra cosa que podemos hacer es cambiar el logotipo por defecto de Windows que se muestra al lado del título. En la parte superior izquierda de la pantalla, al lado de la caja de texto donde escribimos el título de la aplicación, podemos ver el logo por defecto. pinchando sobre él, nos permite seleccionar otro de nuestro equipo, del OneDrive asociado a la cuenta con la que hagamos entrado en AppStudio o de recursos de AppStudio. En mi caso voy a escoger el logo de WPSUG que tengo en mi equipo. La imagen debe cumplir algunos requisitos:

  • Debe tener un alto y ancho mayor de 300 píxeles.
  • Debe ser cuadrada (mismo alto y ancho).

Una vez que escojamos una imágen que cumpla esas dos reglas, se subirá y colocará automáticamente en su posición. Ahora necesito cambiar el Fondo de la app, quiero que sea blanco y el color de la letra, para que la app no sea blanca y negra. Para ello, previo guardado de los cambios, Vamos a pasar de la pestaña “Content” en la que hemos estado a “Themes”:

image

Tenemos varias opciones aquí. En primer lugar podemos escoger el esquema de colores entre tres opciones:

  • Tema oscuro (Dark Style). Es el tema oscuro del dispositivo por defecto.
  • Tema claro (Light Style). Es el tema claro del dispositivo por defecto.
  • Personalizar el tema (Custom Style). Un tema nuevo creado por nosotros.

En el tema personalizado, podemos cambia el color de la barra de aplicación, el color de fondo y el color de letra. Incluso podemos añadir una imagen de fondo a la aplicación si lo deseamos. Una vez que la aplicación quede a nuestro gusto, podemos guardar los cambios y continuar a los Tiles, donde podremos definir los tiles que usaremos en nuestra aplicación. En nuestro caso, vamos a usar el tema oscuro por defecto.

image

Podemos escoger entre tres tipos de tile diferente:

  • Flip template: Un tile que tiene contenido en la parte de atrás y gira para mostrarlo.
  • Cycle template: Un tile al que podemos asociar una colección de imágenes (si tenemos) que se irán mostrando.
  • Iconic template: Un tile con texto y una imagen a mostrar.

En el caso de nuestro ejemplo, vamos a usar el flip template. Nos permite mostrar el logo de WPSUG en varios tamaños y especificar una imagen extra como contenido trasero del tile.

En esta misma pestaña podemos definir también el uso de una splash screen y lock screen. pulsando sobre el botón “Splash & Lock” en la parte de arriba. Tendremos la posibilidad de especificar la imagen que queremos usar en la SplashScreen de Windows Phone y Windows Store y la imagen de lock screen de Windows Phone.

image

Y una vez definido esto último, estamos listos para terminar el proceso en la pestaña de “Publish info”. En esta última pestaña podremos definir varios parámetros de la aplicación:

  • App title: El título de la aplicación.
  • App Description: Una descripción para incluir en la aplicación.
  • Language: Esto es una novedad incluida hace muy poco tiempo. Nos permite indicar el idioma por defecto de la aplicación, de forma que a la hora de certificar no tengamos problemas. Por ejemplo, la aplicaión WPSUG tiene los textos en español. Si selecciono como Language el Inglés, fallará la revisión de la tienda.
  • Include about page: Si lo habilitamos se incluye una página About automáticamente.
  • Enable ad client: Nos permite habilitar la inclusión de publicidad e indicar los datos del PubCenter de Microsoft.
  • Associate app with the Store: permite asociar esta aplicación con la tienda directamente para poder publicarla.
  • Privacy Statement URL: url a la declaración de privacidad de la aplicación. Si no vamos a modificar la aplicación, no es necesario incluir una url propia y AppStudio usará una por defecto.

Ya solo nos queda darle al botón grande y azul que pone “Finish” para poder recoger los frutos de nuestro trabajo. Esto nos llevará a una página donde podremos ver la previsualización de nuestra aplicación tanto en tablet como en teléfono, interactuar con ella y ver si se nos ha olvidado algo:

image

Si todo es correcto, solo tenemos que presionar el botón “Generate” y aparecerá un popup donde podremos indicar que queremos obtener exactamente:

image

En estos momentos, solo podemos generar código/paquetes para Windows Phone 8.1 y Windows 8.1. Podemos escoger si queremos obtener un paquete instalable o uno de publicación y añadir algún comentario. Por úlimo al darle a Generate se cerrará el popup y en la parte de abajo de la pantalla podremos ver un indicador de la generación. Es muy rápido pero no es necesario que esperemos. El sistema nos enviará un email cuando termine, avisándonos de que nuestra aplicación está completa.

image

Es interesante notar que cada generación de una aplicación no pisa la generación anterior. mantenemos como un histórico, de ahí la opción de añadir un comentario. podemos tener varias versiones generadas y ver exactamente que hicimos en cada una apuntándolo en el campo de comentarios.

Y esto es todo por hoy. Espero que os sea muy útil y ayude a que exprimáis todo el jugo a AppStudio.

Un saludo y happy coding!

 

[OFFTOPIC] Starting a new adventure

Hello all!

Today i want to steal some space of my blog to talk about something other than Windows, Windows Phone or XAML. Today i want to talk about myself hehe.

Without getting deep in details, after one year working in a company called Icinetic, yesterday i leave the job. Wait! What? With the actual crisis the world is suffering you are leaving your job? Why? Are you crazy? Well, the why, the crisis, even the job not really matter… The important thing is… i had an awesome year working with such a group of talented people. Share projects, lines of code, adventures and misadventures with a lot of good friends.

In spain there is a popular phrase “lo bueno, si breve, dos veces bueno” (the good things, if brief, twice as good) and another one “no hay mal que cien años dure, ni cuerpo que lo aguante” (there is not bad situations with 100 year durations, and no one can’t hold them) What this two phrases try to explain is: The good things and the bad things aren’t there to stay forever. And i think this was the time to leave in the search of new things… bad or good? i don’t know, and really i don’t care much about that.

So, from today i’m starting a new life as a freelance contractor. Hoping to be able to work with much more intelligent, interesting and tallented people, as i did until today, to try to learn more, do more things and… code a lot of XAML and C# by the way!

Regards and happy coding!

Publicado por Josué Yeray Julián Ferreiro con no comments
Archivado en:

[OFFTOPIC] Empezando una nueva aventura

Hola a todos!

Hoy voy a permitirme robar un poco de espacio para hablar de algo distinto a Windows, Windows Phone, XAML…. Hoy voy a hablar un poco de mi jeje.

Sin entrar mucho en detalles, tras un año en Icinetic ayer fué mi último día. ¿Por qué? Pero si te acabas de mudar a Sevilla! Y que importa realmente… Lo importante es que he pasado un gran año, he tenido el placer de compartir proyecto, aventuras y desventuras con un montón de gente increible y muy muy buena profesional y personalmente, entre ellos Javier Suarez, un gran compañero. He tenido la oportunidad de trabajar en un proyecto muy interesante, con mucha difusión y tras terminar en ese proyecto he podido pasar los últimos 8 meses con un proyecto Windows Phone XAML muy chulo, con bluetooth LE de por medio, mucho (ojo, pero muuuuuucho) trabajo de animaciones y efectos en XAML y bastante grande para lo que suele ser una app móvil.

Pero como dice el dicho: “lo bueno, si breve, dos veces bueno” o era “no hay mal que mil años dure, ni cuerpo que lo aguante”? Bueno, ya me entendéis.

Ahora me lanzo a una nueva aventura, la de hacerme autónomo… o “Contractor” como se dice en la lengua de Shakespeare. Con mucha ilusión, con muchas ideas. Sin un duro, pero con muchas ganas de desarrollar más apps, de escribir más, y de compartir más conocimiento.

¿Qué podría ir mal? Un gran compañero me enseño que no hay nada que no se arregle con un “Malo será…”.

Un saludo a todos y Happy Coding!!

Publicado por Josué Yeray Julián Ferreiro con no comments
Archivado en:

[XAML] Peek view en Visual Studio 14 CTP4

Hola a todos!

Desde ayer está disponible la CTP 4 de Visual Studio 14. Trae un monton de novedades, pero hoy quiero hablaros de una, que por su simplicidad, es una auténtica maravilla para los que pasamos muchas horas trabajando con XAML.

Hablo de Peek definition. En español se podría traducir como “ver definición”. No debemos confundirlo con el Go to definition que ya teníamos disponible en Visual Studio 2013. Aunque su función es parecida, la forma en que se ha implementado es totalmente diferente.

En Visual Studio 2013 ya teníamos Peek Definition para el código C#/VB.NET/C++, pero no para XAML. Este nos permitía ver en línea, sin cambiar de archivo ni posición, la definición del miembro sobre el que la usemos:

image 

Pues por fín se han decidido a implementarlo en XAML también. Funciona con controles de usuario y recursos, de forma que, por ejemplo, podemos ver la definición de una plantilla, sin cambiar de pantalla y editarla mientras vemos los resultados en el contexto real donde se usa:

image

Si trabajas con muchas páginas XAML distintas, controles de usuario y estilos en varios diccionarios de recursos, podrás apreciar la ventaja de “Peek definition” sobre “Go to definition”. Como extra, decir que funciona en cualquier “sabor” de XAML: Tanto proyectos Windows Phone y Windows Store, como WPF, que ya se merecía algo de cariño.

Un saludo y Happy Coding!!

[MATERIALES] Madrid Mobility Day 2014

Hola a todos!

El pasado 30 de septiembre, junto a Javier Suarez Ruíz, Alejandro Campos y Luis Guerrero tuve el placer de participar en el Madrid Mobility Day, organizado en las oficinas de Microsoft Ibérica.

Para empezar a caldear el ambiente, la primera charla que hice fue una breve introducción al desarrollo de aplicaciones universales para Windows 8.1 y Windows Phone 8.1. He subido la presentación a SlideShare para que podáis acceder a ella fácilmente:

También pude compartir charla con Javier Suarez Ruiz en una charla sobre Xamarin.Forms. Javi publicará la presentación y los ejemplos en su blog.

Como siempre, ha sido un placer poder estar con toda la gente de DX de Microsoft Ibérica. Incluso aprovechamos para sacarnos una foto Javi y yo en una de las cafeterías:

photo

Un saludo y Happy Coding!

[Universal Apps] Monetización con Fortumo Mobile Payments

Hola a todos!

Después de un descanso de vacaciones y escribir algo menos de lo normal para dedicar tiempo a otros proyectos… aquí vuelvo a la carga. Además, con uno de los temas que más nos interesan a los devs… ¡Como ganar dinero!.

Bueno, en este caso concreto, veremos realmente como hacer más fácil al usuario gastarse el dinero. Por defecto, podemos integrar de forma muy sencilla las APIs de In App Purchase de WinRT en nuestras aplicaciones universales. El único problema, por buscar alguno, es que el método de pago será una tarjeta de crédito asociada a la cuenta de la tienda. En algunos países, Microsoft ya soporta pagos con el operador móvil, pero son los menos.

El echo es que mucha gente no gusta de incluir una tarjeta de crédito en su cuenta de la tienda y en un gran número de países, no todo el mundo tiene acceso a una. Esto puede crear una situación, anecdótica para el usuario, pero muy preocupante para nosotros: El usuario quiere pagarnos, pero ¡NO PUEDE!

Es curioso, pero es un problema… en el que nadie suele pensar. Desarrollas tu app, pones opciones de pago y la lanzas al mercado. Luego en las analíticas ves que mucha gente presiona el botón de quitar publicidad o de comprar items… pero en la tienda, tus compras reales son mucho menores… Y piensas que es que la gente se ha arrepentido (que seguro que un porcentaje lo ha hecho). Pero la realidad es que hay una parte de esa gente, que ha tenido que cancelar la operación por que no le dabas opciones validas con las que pagar. Creo que es algo, que pocos se plantean, aunque después de pensar en ello 5 minutos digas, ¡Como lo he podido pasar por alto!

Lo que si que tiene todo el mundo, o al menos la gran mayoría, que posee un smartphone es una tarjeta SIM y un contrato (o saldo prepago) con una operadora. De esta forma, si encontrásemos la forma de que el usuario nos pagase con su móvil directamente, todos podrían pagar. Este es el momento en el que alguien podría decir… eh espera, Microsoft soporta pago por operador en Windows Phone. Si y no:

  • Es verdad que Microsoft soporta pago por operador, solo en 37 países de los 191 donde se encuentra presente la tienda.
  • En cada país solo soporta pagos si el operador de tu SIM está adherido. Por ejemplo en España soporta pagos si tu operador es Movistar o Vodafone. Si es Yoigo, Orange, u otro… mala suerte
  • Está soportado en Windows Phone, NO en Windows Store. Esto complica el desarrollo de apps universales.

En este terreno es donde entran empresas como Fortumo Mobile Payments, que incrementa y mejora las opciones de pago que nos ofrece Microsoft:

  • En primer lugar, Fortumo está disponible ahora mismo en 81 paises. Eso son 44 más que el operator billing de Microsoft.
  • En mi opinión, los países presentes en Fortumo son más importantes. Es muy probable que cualquiera en Finlandia, Francia o Estados Unidos tenga una tarjeta de credito. Pero países como India, Moldavia, Nigeria, Pakistan o Korea pueden ser más proclives para el pago con operador. Fortumo incluye todos estos paises y Microsoft no.
  • Fortumo es compatible con Android, Windows Store y Windows Phone (8 y 8.1) con lo que es una solución ideal para nuestras aplicaciones universales. También dispone de un api web, que nos permite integrarlo en páginas web.

Ahora la pregunta es, ¿Que esfuerzo nos requiere Fortumo? Pues sorprendentemente, es muy sencillo de integrar en nuestras aplicaciones. Lo primero que necesitaremos, será registrarnos en su web www.fortumo.com/register. Una vez hecho esto, podremos ver la página de servicios y crear un nuevo servicio:

image

Tras presionar el botón “Create new service”, veremos una lista de los servicios disponibles. En este ejemplo seleccionaremos “In App purchasing for Windows Phone & Windows 8”. En la siguiente página, solo tenemos que pulsar “Start now” para comenzar a configurar el servicio:

StartNow

Ahora llega el momento de configurar nuestro servicio de pago. En primer lugar, ¿En que países funcionará? Podemos escoger país por país o seleccionarlos todos y además marcar la opción “Auto add new territories when they become available”, que añadirá nuevos países cuando estén disponibles. Hablando con la gente de Fortumo me han dicho que normalmente el ritmo es de un par nuevo de territorios cada trimestre.

addTerritories

A continuación, nos piden información sobre el tipo de app o juego en el que queremos implementar Fortumo. En la mayoría de paises no se permiten servicios de pago de juego o con contenido adulto explícito, por lo que no podremos usar Fortumo en ese caso. También nos pedirán el tipo de item a vender. Podremos escoger entre un elemento de la app o juego o créditos virtuales. Por último necesitamos indicar un nombre para el servicio:

 

configureitem1

Y llegamos a la parte interesante. Cuanto queremos cobrar por nuestro producto:

configureprice

Aquí, tenemos varias opciones. Tenemos que entender que el sistema de pago con Fortumo se basa en mensajes SMS especiales. Debido a esto, existen un mínimo y un máximo que podemos cobrar, que puede ser diferente en cada país. Así, tenemos tres opciones:

  • El precio más barato posible. Por ejemplo esto es 2 euros en Austria, 30 céntimos de euro en Bosnia, 1.31 euros en Chipre y 10 céntimos de euro en Venezuela.
  • El precio más caro posible. Esto nos sube a 10 euros en Austria, 1.76 euros en Bosnia, 4.04 euros en Chipre y 10 céntimos de euro en Venezuela. Aquí podemos ver que con este sistema de pago, jugamos al son de las regulaciones de cada país. En Venezuela solo vamos a poder cobrar 10 céntimos de euro.
  • Por último, un valor aproximado al valor que indiquemos en euros. Si indicamos 1.5 euros, En Austria marcará 2 euros, porque es el mínimo, en Chipre subirá a 1.31 euros, en Bosnia al máximo 1.76 euros y así…

También en esta pantalla podremos ver el porcentaje de ese dinero que nos llevamos, que también varía de un país a otro, pues aquí jugamos con las operadoras y las regulaciones. Tenemos países con un share revenue del 85% y otros con un 20%. Puede parecer en un primer momento una locura en cuanto a lo poco que ganamos, pero un 20% de 100 euros es más que un 70% de 0 euros. Aquí el truco está en llegar a captar dinero que antes se nos estaba escapando.

Una vez configurado nuestro “target” de precio, tendremos que indicar información de soporte para los usuarios, nombre del servicio, etc…

ConfigureBillingData

Ya solo nos queda aceptar las regulaciones especiales de cada país y finalizar el proceso confirmando los precios de cada país:

countryRegulations

Y voila! nuestro servicio está creado. El setup nos dirige automáticamente a la página de descarga del SDK, en el menú superior podremos ir a la sección “General” donde podremos hacer varias cosas:

image

En primer lugar, tenemos el botón “Go Live”. Por defecto el servicio se crea en modo pruebas, una vez que publiquemos nuestra app debemos cambiar el servicio para que los pagos se puedan realizar.

A continuación vemos el tipo de servicio y los países disponibles. Y lo más importante al final: El Service ID y el Secret que tendremos que incluir en nuestra aplicación para realizar pagos. Ahora vamos a descargar el SDK. Como estamos haciendo una app universal, solo tendremos que descargar el de Windows Store, que podremos referenciar tanto en el proyecto Windows 8.1 como Windows Phone 8.1. Una vez que lo tengamos referenciado, empezar a trabajar es realmente fácil. Solo tendremos que trabajar con la clase Payment del namespace FortumoWindows:

Make a Payment with Fortumo
public Task<bool> ShowPayment(IDictionary<string, string> parameters)
{
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

    Payment payment = new Payment("ServiceId", "Secret");
    if (parameters != null)
    {
        foreach (var parameter in parameters)
        {
            payment.AddParameter(parameter.Key, parameter.Value);
        }
    }

    payment.Completed += (sender, paymentArgs) =>
    {
        if (paymentArgs.status == PaymentStatus.ok)
        {
            tcs.TrySetResult(true);
        }
        else
        {
            tcs.TrySetResult(false);
        }
    };
    payment.Show();

    return tcs.Task;
}

En el código podemos ver todo lo necesario para hacer un pago. En primer lugar creamos una instancia de la clase Payment pasándole el ServiceID y el Secret que obtuvimos al crear el servicio. A continuación, incluimos parámetros, si existen. Los parámetros disponibles los podemos ver en la web de ayuda de Fortumo.

A continuación, manejamos el evento Completed. En este evento nos llegará el resultado de la operación, de forma que podamos actuar en relación a ella. paymentArgs incluye información como el país y la moneda de pago, el operador móvil, el precio pagado, el id de producto… de forma que podamos verificar o almacenar la información que necesitemos.

Por último, mostramos la ventana de pago con el método Show. Esto lanza la propia página de Fortumo para que el usuario pueda realizar el pago:

image

image

Y listo! Ya tenemos nuestro nuevo sistema de pagos integrado en nuestra app. Con muy poco código, podemos poner a funcionar este sistema en nuestras aplicaciones, incluso combinarlo con el sistema de IAP que ofrece la propia tienda, de forma que el usuario pueda escoger como pagar en cada momento y nosotros obtengamos más ingresos.

Como siempre, aquí tenéis el ejemplo totalmente funcional usado en este artículo. He creado un servicio que se encarga de realizar el pago. Solo tenéis que poner el ServiceID y Secret de vuestra cuenta y debería funcionar perfectamente en cualquier app universal.

Antes de terminar, decir que la gente de Fortumo está haciendo grandes promociones, regalando crédito de Adduplex para los que integren esta solución de pago en sus aplicaciones, como podéis ver en este artículo en su blog,  así que aprovechad la oportunidad!!

Un saludo y Happy Coding!

[Windows Phone 8.1] Nokia SensorCore SDK

El martes 24 de junio pasado Nokia presentó y puso a nuestra disposición su último SDK (por el momento al menos): SensorCore. Es tanto el último que en la página oficial de su documentación no se aclaran si llamarlo Lumia SensorCore SDK o Microsoft Mobile SensorCore SDK. Aún se encuentra en beta, pero es totalmente usable para publicar apps a la tienda.

Ya lo habían presentado en el Build 2014, pero hasta ahora se encontraba en beta privada y exclusivamente bajo invitación. Por fin está aquí, ¿Qué nos aporta?

SensorCore es una tecnología de sensores de muy bajo consumo, integrada a nivel de hardware en ciertos modelos de la familia Lumia. Por el momento lo podemos encontrar y usar en el Lumia 630/635 y el Lumia 930. El Lumia 1520 y el Lumia Icon también incluyen estos sensores, pero deberemos esperar hasta la actualización de firmware Cyan para que el software necesario quede habilitado. En el resto de dispositivos, no podremos usar este SDK. No por una cuestión de software, si no por la falta de los sensores de hardware de los que hace uso.

Estos sensores de los que hablamos, de bajo consumo, se habilitan y recaban información en segundo plano. Según nos cuentan desde Microsoft, el consumo de energía es totalmente marginal. Aprovechan nuestra actividad de GPS para obtener datos del mismo, no activándolo ellos de forma activa, y monitorizando nuestros pasos gracias a su podómetro. Luego el software crea un histórico de todos estos datos de los últimos 10 días y los procesa de forma que puede indicarnos el tipo de actividad realizada en un momento dado (correr, caminar…), lugares frecuentes como nuestra casa o la oficina, número de pasos en un momento determinado, o rutas comunes que solemos usar.

Como estaréis pensando, esta es una cantidad ingente de datos personales y muy sensibles. En todo momento las aplicaciones tendrán que pedirnos acceso a la información y podremos desactivar totalmente esta tecnología en la configuración del dispositivo. Incluso podremos borrar el histórico de los 10 últimos días cuando deseemos.

Ahora que ya sabemos un poco que es y como funciona SensorCore, vamos a ver como integrarlo en nuestra aplicación. SensorCore SDK se compone de cuatro APIs distintas:

  • Step counter, número de pasos del usuario.
  • Activity monitor, tipo de actividad.
  • Place monitor, identifica la casa y la oficina del usuario así como lugares conocidos.
  • Track point monitor, guarda puntos de geo localización.

Vamos a ver cada una de estas APIs en detalle. Pero primero, veamos algunas buenas practicas relacionadas con el uso del SDK.

Buenas prácticas

Como hemos dicho anteriormente, no todos los dispositivos soportan SensorCore. De echo, solo un pequeño número de ellos lo hace en este momento. Hasta que el Lumia 1520 y el Icon obtengan Cyan, solo el Lumia 630/635 y el 930 soportan este SDK. Debido a esto, es especialmente recomendable que siempre nos aseguremos de que el dispositivo sobre el que se ejecuta nuestra aplicación soporta SensorCore. Para ello cada una de las APIs que hemos comentado incluye un método IsSupportedAsync, al que debemos llamar en primer lugar antes de intentar acceder a los datos.

Otra cosa que debemos tener en cuenta es que, aun estando soportado a nivel de hardware y software, puede que el usuario haya desactivado la opción de Location and motion en la configuración del dispositivo. Si es así, cualquier llamada a un API del SDK fallará con una excepción. Para evitar problemas, deberemos envolver nuestro código que haga uso de SensorCore en un bloque Try/Catch. En el Catch podemos obtener la excepción y haciendo uso de la clase SenseHelper que incluye el SDK extraer el error que se ha producido de la propiedad HRESULT de la propia excepción usando el método GetSenseError, que devuelve un enumerado SenseError con el tipo de error encontrado. SenseDisabled o LocationDisabled pueden ser dos errores muy comunes.

Por último, pero no menos importante, deberemos hacernos cargo de activar y desactivar la monitorización del API desde nuestra aplicación cuando la aplicación esté en primer plano o sea enviada a segundo plano. Para esto lo mejor es controlar la visibilidad de la ventana principal, para activar o desactivar el API que estemos usando dependiendo de si es visible o no.

Instalación

Para empezar a usar el SensorCore SDK solo tendremos que ir al administrador de NuGet y buscar “sensorcore”. Encontraremos dos paquetes. SensorCoreSDK Testing tools y SensorCoreSDK.

image

SensorCore SDK incluye los ensamblados necesarios para usar los sensores y acceder a las diferentes APIs. Por su parte las Testing tools incluye las mismas APIs que SensorCore, pero con datos pregrabados que nos permitirán probar las diferentes APIs aún sin disponer de un móvil compatible. Como siempre digo, si queremos hacer una aplicación comercial, estas herramientas nunca sustituirán totalmente las pruebas en dispositivos. Pero al menos nos ayudarán a desarrollar.

Algo que tenemos que tener en cuenta tras incluir SensorCore en nuestra aplicación es que no podremos compilarla como AnyCPU. Al usar componentes nativos, debemos compilarla en ARM para dispositivos o x86 para los emuladores.

Todas las APIs son muy parecidas, por lo que en este artículo vamos a ver a fondo una de ellas, StepCounter y su simulador StepCounterSimulator. Vamos a hacer una aplicación que tenga las siguientes características:

  • Detecte si nuestro dispositivo soporta o no SensorCore.
  • Dependiendo de la configuración de compilación use el simulador o no.
  • Muestre una gráfica de pasos y nos permita cambiar de día.

Al final, nuestro ejemplo debería parecerse a esto, para ello usaremos VisualStates y Behaviors, aunque en este artículo nos centraremos en SensorCore:

image

Step counter API

Como dijimos anteriormente, esta API nos permite obtener el histórico de los pasos dados por el usuario. Pero antes de aventurarnos a usarla, debemos comprobar que está realmente soportada por nuestro dispositivo. Para ello, tanto StepCounter como el resto de APIs de SensorCore disponen de un método llamado IsSupportedAsync, que devuelve true si está soportado o false si no lo está. Mirando a que más adelante queremos poder usar también el simulador, vamos a crear una interface llamada ISensorService donde crearemos un método IsStepsCounterSupported y otro GetStepsForDate:

ISensorService interface
public interface ISensorService
{
    /// <summary>
    /// Get if the device support or not StepsCounter
    /// </summary>
    /// <returns>true if supported, otherwise false.</returns>
    Task<bool> IsStepsCounterSupported();

    /// <summary>
    /// Get steps for a given date.
    /// </summary>
    /// <param name="date"></param>
    /// <returns>Steps recorded for given date.</returns>
    Task<StepCounterReading> GetStepsForDate(DateTime date);
}

De esta forma, podremos usar IoC para decidir que implementación usar sin tener que modificar nuestra ViewModel.

La implementación de IsStepsCounterSupported es muy sencilla, simplemente llamamos al método IsSupportedAsync de la clase StepCounter:

IsStepsCounterSupported
public async Task<bool> IsStepsCounterSupported()
{
    return await StepCounter.IsSupportedAsync();
}

Lo siguiente es implementar el método que nos devolverá los pasos para un día concreto. Para ello primero tendremos que obtener acceso a la clase StepCounter, usando el método GetDefaultAsync. A continuación, podremos usar el método GetStepCountAtAsync, que nos permite indicar una fecha y nos devuelve una instancia del tipo StepCounterReading:

GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    var stepCounter = await StepCounter.GetDefaultAsync();
    var steps = await stepCounter.GetStepCountAtAsync(date);

    return steps;
}

StepCounterReading contiene propiedades con detalles sobre la actividad para la fecha indicada:

  • WalkingStepCount, el número de pasos caminando.
  • RunningStepCount, el número de pasos corriendo.
  • WalkTime, el tiempo que hemos pasado caminando.
  • RunTime, el tiempo que hemos pasado corriendo.
  • TimeStamp, el momento en el que se creó la lectura actual.

Algo a tener en cuenta es que todos los métodos de SensorCore son nativos y si se produce un error, no nos devuelven el error completo como una excepción. En vez de eso, devuelven una excepción genérica con un valor HRESULT. Para saber exactamente que ha ocurrido debemos usar el método GetSenseError de la clase SenseHelper. Por lo tanto, podríamos reescribir el método anterior de forma segura como se muestra a continuación:

Safe GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    Exception failure = null;
    StepCounterReading steps = null;
    try
    {
        var stepCounter = await StepCounter.GetDefaultAsync();
        steps = await stepCounter.GetStepCountAtAsync(date);
    }
    catch (Exception ex)
    {
        failure = ex;
    }

    GetError(failure);

    return steps;
}

Ahora si se produce un error, podremos obtenerlo con GetError y reaccionar a él. El método GetSenseError devuelve un tipo que nos indica que es lo que ha ocurrido y que procesamos en el método GetError del código anterior:

GetError
private static void GetError(Exception failure)
{
    var error = SenseHelper.GetSenseError(failure.HResult);
    switch (error)
    {
        case SenseError.Busy:
            break;
        case SenseError.GeneralFailure:
            break;
        case SenseError.IncompatibleSDK:
            break;
        case SenseError.InvalidParameter:
            break;
        case SenseError.LocationDisabled:
            break;
        case SenseError.NotFound:
            break;
        case SenseError.SenseDisabled:
            break;
        case SenseError.SenseNotAvailable:
            break;
        case SenseError.SensorDeactivated:
            break;
        case SenseError.SensorNotAvailable:
            break;
        default:
            break;
    }
}

Solo tendremos que obtener el error y usando un Switch, ver lo que ha ocurrido para mostrar un mensaje al usuario, llevarlo a una pantalla de configuración o lo que necesitemos.

Ahora que ya es seguro usar nuestro servicio, podemos incluirlo en nuestra ViewModel para obtener los pasos por cada fecha:

VMSteps
IsSupported = await this.sensorService.IsStepsCounterSupported();

if (IsSupported)
{
    var stepsReading = await this.sensorService.GetStepsForDate(this.currentDate);
    if (stepsReading != null)
    {
        Steps = stepsReading.WalkingStepCount + stepsReading.RunningStepCount;
    }
}

Y listo, ya tenemos nuestra app funcionando. Pero solo en terminales que soporten SensorCore. A continuación vamos a ver como usar el simulador para nuestras pruebas en el emulador o en dispositivos sin SensorCore.

Step counter simulator API

Los métodos del simulador son exactamente los mismos que los del API normal, la única diferencia es que la clase no es StepCounter, es StepCounterSimulator y que necesitamos indicarle de donde queremos recoger los datos. Podemos grabar datos reales de un dispositivo o cargar datos ya grabados. En el repositorio de GitHub de Nokia tenemos varios archivos grabados que solo tendremos que descargar y agregar a nuestro proyecto (Content/Copy always). Podéis encontrarlos aquí.

Lo que vamos a hacer es crear una nueva implementación de la interface ISensorService. En este caso IsStepsCounterSupported devolverá true simplemente, sin realizar ninguna comprobación. Después de todo, estamos usando un simulador. Antes de poder implementar GetStepsForDate, crearemos un constructor que se encargue de cargar los datos pre grabados desde disco:

Fake data init
private async Task InitializeAsync()
{
    recording = await SenseRecording.LoadFromFileAsync("record.txt");
}

recording es una variable de tipo SenseRecording, que contiene todos los datos pregrabados. Ahora la usaremos para inicializar la clase StepCounterSimulator:

Simulated GetStepsForDate
public async Task<StepCounterReading> GetStepsForDate(DateTime date)
{
    var stepCounter = await StepCounterSimulator.GetDefaultAsync(recording, date.AddDays(-10));
    var steps = await stepCounter.GetStepCountAtAsync(date);

    return steps;
}

Además de la grabación de datos, también indicamos una fecha en el método GetDefaultAsync. Esta fecha sobre escribe la que se ha grabado en los datos para permitirnos cambiarla sin tener que editar el archivo de disco. En este caso simplemente le decimos que la fecha de inicio de los datos debe ser hace 10 días. Y no tenemos que hacer nada más, automáticamente llamamos a GetStepCountAtAsync igual que en la implementación real y nos devolverá datos, siempre que la fecha esté dentro de esos 10 días anteriores a hoy.

En este caso no hace falta incluir una manera segura de llamar a los datos, porque el simulador siempre devolverá datos correctos.

Usando IoC para decidir el API a usar

Ahora ya tenemos nuestras dos implementaciones: SenseService y SenseServiceFake. Pero, ¿Como cambiamos entre una y otra? Bien, en primer lugar es importante que lo que consuma nuestra ViewModel sea ISenseService, la interface, para poder cambiar de implementación. Para automatizar, aunque esto puede variar según tus necesidades, podemos usar nuestro Locator de ViewModels en conjunción con la configuración de compilación:

  • Para publicar en la store y hacer pruebas reales, tengo que compilar en Release, por lo que cuando se compile en Release usaré la implementación real SensorService.
  • Para pruebas durante el desarrollo en el emulador uso la configuración de Debug, por lo que cuando compile en Debug usaré la implementación simulada SensorServiceFake.

Con esto, nuestro Locator, podría quedar de la siguiente forma:

VMLocator
public VMLocator()
{
    ContainerBuilder builder = new ContainerBuilder();

#if DEBUG
    builder.RegisterType<SensorServiceFake>().As<ISensorService>().SingleInstance();
#else
    builder.RegisterType<SensorService>().As<ISensorService>().SingleInstance();
#endif
    builder.RegisterType<VMSteps>();

    this.container = builder.Build();
}

Ahora solo tenemos que incluir la interface ISensorService en nuestra ViewModel y voila! Todo debería funcionar correctamente, tanto en Debug como en Release, usando en cada caso la implementación correcta.

VMSteps constructor
public VMSteps(ISensorService sensorService)
{
    this.sensorService = sensorService;

    this.previousDayCommand = new DelegateCommand(ExecutePreviousDayCommand);
    this.nextDayCommand = new DelegateCommand(ExecuteNextDayCommand, CanExecuteNextDayCommand);
}

Y hasta aquí nuestro artículo de hoy. Como siempre, podéis descargar el código del ejemplo aquí para que os sirva de referencia. Espero que os sea de mucha utilidad y ver nuevas apps usando este SDK en la tienda.

Un saludo y Happy Coding!!

Almacenar credenciales de forma segura con PasswordVault

Un problema al que nos enfrentamos cuando tenemos que almacenar credenciales de nuestros usuarios en una aplicación móvil, es el como hacerlo de forma segura. Por muy seguro que un sistema afirme ser, siempre lo será tanto como su parte menos segura. Por lo que guardar el nombre y password de un usuario en los settings o en el almacenamiento local de nuestra aplicación sin cifrado de ningún tipo, es una negligencia gravísima. Bastará tener acceso al sistema de archivos por parte de un atacante para obtener esos datos.

Existen muchas formas de evitar esto. Y todas pasan de alguna forma por encriptar los datos almacenados. Quizás podríamos pensar que nos podríamos proteger simplemente calculando un hash del password y usar este hash para autentificar a nuestro usuario en el backend. Esto uede funcionar si el servicio de backend que usamos está bajo nuestro control o siendo de terceros admite esta opción. Pero en la gran mayoría de casos el backend nos pedirá un usuario y password. Incluso en el caso de poder usar el hash de un password, almacenarlo en texto plano junto al nombre de usuario es ya una información muy valiosa para un atacante.

En Windows 8.1 y Windows Phone 8.1 (Tanto Windows XAML como Silverlight) disponemos de un API llamada PasswordVault, que representa un almacén seguro para credenciales de usuario. Una aplicación solo podrá acceder a las credenciales almacenadas por ella misma y no a los de otras aplicaciones. Usarla además es realmente sencillo.

PasswordVault nos permite guardar las credenciales de un usuario mediante el método Add, pasándole una instancia de la clase PasswordCredential. El constructor de PasswordCredential nos permite indicar tres valores:

  • Resource, un nombre que identifica la credencial que se está creando.
  • UserName, el nombre del usuario… lógico no?
  • Password, el password.
PasswordCredential
var credential = new PasswordCredential("AppName_userCredentials", userEmail, userPassword);

Una vez creado, solo tenemos que pasarlo al método Add de nuestra instancia de PasswordVault:

PasswordVault Add method
PasswordVault pVault = new PasswordVault();
pVault.Add(credential);

Y ya hemos guardado nuestras credenciales de forma segura. Para recuperar los datos cuando los necesitemos de nuevo, podemos usar el método FindAllByResource. Este método recibe un parámetro con el nombre de recurso que indicamos al crear la credencial y devuelve una lista con todas las credenciales que coincidan con ese nombre:

FindAllByResource
PasswordVault pVault = new PasswordVault();
var credentials = pVault.FindAllByResource(resourceName);
return credentials.FirstOrDefault();

Es muy importante que, si existe la posibilidad de que al pedir las credenciales, estas no existan, envolvamos la llamada a FindAllByResource en un bloque Try/Catch. En vez de simplemente devolver nulo, el método falla con una excepción si no existen.

Tras obtener la credencial, podremos obtener directamente el nombre de usuario pero para poder acceder al password tendremos primero que usar el método RetrievePassword de la instancia de PasswordCredential con la que estemos trabajando.

RetrievePassword
var credential = credentials.First();
credential.RetrievePassword();

Tras esto podremos acceder al password almacenado usando la propiedad Password de PasswordCredential.

Por último, puede que en algún momento el usuario quiera que dejemos de almacenar sus credenciales, por lo que podremos usar el método Remove, para eliminarlas del PasswordVault.

PasswordVault Remove
var credentials = this.pVault.FindAllByResource(resourceName);
var credential = credentials.First();
if (credential != null)
    this.pVault.Remove(credential);

En este sentido es interesante tener en cuenta algo. Cuando desinstalemos la aplicación, las credenciales guardadas en el PasswordVault no serán eliminadas. Cuando guardamos algo en los settings o el almacenamiento local de nuestra aplicación, al desinstalar se elimina todo. Esto no ocurre con las credenciales guardadas en el PasswordVault, pues usan un almacenamiento central del sistema.

Y esto es todo! Usar PasswordVault es muy fácil y añade un extra de seguridad a nuestra aplicación, así que no hay excusa para no usarlo. Aquí os dejo una aplicación universal de ejemplo con el código MVVM y el servicio de credenciales compartido. Espero que os sea de utilidad.

Un saludo y Happy Coding!

[MATERIALES] Windows phone 8.1, MVVM, behaviors, animations y visual states.

El viernes 20 de Junio estuve con los chicos de RiojaDotNet en Logroño, dando una charla con Asier Tarancon sobre apps universales. Asier hizo una gran introducción a la plataforma y me allanó el terreno para hablar sobre como implementar MVVM en nuestras apps universales y como enriquecerlas con behaviors, animations y visual states.

Aquí os traigo los materiales de la misma. En primer lugar la presentación que usé. Es muy pequeña porque quise sobre todo usar Visual Studio para explicar las cosas:

 

Y a continuación los ejemplos de behaviors, animations y visual states.

Espero que os sean útiles.

Un saludo y Happy Coding!

[Windows Phone 8.1] Trio de ases: Behaviors, Animations y VisualStates (3)

Y llegamos al último artículo de esta serie, tras ver como trabajar con behaviors y animaciones, en esta ocasión vamos a hablar de los estados visuales.

Los VisualStates nos permiten definir diferentes estados para nuestra página o control para cambiar su apariencia e incluso su funcionalidad. Por ejemplo en una página donde el usuario puede ver un resumen de datos y tiene la opción de editarlos, podemos crear un VisualState de solo lectura y otro de edición, cambiando propiedades de los controles en cada uno de ellos para modificar la forma en la que el usuario interactúa con la página.

También, como vamos a ver en el ejemplo de este artículo, podemos crear estados visuales que modifiquen la interface de usuario dependiendo de la orientación del dispositivo. De esta forma podemos ofrecer al usuario la mejor experiencia en cada momento.

Los VisualStates usan animaciones para modificar los controles a los que quieren afectar y podemos usar behaviors para iniciar el proceso de cambio de uno a otro.

Por regla general, los VisualStates de una página se definirán en la primera Grid que contenga la página. Para ello incorporaremos el nodo, que debe ser el primero dentro de la Grid, VisualStateManager para definir los grupos y los estados de cada grupo:

VisualStateManager
<Grid>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="LayoutStates">
            <VisualState x:Name="Landscape">

            </VisualState>
            <VisualState x:Name="Portrait">

            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

</Grid>

Tras definir el nodo VisualStateManager.VisualStateGroups, definimos los grupos que necesitemos, en este caso uno. El nombre, LayoutStates, es libre y a nuestra elección. Por claridad deberíamos indicar un nombre descriptivo de la finalidad del grupo. En este caso este grupo contiene dos estados, Landscape y Portrait, nombres también a nuestra elección. Si quisiésemos tener otro grupo, podríamos ponerlo a continuación de este con sus correspondientes estados.

En este ejemplo, tenemos una lista de imágenes y queremos variar la forma de mostrarlas al usuario dependiendo de la orientación del dispositivo. Cuando estemos en horizontal, queremos usar un FlipView que haga uso de toda la pantalla. Por el contrario cuando la orientación sea vertical, queremos usar una GridView que muestre las imágenes en una cuadrícula. Así mismo, cuando estemos en vertical queremos que al hacer click en las imágenes aparezca un popup con la imagen seleccionada. Nuestro XAML quedaría más o menos así:

 

XAML
<GridView x:Name="ListImages" ItemsSource="{Binding Images}">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Width="150" Height="150" Margin="0">
                <Image Stretch="UniformToFill" Source="{Binding }"/>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
    <i:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="SelectionChanged">
            <core:GoToStateAction StateName="BigImage"/>
        </core:EventTriggerBehavior>
    </i:Interaction.Behaviors>
</GridView>

<Grid x:Name="Popup" Visibility="Collapsed" Background="#AA555555" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
    <Image Margin="40" Stretch="Uniform" Source="{Binding SelectedItem, ElementName=ListImages}">
    </Image>
    <i:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="Tapped">
            <core:GoToStateAction StateName="NormalState"/>
        </core:EventTriggerBehavior>
    </i:Interaction.Behaviors>
</Grid>

<FlipView x:Name="flipImages" Visibility="Collapsed" ItemsSource="{Binding Images}" SelectedItem="{Binding SelectedItem, ElementName=ListImages}">
    <FlipView.ItemTemplate>
        <DataTemplate>
            <Image Stretch="Uniform" Source="{Binding }"></Image>
        </DataTemplate>
    </FlipView.ItemTemplate>
</FlipView>

 

Como vemos, simplemente tenemos todos los controles necesarios: un GridView, una Grid llamada Popup y por último un FlipView. Por defecto tanto el popup como el FlipView tienen su visibilidad establecida a Collapsed. Lo que vamos a hacer es, en cada VisualState, mostrar u ocultar los controles que nos interesen en cada momento:

VisualState Landscape
<VisualState x:Name="Landscape">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListImages" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="flipImages" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

 

VisualState Portrait
<VisualState x:Name="Portrait">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListImages" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Visible"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="flipImages" Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Collapsed"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

 

Así, en el caso del VisualState Landscape, cambiamos la visibilidad del GridView a Collapsed y la del FlipView a Visibile. En el Portrait lo contrario, mostramos el GridView y ocultamos el FlipView.

¿Y como lanzamos cada VisualState? Tenemos que hacerlo manualmente. Detectar el cambio de orientación y cambiar al VisualState que corresponda. Aquí es donde los behaviors vienen en nuestra ayuda. En concreto vamos a servirnos del Behavior DataTriggerBehavior. En nuestra ViewModel vamos a acceder a la clase DisplayInformation, que nos ofrece la propiedad CurrentOrientation y el evento OrientationChanged. Analizando los datos que nos ofrecen vamos a exponer una propiedad llamada IsLandscape:

DisplayInformation
public VMMain()
{
    var dInfo = DisplayInformation.GetForCurrentView();
    if (dInfo.CurrentOrientation == DisplayOrientations.Landscape || dInfo.CurrentOrientation == DisplayOrientations.LandscapeFlipped)
        IsLandscape = true;
    else
        IsLandscape = false;

    dInfo.OrientationChanged += dInfo_OrientationChanged;
}

Luego en XAML, usaremos el DataTriggerBehavior para ver si esta propiedad es true, momento en el que usaremos el GoToStateAction para cambiar al estado Landscape, o false, para cambiar al estado Portrait:

DataTriggerBehavior
<i:Interaction.Behaviors>
    <core:DataTriggerBehavior Binding="{Binding IsLandscape}" ComparisonCondition="Equal" Value="true">
        <core:GoToStateAction StateName="Landscape"/>
    </core:DataTriggerBehavior>
    <core:DataTriggerBehavior Binding="{Binding IsLandscape}" ComparisonCondition="Equal" Value="false">
        <core:GoToStateAction StateName="Portrait"/>
    </core:DataTriggerBehavior>
</i:Interaction.Behaviors>

Y Voila! Si ejecutamos nuestra aplicación en el simulador de Windows 8.1 o el emulador de Windows Phone 8.1, veremos que al cambiar la orientación, automáticamente cambia la apariencia de nuestra aplicación. Además, como todo lo visto en los artículos anteriores, los VisualStates son también totalmente compatibles entre Windows Phone y Windows Store, por lo que el ejemplo incluido en este artículo es una aplicación universal que comparte el 100% del código C# y XAML usado:

image

Las aplicaciones de los VisualStates son muchas. Pero lo más importante es que nos ofrecen una forma sencilla de cambiar el aspecto de nuestra página. De echo, si editas la plantilla por defecto de cualquier control de XAML, verás que usa VisualStates para definir estados como Focus, Tapped, o Selected.

Y con esto llegamos al final de este artículo y de la serie. Espero que haya sido útil para ver una forma nueva de trabajar con el aspecto visual de nuestra página, lanzar acciones en respuesta a las del usuario y crear bonitas animaciones. Como siempre, aquí puedes descargar este último ejemplo para usarlo como referencia.

Un saludo y Happy Coding!!

 

[Windows Phone 8.1] Trio de ases: Behaviors, Animations y VisualStates (2)

En el artículo anterior de esta serie hablamos sobre el behavior SDK, una herramienta que nos permitía realizar ciertas acciones directamente en XAML y sin necesidad de escribir código C# en respuesta a cambios de datos o lanzamiento de eventos. En este, vamos a ver otra herramienta que XAML pone a nuestra disposición: las animaciones.

En todo desarrollo existe algo en lo que debemos poner toda nuestra atención: fluidez. Que nuestra aplicación se comporte rápido y de forma fluida es muy importante. Pero muchas veces, podemos dividir la fluidez en dos conceptos: fluidez real y fluidez percibida. Y contrariamente a lo que pudiésemos pensar, es la segunda, la fluidez percibida, la más importante. Por su puesto que es muy importante que nuestra aplicación sea fluida y rápida. Pero siempre existirán puntos, como ciertos procesos de carga, que serán inevitables. Si aderezamos esos momentos con una distracción para el usuario, desviando su atención del tiempo exacto que tardamos en cargar datos, conseguiremos que este perciba la aplicación como más fluida y rápida de lo que realmente es.

Pongamos un ejemplo. Imaginad que tras cargar la página principal de nuestra aplicación y mostrarla en pantalla debemos llamar a un servicio web para obtener ciertos datos necesarios. aquí el truco para dar fluidez a la aplicación es muy sencillo: Primero mostramos la página con todo el contenido estático que podamos, para a continuación en otro hilo cargar los datos del servicio. La página principal se carga rápido, la interface no se bloquea. Podemos decir que nuestra aplicación es fluida. Pero realmente, existe un ligero desfase, en el que el usuario ve la barra de carga de la aplicación, quizás durante 500 milisegundos o un segundo. Aunque se ha cargado todo sin bloquear nada, el usuario ha tenido que esperar casi un segundo para poder empezar a usar nuestra aplicación. Bien, si sabemos que ese tiempo oscila entre 500 y 1000 milisegundos, aprovechémoslo. Por ejemplo podríamos hacer que al cargarse la pantalla, los elementos estáticos en vez de aparecer ya posicionados, vayan apareciendo en una sucesión, tener una imagen como la splash screen sobre la página que animemos para que desaparezca con un fade out, o algo por el estilo. Si ajustamos esta animación a 750 milisegundos por ejemplo, por debajo podemos al mismo tiempo pedir los datos al servicio. El resultado es que: En el mejor de los casos cuando la animación acabe la página estará operativa. En el peor de los casos quizás el usuario tendrá que esperar unos 250 milisegundos a obtener todo. Pero fijaros que hemos pasado de una espera de 500 a 1000 milisegundos a otra de entre 0 y 250 milisegundos. No hemos hecho que nuestra aplicación sea más rápida, pero si hemos mejorado la fluidez percibida. Todo esto gracias además a que las animaciones en XAML son totalmente asíncronas, mientras no bloqueemos el hilo de interface de usuario, podemos ejecutar código al mismo tiempo que lanzamos una animación. ¿Qué os parece el truco?

Además de esta utilidad “práctica”, las animaciones también cumplen un papel claramente estético. usar una pequeña y simple animación para mostrar un elemento o resaltarlo, como veremos hoy, pueden mejorar el aspecto de nuestra aplicación muchísimo. Así que empecemos a meternos en faena, viendo como crear animaciones en Windows Phone 8.1, Windows 8.1 y apps universales.

Storyboard, Animations y KeyFrames.

Toda animación en XAML se encuadra dentro de un elemento Storyboard. El elemento Storyboard define la duración, el auto rebobinado, el comportamiento de repetición y más cosas mediante varias propiedades:

  • Duration. Nos permite expresar el tiempo como un TimeSpan (hh:mm:ss.ms) se trata del tiempo total que va a durar la animación en completarse.
  • BeginTime. Expresa, también como un TimeSpan, el tiempo a esperar, una vez comenzada la ejecución de la animación, antes de ejecutar el primer frame. Es muy útil si queremos lanzar y sincronizar o encadenar distintas animaciones.
  • FillBehavior. Aporta dos opciones: HoldEnd y Stop. Indica el comportamiento a llevar a cabo al terminar la animación. HoldEnd es el valor por defecto y mantiene la animación en el último cuadro de la misma. Stop para la animación y vuelve al estado inicial.
  • AutoReverse: indica si al terminar se debe ejecutar la animación al revés automáticamente.
  • RepeatBehavior: Permite especificar el comportamiento de repetición: Puede contener un número que indica el número de veces que se repetirá la animación, 0 para nunca, o la palabra Forever, que indica que nunca dejará de repetirse a no ser que la paremos explícitamente.
  • SpeedRatio. La velocidad de reproducción. Podemos considerarlo como un avance rápido de la animación.

La clase Storyboard también contiene dos métodos: Begin y Pause, que nos permiten comenzar o parar la reproducción de la animación. Así mismo dispondremos del evento Completed para saber cuando ha terminado. En XAML, Un Storyboard con una duración de 2 segundos, 1 repetición y auto rebobinado tendría este aspecto:

Sample storyboard
<Storyboard x:Key="SampleStoryboard" RepeatBehavior="1" Duration="0:0:2" AutoReverse="True">
</Storyboard>

Al declarar un Storyboard usamos el identificador x:Key. Esto se debe a que una Storyboard es un recurso, ya sea de la aplicación, de la página o de un control.

Lo siguiente que tenemos que hacer es definir la colección de animaciones a usar en el Storyboard. Disponemos de diferentes tipos de animaciones, dependiendo del tipo de la propeidad que deseemos modificar. De cada tipo existen dos variantes: Una variante simple, que nos permite indicar un valor final y una duración y una variante más completa que nos permite definir una colección de cuadros para la animación:

  • DoubleAnimation / DoubleAnimationUsingKeyFrames, para animar propiedades numéricas.
  • ColorAnimation / ColorAnimationUsingKeyFrames, para animar propiedades basadas en colores.
  • PointAnimation / PointAnimationUsingKeyFrames, para animar propiedades basadas en puntos.
  • ObjectAnimationUsingKeyFrames, para animar cualquier otro tipo de propiedad.

Dentro de la animación usaremos las propiedades Storyboard.TargetName y Storyboard.Property para definir el elemento y la propiedad que deseamos animar. En este sentido debemos tener en cuenta que, en un mismo Storyboard no podremos definir dos animaciones distintas para una misma propiedad de un mismo elemento. Esto no es problema puesto que al poder usar KeyFrames, dentro de una animación podemos definir tantos cambios como queramos. Por ejemplo podemos animar la propiedad Opacity de un elemento de la siguiente forma usando una DoubleAnimationUsingKeyFrames:

DoubleAnimationUsingKeyFrames
<Storyboard x:Key="SampleStoryboard" RepeatBehavior="1" Duration="0:0:2" AutoReverse="True">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="Opacity">
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

A continuación ya solo nos queda definir los frames de la animación. Cada frame es un instante en el tiempo de la animación en el que definimos un valor para la propiedad que estamos animando. Cada tipo de animación soporta cuatro tipos de frames:

  • Linear, que usa una interpolación lineal entre frames para animar la propiedad.
  • Discrete, que usa una interpolación discreta entre frames para animar la propiedad.
  • Easing, que usa una función de easing para modificar la interpolación entre frames.
  • Spline que usa un Spline para definir la interpolación entre frames.

Normalmente utilizaremos Lineal y Easing dependiendo de si queremos una animación uniforme o aplicar efectos de rebote, aceleración o deceleración entre cuadros. El uso es el mismo en ambos casos, debemos indicar el tiempo dentro del Storyboard en el que deseamos ejecutar cada frame y el valor que debe tener la propiedad animada en ese momento. A mayores, en el caso del Easing podremos indicar una función de easing para aplicar el efecto deseado:

 

LinearDoubleKeyFrame
<Storyboard x:Key="SampleStoryboard" RepeatBehavior="1" Duration="0:0:2" AutoReverse="True">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="Opacity">
        <LinearDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

 

 

EasingDoubleKeyFrame
<Storyboard x:Key="SampleStoryboard" RepeatBehavior="1" Duration="0:0:2" AutoReverse="True">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="Opacity">
        <EasingDoubleKeyFrame KeyTime="0:0:2" Value="1">
            <EasingDoubleKeyFrame.EasingFunction>
                <CubicEase EasingMode="EaseOut"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

 

Y ya tenemos lista nuestra animación. En ambos casos animamos la propiedad Opacity de un elemento llamado ScoreCard hasta un valor de 1. No indicamos el valor inicial de la propiedad, así la animación tomará como valor inicial el que tenga la propiedad en el momento en que se ejecute la animación. En el caso del EasingDoubleKeyFrame además definimos la función de easing, un Cubic ease en modo out. Esto hace que la animación haga un efecto de aceleración.

Como hemos mencionado antes, dentro de un Storyboard podemos definir distintas animaciones para conseguir efectos más complejos. Incluso podemos combinar distintas animaciones (jugando con la propiedad BeginTime). En el ejemplo adjunto a este artículo tenemos una animación que cambia el color y la escala de un TextBlock con un número y además muestra unos rayos girando a toda pantalla:

image

Los rayos no aparecen de golpe, se anima el tamaño y la opacidad para que aparezcan expandiéndose y luego girando. El código de la animación completa es el siguiente:

Rays animation
<Page.Resources>
    <Storyboard x:Key="ShowSuccess">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Rays" Storyboard.TargetProperty="Opacity">
            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value=".5">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Rays" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="15">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Rays" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="15">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1.2">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1.2">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScoreCard" Storyboard.TargetProperty="Opacity">
            <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1">
                <EasingDoubleKeyFrame.EasingFunction>
                    <CubicEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="AnimateRays" RepeatBehavior="Forever" BeginTime="0:0:0.1" FillBehavior="HoldEnd" >
        <DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetName="Rays" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)">
            <LinearDoubleKeyFrame KeyTime="0:0:12" Value="500"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

Quizás lo más extraño de esta animación es la forma de definir las propiedades. en vez de usar una propiedad del elemento estamos usando una transformación para poder rotarlo o escalarlo, por eso la animación se define como (UIElement.RenderTransform).(CompositeTransform.ScaleY):

  • (UIElement.RenderTransform) Aquí indicamos que de un UIElement (Todos los elementos en XAML heredan de la clase base UIElement), deseamos acceder a su propiedad RenderTransform.
  • (CompositeTransfrom.ScaleY) indicamos que dentro de la propiedad RenderTransform tenemos definido un CompositeTransform y que deseamos animar la propiedad ScaleY del composite transform.

En el TextBlock ScoreCard, definimos el RenderTransform de la siguiente forma:

CompositeTransform
<TextBlock x:Name="ScoreCard" Text="{Binding ScoreValue, Mode=TwoWay}" FontSize="64" Width="130" Height="70"
           TextAlignment="Center" FontWeight="Bold" Grid.Row="1" Opacity=".5" VerticalAlignment="Center"
           HorizontalAlignment="Center">
    <TextBlock.RenderTransform>
        <CompositeTransform CenterX="65" CenterY="35" ScaleX="1" ScaleY="1"/>
    </TextBlock.RenderTransform>
</TextBlock>

Por último, solo nos queda iniciar las animaciones en el momento justo. Aquí es donde entran en juego los behaviors que explicamos en el artículo anterior. En este caso podemos hacer uso de la acción ControlStoryboardAction del namespace Microsoft.Xaml.Interaction.Media. Como disparador, podemos usar el click de un botón:

ControlStoyboardAction (1)
<Button Content="play">
    <i:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="Tapped">
            <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource ShowSuccess}"/>
            <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource AnimateRays}"/>
        </core:EventTriggerBehavior>
    </i:Interaction.Behaviors>
</Button>

Simplemente indicamos el evento, Tapped, y lo que queremos hacer con cada Storyboard (Play, Pause, Stop…) y por último el Storyboard afectado y listo! Pero pensemos por un momento, que queremos mostrar esta animación cuando el usuario completa el 100% de una tarea o llega a un hito concreto en nuestra aplicación. Para esto, podemos usar el disparador basado en datos (DataTiggerBehavior). Lo podemos definir por ejemplo en el TextBlock ScoreCard:

ControlStoryboardAction(2)
<TextBlock x:Name="ScoreCard" Text="{Binding ScoreValue, Mode=TwoWay}" FontSize="64" Width="130" Height="70"
           TextAlignment="Center" FontWeight="Bold" Grid.Row="1" Opacity=".5" VerticalAlignment="Center"
           HorizontalAlignment="Center">
    <TextBlock.RenderTransform>
        <CompositeTransform CenterX="65" CenterY="35" ScaleX="1" ScaleY="1"/>
    </TextBlock.RenderTransform>
    <i:Interaction.Behaviors>
        <core:DataTriggerBehavior Binding="{Binding ScoreValue}" ComparisonCondition="GreaterThanOrEqual" Value="100">
            <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource ShowSuccess}"/>
            <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource AnimateRays}"/>
        </core:DataTriggerBehavior>
    </i:Interaction.Behaviors>
</TextBlock>

En este caso, indicamos al DataTriggerBehavior la propiedad que nos interesa de nuestra ViewModel (ScoreValue), la condición de comparación que debe cumplir (GreaterThanOrEqual) y el valor contra el que compararlo (100 en este caso).

En nuestra ViewModel, simplemente incrementamos la propiedad y notificamos el cambio:

ScoreValue
public int ScoreValue
{
    get { return this.scoreValue; }
    set
    {
        this.scoreValue = value;
        RaisePropertyChanged();
    }
}

Sin tener que escribir ningún código extra, cuando la propiedad ScoreValue llegue a 100, se ejecutará la animación ShowSuccess y AnimateRays. Como podemos observar, este último método nos ofrece la gran ventaja de hacer que nuestras vistas sean más ricas y reaccionen de forma animada a los cambios de los datos de nuestras ViewModels, abriendo un gran abanico de posibilidades para nuestra UI.

Por supuesto, algo a tener muy en cuenta es que, al igual que pasa con los behaviors, el sistema de animación es totalmente universal y compartido entre Windows phone y Windows store. De echo, todo el código de este artículo está compartido en un proyecto universal: la página, las animaciones y las viewmodels. No hay ni una sola línea de código C# o XAML en los proyectos específicos de la plataforma.

Y hasta aquí llega este segundo artículo. En el próximo colocaremos la última pieza de este trio de ases, los VisualStates, que nos permitirán crear páginas totalmente adaptables al momento, datos, situación… Pero para eso todavía os haré esperar un poco más. Mientras tanto, podéis descargaros de aquí el código completo y funcional de este artículo y ejecutarlo para ver lo bien que quedan las animaciones y lo sencilla que es hacerlas.

Un saludo y Happy Coding!!

[Windows Phone 8.1] Trio de ases: Behaviors, Animations y VisualStates (1)

Hola a todos!

Hoy vamos a comenzar a dar un repaso a un trio clásico de XAML que nos ayudará a crear mejores apps tanto para Windows Phone 8.1 como para Windows Store y Universal Apps. Hablo sin duda del equipo formado por los behaviors, las animaciones y los visual states.

Behaviors

Los behaviors (o comportamientos, si queréis traducirlo) nos permiten agregar cierta inteligencia a nuestro XAML para que realice acciones sin tener que escribir código para ello. Con Windows 8.1 se introdujo una extensión llamada Behaviors SDK, que ahora tenemos disponible también en Windows Phone 8.1, exactamente con la misma funcionalidad.

Podríamos dividir su uso en dos grandes bloques. Por un lado tenemos los triggers, que nos permiten definir la situación bajo la cual nuestros behaviors deben ser ejecutados. Por otro tenemos los behaviors y las activities propiamente dichos, que nos permiten definir qué hacer cuando se active el trigger.

En cuanto a triggers, tenemos los siguientes tipos:

  • DataTriggerBehavior. Este trigger nos permite ejecutar behaviors basándonos en datos para definir cuando comenzar la ejecución. Dispone de varias propiedades para definir el momento del lanzamiento:
    • Binding: Nos permite definir un enlace a una propiedad de otro objeto/ViewModel para usar su valor como base de la ejecución.
    • ComparisonCondition: Se trata de un enumerado en el que podemos definir como observar el valor de binding: Equal, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual y NotEqual.
    • Value: el valor contra el que comparar el Binding.
  • EventTriggerBehavior. Nos permite definir un evento, que al lanzarse, ejecutará nuestros behaviors. Dispone de una propiedad EventName, donde podremos definir el nombre del evento que queremos escuchar y SourceObject, donde podremos definir el objeto de origen del evento, en caso de que sea distinto del objeto que contiene al trigger.

Y por el lado de los behaviors, encontramos los siguientes:

  • IncrementalUpdateBehavior. Un gran desconocido pero muy útil, sobre todo si trabajamos con grandes listas. Dentro de un DataTemplate de un ListView o GridView, nos permite definir diferentes “fases” de carga de los controles que lo componen. De esta forma, al hacer scroll, la carga de los diferentes elementos y su visualización se realizará en secuencia. Por ejemplo, podemos tener una DataTemplate que muestre un texto y una foto. Con este behavior, en su propiedad Phase, podemos  establecer la fase 1  al texto y la 2 a la imagen. Al hacer scroll, la lista mostrará primero los textos y a continuación cargará la imagen, lo que nos dará mayor fluided.
  • CallMethodAction. Como su nombre bien indica, nos permite ejecutar un método. Contiene una propiedad MethodName, donde podemos definir el nombre del método a ejecutar y una propiedad TargetObject, donde podremos definir un enlace a datos al objeto que contiene el método. Importante tener en cuenta que el método debe ser público para poder ejecutarlo.
  • GoToStateAction. Nos permite cambiar nuestro VisualState actual, definiendo el nombre del estado nuevo en la propiedad StateName, el objeto que lo contiene en la propiedad TargetObject y si deseamos usar transiciones entre el estado actual y el nuevo, con la propiedad UseTransitions. Más adelante cuando examinemos los VisualStates, veremos la potencia de este behavior.
  • InvokeCommandAction. Ejecuta el comando indicado. Disponemos de una propiedad Command, donde podemos definir el comando al igual que haríamos en un elemento Button y CommandParameter, que nos permite definir un parámetro.
  • NavigateToPageAction. Nos permite iniciar la navegación a una página. En la propiedad TargetPage debemos indicar el nombre cualificado completo de la clase de la página a la que queremos navegar, mientras que la propiedad Parameter nos permite indicar un parámetro que se enviará a dicha página.
  • ControlStoryboardAction. Como su nombre bien indica, este behavior nos ayudará a controlar una Storyboard, que como veremos más adelante es la base de cualquier animación en XAML. Dispone de una propiedad ControlStoryboardOption, donde podremos definir el tipo de acción que deseamos realizar: Play, Pause, Resume, SkipToFill, Stop, TogglePlayPause… También disponemos de la propiedad Storyboard, donde podremos indicar la Storyboard que deseamos controlar.
  • PlaySoundAction. Reproducción de sonido, dispone de una propiedad Source, donde definiremos el path del archivo de sonido a reproducir y una propiedad Volume, donde podremos definir el volumen de reproducción.

Como podemos ver, si combinamos los distintos tipos de behaviors con los tiggers, disponemos de una amplia gama de acciones a realizar bajo diferentes condiciones sin necesidad de añadir código C#. Podemos poner como ejemplo:

  • Ejecutar una animación cuando una propiedad boolean de nuestra ViewModel sea True.
  • Navegar a una página distinta cuando el usuario haga click en una imagen.
  • Reproducir un sonido al cambiar el elemento seleccionado de una lista.
  • Invocar a un comando o un método cuando cierto número de elementos haya sido cargado.
  • Cambiar el VisualState en respuesta a un cambio en los datos de nuestra ViewModel.

Bien, ¿Y como se usan? Primero debemos añadir una referencia al Behavior SDK que se incluye por defecto para Windows Phone 8.1 y Windows Store 8.1. Para ello solo tendremos que pulsar el botón derecho sobre nuestro proyecto, seleccionar Add Reference e ir al nodo de Extensions y marcar la extensión “Behaviors SDK (XAML)” como podemos ver en la imagen a continuación:

image

Una vez hecho esto, tendremos que añadir los namespaces Microsoft.Xaml.Interactivity, Microsoft.Xaml.Interaction.Core y Microsoft.Xaml.Interaction.Media a nuestra página o user control:

Namespaces
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:media="using:Microsoft.Xaml.Interactions.Media"

Y ya podremos empezar a usar los behaviors. Por ejemplo, Si tenemos dos paginas en nuestro proyecto, MainPage y SecondaryPage, podemos usar el behavior NavigateToPageAction en conjunción con el trigger EventTriggerBehavior para navegar de MainPage a SecondaryPage al hacer tap sobre una imagen:

NavigateToPageAction
<Grid>
    <Image Source="/assets/picture.jpg" Width="300">
        <i:Interaction.Behaviors>
            <core:EventTriggerBehavior EventName="Tapped">
                <core:NavigateToPageAction TargetPage="BehaviorsSample.SecondaryPage"/>
            </core:EventTriggerBehavior>
        </i:Interaction.Behaviors>
    </Image>
</Grid>

La utilización es muy simple y siempre igual para todos los behaviors. Dentro de un elemento definimos un hijo de tipo Interaction.Behaviors, que se encuentra en el namespace Microsoft.Xaml.Interactivity, a continuación el trigger a usar, definido en Microsoft.Xaml.Interactions.Core y por último la acción a realizar por el behavior.

Además, como son exactamente iguales en Windows Store y Windows Phone, podemos compartir el XAML donde los usemos en la carpeta shared de un proyecto universal sin problemas.

En el siguiente artículo de la serie, veremos las animaciones y volveremos a hablar de los behaviors y los triggers, como una fantástica forma de lanzar animaciones basándonos en datos y eventos, sin tener que escribir código extra para manejarlas.

Por ahora, aquí podréis descargar el ejemplo de hoy para que empecéis a practicar con los behaviors y ver la potencia que encierran. Como en el ejemplo, ser capaces de integrar en vuestras aplicaciones navegación, basada en eventos, sin tener que crear un servicio de navegación, comandos ni eventos.

Un saludo y Happy Coding!

[Windows Phone 8.1] WebAuthenticationBroker

Hola a todos!

Después de unos días sin escribir y de que se presentase oficialmente la RTM del SDK de Windows Phone 8.1, volvemos a la carga con novedades en las APIs de Windows Phone 8.1. En esta ocasión quiero hablar del WebAuthenticationBroker. Los que hayáis trabajado con apps Windows Store, lo conoceréis sin duda. Para el resto, aquí va una introducción.

¿Qué es el WebAuthenticationBroker?

El WebAuthenticationBroker es una clase pensada para facilitarnos la vida como desarrolladores a la hora de usar sistemas de autenticación OAuth de cualquier tipo. Facebook, Twitter, LinkedIn… casi toda empresa que expone APIs públicas usa OAuth como sistema de identificación. Básicamente este sistema nos permite registrar nuestra app en él y obtener unos identificadores para la misma. Luego, en tiempo de ejecución nuestra aplicación abre una web del sistema, enviando los identificadores recibidos a la misma y en la que se pide al usuario que introduzca su nombre y clave para autorizar a nuestra aplicación a acceder a su cuenta. Lo bueno de esto es que el usuario no introduce sus datos en nuestra aplicación, lo hace en una web del creador del API, por lo que su confianza debería ser mayor. Al final, nuestra aplicación recibe un token, normalmente con un tiempo de vida predeterminado, que podemos usar para autentificar las llamadas al API que realicemos. Además, el usuario puede revocarnos el acceso desde la administración del API en cualquier momento. A continuación os muestro un esquema, OAuth tiene un funcionamiento interno que en este esquema simplifico para enseñar como se relaciona con nuestra aplicación, no como funciona internamente que está fuera del alcance de este artículo:

image

Como podemos ver, simplemente hacemos una petición a una URL OAuth. En realidad navegamos a esa URL, lo cual muestra la web de autenticación. En el ejemplo he puesto una de las webs de autenticación que usa Facebook. En esa web, el usuario puede permitirnos el acceso a su cuenta. Si funciona correctamente, la web nos redirigirá a una URL de respuesta configurada en el servicio al registrar nuestra aplicación. En esa redirección, suele enviarse como parámetro el token de acceso a la API. Y listo, ya podemos realizar peticiones autenticadas con el token que hemos recibido.

¿Como implementamos esto antes de Windows Phone 8.1?

Pues muy a “mano”. Básicamente, siguiendo los siguientes pasos:

  • Construías en nuestra aplicación una página que contuviese un WebBrowser.
  • Si no estamos autenticados (no tenemos un token) navegar a la URL de OAuth en esa página
  • Vigilar los eventos de navegación del WebBrowser para detectar cuando esta redirigiéndonos a la respuesta.
  • Procesar la URL de respuesta para extraer el token.

No es que sea el proceso más complicado del mundo, pero es todo muy manual y puede llevarnos un rato de prueba/error si queremos que funcione con varios proveedores distintos al mismo tiempo.

¿Como implementamos esto ahora en Windows Phone 8.1?

Lo implementamos usando el WebAuthenticationBroker, que nos facilita la vida:

  • Si no estamos autenticados (no tenemos un token) llamamos al método AuthenticateAndContinue de la clase WebAuthenticationBroker.
  • Recibimos la respuesta y extraemos el token.

Si, ya está. Solo tenemos que hacer eso. El propio WebAuthenticationBroker se encarga por nosotros de todos los demás pasos. Esta fue una de las grandes novedades que incluyó Windows 8 en su API WinRT cuando salió y por fin la tenemos disponible en Windows Phone. No obstante existen ciertas diferencias entre el API de Windows 8 y la de Windows Phone 8.1, como veremos a continuación.

WebAuthenticationBroker: Windows Store vs Windows Phone

Como hemos dicho, solo tenemos que llamar al método AuthenticateAndContinue de la clase WebAuthenticationBroker, pero este método no existe en Windows Store, donde usaremos en su lugar AuthenticateAsync. Esta diferencia, que encontraremos en algunas APIs más de Windows Phone 8.1, va mas allá de un simple cambio de nombre. AuthenticateAsync es, como su nombre indica asíncrono, podemos usar await para esperar que se complete y nos de el resultado. Por el contrario, AuthenticateAndContinue no es asíncrono y devuelve ¡VOID!, ahora veremos porqué y como obtener el resultado.

El problema principal es el siguiente. En terminales de baja memoria, 512Mb, Windows Phone no puede asegurar en todo momento que pueda mantener nuestra aplicación abierta al mismo tiempo que lanza otra aplicación, la cual se encargará de realizar la autenticación. Por este motivo, ciertas APIs que necesitan abrir un “Broker”, una aplicación del sistema para realizar una acción, como el WebAuthenticationBroker, han implementado un método llamado “DoSomethingAndContinue” que no devuelve ningún resultado. En su lugar seguiremos el siguiente flujo:

image

Básicamente tenemos los siguientes pasos:

  1. Nuestra aplicación ejecuta el método AuthenticateAndContinue del WebAuthenticationBroker. En esta
  2. La aplicación WebAuhtenticationBroker del sistema pasa a primer plano y nuestra app se suspende. Dependerá del sistema si se conserva en memoria o no.
  3. El usuario realiza las acciones necesarias y el WebAuthenticationBroker termina, activando nuestra app de nuevo.
  4. Nuestra app pasa al primer plano, recibiendo como argumento un objeto de tipo AuthenticationResult.
  5. Tenemos que enviar la respuesta a nuestra página/viewmodel para seguir trabajando.

Viendo el código.

Ahora que tenemos claro como funciona el proceso, vamos a ver el código implicado en él. Para el primer paso, necesitaremos una URL de autenticación OAuth que podamos usar. En este ejemplo vamos a usar una de Facebook. Para obtener una URL, tendremos que ir a https://developers.facebook.com/, registrarnos y crear una nueva app en el menú superior “Apps”:

image

Una vez creada, nos mostrará la página de la app con el AppID y el AppSecret, estos identificadores serán necesarios para componer nuestra URL de conexión a continuación:

https://www.facebook.com/dialog/oauth?client_id=[APPID]&redirect_uri=http://localhost/reply&scope=[PERMISOS]&display=popup&response_type=token

Simplemente tenemos que sustituir el texto [APPID] por el appID que nos dio Facebook al registrar la aplicación y añadir los permisos necesarios, según las acciones que deseemos realizar.

El código sería algo como esto:

AuthenticateAndContinue
private void LoginFacebookExecute()
{
    string appID = "YOUR APP ID FROM FACEBOOK";
    string permissions = "publish_actions";
    string authUrl = string.Format("https://www.facebook.com/dialog/oauth?client_id={0}&redirect_uri=http://localhost/reply&scope={1}&display=popup&response_type=token", appID, permissions);
            
    WebAuthenticationBroker.AuthenticateAndContinue(new Uri(authUrl), new Uri("http://localhost/reply"), null, WebAuthenticationOptions.None);
}

Como vemos no es un código complicado. formamos la URL de OAuth2 de Facebook añadiendo nuestro APPID y los permisos a usar y a continuación llamamos al método AuthenticateAndContinue. Es en esta parte donde viene lo divertido. Justo tras llamar a ese método, nuestra aplicación se suspende y se abre el WebAuthenticationBroker. ¿Y como obtenemos el resultado de la operación? Bien, controlando el método OnActivated de la clase App (App.xaml.cs) Desde ese método deberemos pasar la información a nuestra ViewModel. Para hacer esto más sencillo, yo he creado un Interface que añado a todas las ViewModels que hacen uso de métodos “…AndContinue”. Esta interface se llama IContinuationAwareViewModel y define el método Continue:

IContinuationAwareViewModel
public interface IContinuationAwareViewModel
{
    void Continue(IActivatedEventArgs args);
}

Simplemente la implemento en la ViewModel donde hemos llamado a AuthenticateAndContinue y dentro del método Continue pongo el código para recibir y procesar la respuesta de Facebook:

Continue
public void Continue(IActivatedEventArgs args)
{
    var arguments = (WebAuthenticationBrokerContinuationEventArgs)args;
    WebAuthenticationResult result = arguments.WebAuthenticationResult;

    if (result.ResponseStatus == WebAuthenticationStatus.Success)
    {
        string responseCode = result.ResponseData;
        responseCode = responseCode.Substring(responseCode.IndexOf("=") + 1);
        AccessToken = responseCode.Split('&').First();
        ExpirationTime = int.Parse(responseCode.Split('&').Last().Split('=').Last());
    }
}

Comprobamos si la operación a tenido éxito y simplemente procesamos la cadena de texto que devuelve Facebook con el token de acceso y el tiempo de expiración en segundos.

Ahora ya solo nos queda ver como invocar a este método desde el OnActivated de la clase App:

App OnActivated
protected override void OnActivated(IActivatedEventArgs args)
{
    var rootFrame = (Frame)Window.Current.Content;
    var currentPage = (Page)rootFrame.Content;
    var currentViewModel = (IContinuationAwareViewModel)currentPage.DataContext;

    currentViewModel.Continue(args);
}

Primero obtenemos el frame actual de la aplicación, del cual extraemos la página activa. Del DataContext de la página extraemos la ViewModel (que convertimos a IContinuationAwareViewModel) y ya solo nos queda invocar al método Continue pasando los argumentos de activación. Es tarea del método Continue implementado en la ViewModel decidir como usar esos argumentos y que información obtener. De esta forma, si tenemos varias páginas que usen métodos “…AndContinue” el método de activación será standard para todas ellas.

Y con esto llegamos al final de esta artículo. Como siempre, aquí tienes el ejemplo funcionando con el código que hemos visto, para que puedas jugar con él y por supuesto usando MVVM para que veas como implementar WebAuthenticationBroker con todas las piezas en su sitio.

Espero que lo disfrutes.

Un saludo y Happy Coding!

 

 

[Windows Phone] Este viernes 16 de mayo llega //Publish

Hola a todos!

Después de mucho tiempo de preparación, ya está aquí el 16 de mayo y el evento //Publish que se celebrará a nivel mundial en más de 60 ciudades al mismo tiempo.

En Madrid, las inscripciones ya están cerradas y tenemos todo preparado para pasar un gran día juntos, desarrollando apps y con muchas sorpresas.

El día empezará a las 9 de la mañana con el registro general. Según vayáis llegando, empezaréis a desarrollar. Hemos decidido no hacer charlas técnicas ese día. Estaremos varios por allí para ayudaros con lo que necesitéis, pero el objetivo es desarrollar y no queremos distraeros. a la 1 de la tarde pararemos una hora para comer y luego seguiremos hasta las 6 y media de la tarde desarrollando. A las 6:30 pararemos, para que cada cual presente sus apps a los demás y el jurado, compuesto por Javier Suarez Ruiz, Santiago Porras y Alejandro Campos, deliberará sobre las mejores apps. Entre ellas escogeremos dos: La primera podrá escoger como premio entre un Lumia 1520 y una tablet Dell Venue 8 Pro. El premio restante será para el segundo. Pero no os preocupéis que tendremos sorpresas y regalos parar todos!!.

¿Vas a venir? ¿Qué tienes que traer para poder aprovechar bien el día?

  • Lo principal: muchas ganas de desarrollar, aprender y sacar una app al público.
  • Como solo tenemos un día, lo ideal es que ya traigas algo desarrollado de casa, pero si vienes con una idea, haremos lo posible para que te vayas con mucho hecho y funcionando… incluso puede que con la aplicación publicada en la tienda.
  • Recuerda traer tu portatil, con el software necesario instalado. Si tienes que instalarlo sobre la marcha te ayudaremos, pero perderas un valioso tiempo instalando. Necesitarás al menos:
    • Windows 8.1 Pro o superior de 64 bits
    • SDK de Windows Phone 8.1

Si tu idea es desarrollar una app Windows Phone 8.1, tendremos varios terminales para que la puedas probar (un Lumia 1520 y un Lumia 625 listos con Windows Phone 8.1).

Para ayudarte, tendrás un monton de gente que controla de Windows Phone:

  • Javier Suarez Ruiz
  • Santiago Porras
  • Alejandro Campos
  • Un servidor

Además, Santiago llegará al evento con la gorra y bufanda de diseñador, listo para daros un apoyo en el diseño de vuestra app para sacarle el máximo partido y que tenga la mejor apariencia posible.

Por último, recordaros que el evento se celebrará en las oficinas de Microsoft Ibérica:

 

Asignar imagen

 

Paseo Club Deportivo, número 1.

Centro Empresarial La Finca.

Un saludo y Happy Coding!

 

[Windows Phone 8.1] MVVM en apps universales.

Hola a todos!

Una de las grandes novedades, como ya vimos anteriormente, en la nueva versión de Windows Phone 8.1 son las aplicaciones universales. Este nuevo tipo de proyecto nos promete ser capaces de compartir la gran mayoría de código entre una versión Windows Store y otra Windows Phone de la misma aplicación. ¿Pero realmente es así? Antes de empezar a repasar nuevas APIs, XAML y otras cosas, creo que es importante ver como podemos usar MVVM en nuestras aplicaciones universales para compartir gran parte del código.

Implementando MVVM en proyectos universales

Realmente la implementación de MVVM no cambia en absoluto, con respecto a la que podríamos hacer en Windows Phone 8.0 o Windows Store. Lo único que cambia es el proyecto donde colocamos nuestros archivos.

Mientras que anteriormente implementábamos MVVM en el propio proyecto de la aplicación o una PCL, en las apps universales usaremos el proyecto Shared para almacenar nuestros archivos. El objetivo es llegar a tener solo el XAML de las páginas en cada proyecto de plataforma y que toda la lógica de la aplicación esté centralizada en el proyecto Shared. ¿Es esto posible? Si en la mayoría de los casos. Siempre nos encontraremos con casos en que las APIs de Phone y Store no son exactamente iguales, incluso donde su valor de retorno no es el mismo. En estos casos tendremos dos opciones: Crear una ViewModel específica para cada proyecto o usar bloques condicionales de compilación. ¿Cual usar en cada caso? Pues dependerá un poco de nuestras necesidades particulares en cada momento y de lo que necesitemos hacer. Si es poco el código particular de cada plataforma y mucho el código compartido, quizás usar un bloque condicional nos ayude a compartir código y no tener que reescribirlo todo. Sin embargo, si el código particular es la mayoría de la ViewModel, quizás sea mejor crear una por cada plataforma.

Vamos a comenzar por las bases. En todo proyecto XAML con MVVM, me gusta crear dos clases base: PageBase y ViewModelBase.

PageBase

PageBase la usaré como clase base de todas mis páginas. Esto me permite varias cosas:

  • Definir comportamientos comunes a todas las páginas, como detección de internet.
  • Definición de transiciones comunes.
  • En conjunto con la ViewModelBase, “rutear” los metodos de navegación hacia las ViewModels.
  • Establecer en la ViewModelBase el Frame activo de cada página, de forma que sea más facilmente accesible.

Con esta funcionalidad, la clase PageBase tendría este aspecto:

PageBase
public class PageBase : Page
{
    private ViewModelBase vm;

    public PageBase()
    {
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);

        vm = (ViewModelBase)this.DataContext;
        vm.SetAppFrame(this.Frame);
        vm.OnNavigatedTo(e);
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
        vm.OnNavigatedFrom(e);
    }

    protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
    {
        base.OnNavigatingFrom(e);
        vm.OnNavigatingFrom(e);
    }
}

Aquí podemos observar una de las ventajas de Windows Phone 8.1 y las aplicaciones universales. Ya no tenemos un objeto PhoneApplicationPage. Ahora todas las páginas, tanto de Windows Phone como Windows Store, heredan del objeto Page. Esto permite que de igual forma, nuestra página base pueda ser compartida entre las dos plataformas.

Podéis ver que el código no es nada de otro mundo, controlamos los métodos de navegación y en el método OnNavigateTo obtenemos la ViewModel asignada a la página y establecemos el Frame activo en la misma. Por último en todos los métodos de navegación invocamos un método que se llama igual en la ViewModelBase. De esta forma no necesitamos usar code behind para controlar la navegación de nuestras páginas.

ViewModelBase

Nuestra ViewModelBase actuará como la clase base de las cuales heredarán todas nuestras ViewModels, lo que nos permitirá darles una funcionalidad básica y homogénea a todas:

  • Acceso al objeto Frame de la página asociada.
  • Acceso a los eventos de navegación para sobre escribirlos.
  • Notificación de cambios.
  • Notificación de carga a la UI.

El código de la ViewModelBase, sería parecido a este:

ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
    private Frame appFrame;
    private bool isBusy;

    public ViewModelBase()
    {
    }

    public Frame AppFrame
    {
        get { return appFrame; }
    }

    public bool IsBusy
    {
        get { return isBusy; }
        set
        {
            isBusy = value;
            RaisePropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public abstract Task OnNavigatedFrom(NavigationEventArgs args);

    public abstract Task OnNavigatingFrom(NavigatingCancelEventArgs args);

    public abstract Task OnNavigatedTo(NavigationEventArgs args);

    public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
    {
        var Handler = PropertyChanged;
        if (Handler != null)
            Handler(this, new PropertyChangedEventArgs(propertyName));
    }

    internal void SetAppFrame(Frame viewFrame)
    {
        appFrame = viewFrame;
    }
}

Hemos creado tres métodos abstractos: OnNavigatedFrom, OnNavigatingFrom y OnNavigatedTo que implementaremos en nuestras ViewModels para poder ejecutar código durante la navegación. Esto es muy útil cuando queremos recibir un parámetro de otra página, pero también al cargar datos. Si realizamos carga de datos en el constructor, podemos ralentizar la carga de la página. Sin embargo usando el método OnNavigatedTo, realizamos la carga una vez que la página está activa, no antes.

Ahora ya podemos empezar a crear nuestras páginas y ViewModels, usando las clases base que hemos definido.

Páginas

En cada página, tendremos que incluir el namespace de nuestra página base y cambiar su tipo, tanto en XAML como en C#:

 

MainPage.xaml
<base:PageBase
    x:Class="Wp81MVVM.MainPage"
    xmlns:base="using:Wp81MVVM.Base"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Wp81MVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    </Grid>
</base:PageBase>

 

 

MainPage.xaml.cs
namespace Wp81MVVM
{
    using Wp81MVVM.Base;

    public sealed partial class MainPage : PageBase
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}

 

Ahora solo tenemos que definir una ViewModel para esta página, implementando la ViewModelBase y sus métodos abstractos de navegación:

ViewModel
public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {

    }

    public override Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
    {
        return null;
    }

    public override Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
    {
        return null;
    }

    public override Task OnNavigatingFrom(Windows.UI.Xaml.Navigation.NavigatingCancelEventArgs args)
    {
        return null;
    }
}

Ya solo nos queda unir nuestra página y nuestra ViewModel, de la forma que más nos guste: usando IoC, asignándola directamente en el constructor de la página… En este caso voy a usar Unity para resolver nuestra ViewModel y servicios. Para ello, lo primero que necesitaremos es añadir una referencia en nuestro proyecto universal a Unity. En este tipo de proyectos esto significa añadir la referencia a cada proyecto de plataforma (Phone y Store). Vamos a comenzar por el proyecto Store. Solo tenemos que hacer click derecho sobre el nodo “References” del proyecto y seleccionar la opción “Manage NuGet packages”, buscar “Unity” y presionar el botón instalar:

image

Ahora deberíamos repetir lo mismo con el proyecto Phone, pero el resultado será el siguiente:

image

El problema aquí es que, en la mayoría de los casos, todavía no se han actualizado los paquetes de NuGet para soportar los tipos de proyecto “WindowsPhoneApp, version=8.1”. ¿Quiere decir esto que no podemos usar Unity? Ni mucho menos. Lo unico que quiere decir es que no podemos instalarlo desde NuGet. Como Windows Phone 8.1 y Windows Store comparten ahora todo el core de desarrollo, “en teoría” podríamos usar el mismo ensamblado que en Windows Store. Solo tenemos que hacer click con el botón derecho sobre “References” en el proyecto Windows Phone y seleccionar “Add Reference”, presionar el botón “Browse” e ir a la carpeta de packages donde se ha descargado Unity para Windows Store previamente, seleccionando la carpeta NetCore45, que es la usada en Windows Store, seleccionamos la dll dentro de esa carpeta y presionamos “add” y “ok” y listo, ya tendremos Unity funcionando en nuestra aplicación Windows Phone 8.1.

Ahora en nuestro proyecto Shared, en la carpeta Base de ViewModels, vamos a añadir una nueva clase llamada ViewModelLocator, que haga uso de Unity para registrar y resolver nuestras ViewModels, exponiéndolas mediante propiedades públicas:

ViewModelLocator
public class ViewModelLocator
{
    IUnityContainer container;

    public ViewModelLocator()
    {
        container = new UnityContainer();

        container.RegisterType<MainViewModel>();
    }

    public MainViewModel MainViewModel
    {
        get { return container.Resolve<MainViewModel>(); }
    }
}

Como podemos ver, desde nuestro proyecto Shared podemos acceder a cualquier ensamblado referenciado en nuestros proyectos de plataforma, haciendo muy sencillo escribir código compartido. Ya solo nos queda añadir nuestro ViewModelLocator como una referencia en el archivo App.xaml, que también se encuentra compartido en el proyecto Shared:

Shared App.xaml
<Application
    x:Class="Wp81MVVM.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Wp81MVVM"
    xmlns:VMBase="using:Wp81MVVM.Base">

    <Application.Resources>
        <VMBase:ViewModelLocator x:Key="Locator"/>
    </Application.Resources>
</Application>

Y ya lo podemos usar en las páginas de ambas plataformas. De la misma forma que haríamos en un proyecto de Phone o Store tradicional:

 

MainPage.xaml Windows Store
<base:PageBase
    x:Class="Wp81MVVM.MainPage"
    xmlns:base="using:Wp81MVVM.Base"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Wp81MVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    </Grid>
</base:PageBase>

 

 

 

Mainpage.xaml Windows Phone
<base:PageBase
    x:Class="Wp81MVVM.MainPage"
    xmlns:base="using:Wp81MVVM.Base"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Wp81MVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">

    <Grid>

    </Grid>
</base:PageBase>

 

En este punto, puede parecer que tener una sola página es mejor y compartirla en el proyecto Shared. Si, podríamos hacerlo sin ninguna dificultad. Pero en la mayoría de los casos, haría que en Phone o Store, la UX fuese horrible. En mi caso soy un firme defensor de hacer una interface de usuario para cada plataforma, aprovechándonos de sus puntos fuertes. Sin embargo, si que es posible que nos encontremos compartiendo pequeños trozos de interface en forma de controles de usuario y en muchos casos usando las diferentes páginas solo para modificar la distribución de la información en pantalla para aprovechar mejor el área de trabajo de cada plataforma. Esto da para un próximo artículo.

Conclusión

Esto es todo por hoy, como hemos podido ver es muy fácil compartir código con los proyectos universales, sin necesidad de hacks ni cosas extrañas (seguro que en algún momento nos hacen falta jeje) y nos permite construir apps para la plataforma Windows en conjunto. Como siempre, podéis descargar todo el código de ejemplo aquí para jugar con él y usarlo como punto de partida para vuestras apps.

Sería un buen arranque para una app que queráis presentar en el Hackathon //Publish del 16 de Mayo en Madrid, Barcelona o Palma de Mayorca. Premios, asistencia para el diseño de tu app, y mentores para ayudarte a crear el próximo hit!

Un saludo y Happy Coding!

 

[Eventos] Después del //Build/ llega //Learn/ y //Publish/

Hola a todos!

Solo hace unos días que termino el //Build/ 2014. Este año los seguidores de Windows Phone tuvimos grandes novedades y anuncios con la presentación de Windows Phone 8.1. Desde Microsoft Corp. y la comunidad de MVPs de Windows Phone, queremos ayudarte a seguir el ritmo de las novedades y crear grandes aplicaciones usando las nuevas ventajas que ofrece la plataforma Windows con esta última revisión.

Para ello, se han creado dos eventos que tendrán lugar en Abril y Mayo: //Learn/ y //Publish/

//Learn/

Este evento online está organizado directamente por los MVPs de Windows Phone. Durante 6 horas, tendremos webcast sobre diferentes temáticas de Windows Phone 8.1: aplicaciones universales, diseño, nuevas APIs, notificaciones… Lo mejor de todo, es que no solo será en Inglés. Dispondremos de una track principal en inglés y tracks paralelas en Chino, Ruso, Portugués, Frances y Español!!

En la Track en Español, Sorey García, Roberto Alvarado, Jose Luis Latorre, Ivan Toledo y un servidor estaremos dando todo el contenido. Las sesiones que tendremos serán las siguientes (Puede variar alguna todavía):

  • Introducción a Windows Phone 8.1
  • Empezando a construir apps Windows XAML
  • Controles de layout y transiciones de páginas
  • Adaptando tu UI a diferentes pantallas
  • Ciclo de vida de apps WinRT XAML
  • Tiles, badges, notificaciones y el Action center
  • Tareas en background
  • Windows XAML para desarrolladores Silverlight

El evento se celebrará el 24 de Abril, se pueden registrar aquí. Al ser un evento internacional los horarios han tenido que ser consensuados y España no sale demasiado bien parada. El evento en España empezará alrededor de la 1 de la mañana del día 25… pero no os preocupéis, el material será grabado y al día siguiente, o tan pronto tenga los enlaces, los pondré aquí.

//Publish/

Cerca de un mes después del evento //Learn/ tendremos un nuevo evento, esta vez presencial. Se trata de un hackathon organizado entre Microsoft y la comunidad MVP. En él ayudaremos a los asistentes a crear apps para Windows 8.1 y Windows Phone 8.1, tendremos mentores, premios, dispositivos para probar vuestras apps y os invitaremos a comer.

En España tenemos la suerte de tener tres sedes: Barcelona, organizada por Microsoft y Madrid y Palma de Mallorca, organizadas por la comunidad MVP. En mi caso estaré en Madrid, junto con Javier Suarez y Santiago Porras para ayudaros a crear apps increíbles y publicarlas. El evento en Madrid se llevará a cabo en las oficinas de Microsoft el día 16 de mayo.

En breve tendremos más información, según vayamos avanzando con la organización y actualizaré este post con todas las novedades. Por ahora podéis registraros en cualquiera de las tres sedes aquí.

Estos eventos son grandes oportunidades para actualizar vuestros conocimientos o introduciros en la plataforma Windows, así que no perdáis esta oportunidad. Nos vemos en breve.

Un saludo y Happy Coding!

Más artículos Página siguiente >