[Tips and Tricks] Universal Apps. Behavior para gestionar gestos swipe con comando

SwipeIntroducción

En las aplicaciones móviles la pantalla táctil permite una interacción profunda del usuario con la misma. Gracias a la pantalla táctil el usuario puede interactuar utilizando gran variedad de gestos. Tocar, un toque prolongado, deslizar, pinzamientos y asi cubriendo una gran variedad de gestos con uno o varios dedos.

En aplicaciones Windows Phone 8.1 Silverlight asi como en aplicaciones Universales WinRT tenemos a nuestra disposición un conjunto de eventos de manipulación que nos permiten gestionar la actividad dada en la pantalla táctil con uno o más dedos. Podemos determinar toques, toques prolongados, deslizamientos, etc. Sin embargo, la gestión es mediante eventos, ¿que ocurre si deseamos una gestión igual de sencilla pero manteniendo nuestra arquitectura sólida basada en MVVM?

En este artículo vamos a ver como crear un Behavior que nos permita capturar un tipo de gestos e invocar un comando de nuestra ViewModel.

¿Te apuntas?

Crear el Behavior

Comenzamos creando un nuevo proyecto:

Nueva Aplicación Universal

Nueva Aplicación Universal

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo tendra un Behavior asociado al Grid principal que capturará los gestos de tipo Swipe lanzando un comando de la ViewModel pasando como parámetro la dirección del gesto.

NOTA: El Behavior se podrá asociar a cualquier elemento derivado de UIElement.

Los Behaviors nos permiten añadir lógica a nuestro XAML para realizar acciones sencillas (pero increíblemente útiles). Entre el conjunto variado de acciones que nos permiten ejecutar, podemos lanzar animaciones, cambiar el estado de un VisualState, realizar la navegación de páginas o invocar un comando. En líneas generales suelen permitir realizar tareas habituales sin necesidad de escribir código en el code-behind de la vista lo que hace posible que mantengamos una arquitectura sólida sin código auxiliar repartido en distintas partes.

Añadir extensión Behaviors

Añadir extensión Behaviors

Con la llegada del SDK de Windows 8.1 recibimos también la incorporación del Behavior SDK. Disponible en una librería aparte del Core de .NET, podemos utilizarlo facilmente añadiendo la referencia a la misma.

NOTA: Con la llegada de Windows Phone 8.1 recibimos acceso a la misma extensión.

Si para nuestros objetivos, ninguno de los Behaviors que tenemos disponible nos cubre, podemos crear Behaviors nuevos totalmente personalizados.

A diferencia de la implementación que teníamos en WPF por ejemplo, no contamos con clases del tipo Behavior<T> de las que derivar, sino que contamos con interfaces, IBehavior. Disponible en el namespace Microsoft.Xaml.Interactivity.

public abstract class Behavior<T> : DependencyObject, IBehavior
       where T : DependencyObject
{

     public T AssociatedObject { get; set; }

     protected virtual void OnAttached()
     {
     }

     protected virtual void OnDetaching()
     {
     }

     public void Attach(DependencyObject associatedObject)
     {
            if (associatedObject == this.AssociatedObject ||
                DesignMode.DesignModeEnabled)
            {
                return;
            }

            this.AssociatedObject = (T)associatedObject;
            OnAttached();
     }

     public void Detach()
     {
            if (!DesignMode.DesignModeEnabled)
            {
                OnDetaching();
            }
     }

     DependencyObject IBehavior.AssociatedObject
     {
            get { return this.AssociatedObject; }
     }
}

La clase superior implementa la interfaz IBehavior de modo que facilmente podemos sobreescribir la propiedad AssociatedObject. Creamos un nuevo Behavior para los elementos que deriven de UIElement:

public class SwipeBehavior : Behavior<UIElement>
{

}

Implementamos la interfaz:

protected override void OnAttached()
{
     base.OnAttached();

     this.AssociatedObject.ManipulationMode =
                this.AssociatedObject.ManipulationMode |
                ManipulationModes.TranslateX |
                ManipulationModes.TranslateY;

     this.AssociatedObject.ManipulationCompleted += OnManipulationCompleted;
}

protected override void OnDetaching()
{
     base.OnDetaching();

     this.AssociatedObject.ManipulationCompleted -= OnManipulationCompleted;
}

private void OnManipulationCompleted(object sender,
            ManipulationCompletedRoutedEventArgs e)
{

}

SwipeBehavior hereda de la clase Behavior<T>.  En el evento sobreescrito OnAttached nos suscribimos al evento ManipulationCompleted gestionando los modos de trasladación en el eje X e Y (Swipes). En el método OnDetaching realizamos la dessuscripción del evento ManipulationCompleted.

public ICommand SwipeCommand
{
     get { return (ICommand)GetValue(SwipeCommandProperty); }
     set { SetValue(SwipeCommandProperty, value); }
}

public static readonly DependencyProperty SwipeCommandProperty =
     DependencyProperty.Register("SwipeCommand",
     typeof(ICommand),    
     typeof(SwipeBehavior),  
     new PropertyMetadata(null));

public static ICommand GetSwipeCommand(DependencyObject d)
{
     return (ICommand)d.GetValue(SwipeCommandProperty);
}

Creamos una DependencyProperty de tipo ICommand. De esta forma, permitimos bindear un comando a ejecutar pasándole como parámetro la dirección del gesto.

Gestión de gestos

Verificaremos valores recibidos de ManipulationCompletedRoutedEventArgs. Concretamente haremos varias verificaciones tanto en el eje X como en el Y para determinar la dirección del gesto. Creamos dos sencillas constantes para simplificar los cálculos:

private const double Min = 0.5;
private const double Max = 100;

En el evento ManipulationCompleted:

bool right = e.Velocities.Linear.X.CompareTo(Min) >= 0 && e.Velocities.Linear.X.CompareTo(Max) <= 0;
bool left = e.Velocities.Linear.X.CompareTo(-Max) >= 0 && e.Velocities.Linear.X.CompareTo(-Min) <= 0;
bool up = e.Velocities.Linear.Y.CompareTo(-Max) >= 0 && e.Velocities.Linear.Y.CompareTo(-Min) <= 0;
bool down = e.Velocities.Linear.Y.CompareTo(Min) >= 0 && e.Velocities.Linear.Y.CompareTo(Max) <= 0;

var swipeCommand = GetSwipeCommand(this);

if (swipeCommand != null)
{
     if (right && !(up || down))
                    swipeCommand.Execute(SwipeDirection.Right);
     if (left && !(up || down))
                    swipeCommand.Execute(SwipeDirection.Left);
     if (up && !(right || left))
                    swipeCommand.Execute(SwipeDirection.Up);
     if (down && !(right || left))
                    swipeCommand.Execute(SwipeDirection.Down);
}

El argumento de tipo ManipulationCompletedRoutedEventArgs, cuenta con toda la información relacionada con la manipulación realizada (gesto). Entre el conjunto de propiedades disponibles contamos con Velocities.Linear.X y Velocities.Linear.Y que nos permiten determinar la dirección del gesto. Dependiendo de la dirección del gesto, ejecutaremos el comando bindeado en la propiedad SwipeCommand pasándole como parámetro la dirección.

En nuestra interfaz, detectaremos el gesto de tipo Swipe y segun la orientación del gesto, mostrando un texto de información indicándola.

En nuestra ViewModel, definiremos la propiedad que mostrará en pantalla la orientación del gesto:

private string _info;

public string Info
{
     get { return _info; }
     set
     {
          _info = value;
          RaisePropertyChanged();
     }
}

Al lanzar un gesto de tipo Swipe lanzaremos un comando:

private ICommand _swipeCommand;

public ICommand SwipeCommand
{
     get { return _swipeCommand = _swipeCommand ?? new DelegateCommand<SwipeDirection>(SwipeCommandExecute); }
}

private void SwipeCommandExecute(SwipeDirection direction)
{
     switch (direction)
     {
          case SwipeDirection.Up:
               Info = "Arriba";
               break;
          case SwipeDirection.Down:
               Info = "Abajo";
               break;
          case SwipeDirection.Left:
               Info = "Izquierda";
               break;
          case SwipeDirection.Right:
               Info = "Derecha";
               break;
     }
}

Recibimos como parámetro el tipo del Swipe de modo que podemos ejecutar en consecuencia.

En la interfaz de usuario, ante cualquier elemento visual derivado de UIElement:

<interactivity:Interaction.Behaviors>
     <behaviors:SwipeBehavior
          SwipeCommand="{Binding SwipeCommand}" />
</interactivity:Interaction.Behaviors>

Sencillo, ¿verdad?

GesturesBehavior

GesturesBehavior

Podéis descargar el sencillo ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información

[Tips and Tricks] Universal Apps. Evento ItemClick a Comando

List All Application installed on cmputerEl problema

Los controles ListView y GridView son una pieza angular muy importante en la mayoría de aplicaciones Windows Store y Windows Phone. En su uso necesitaremos determinar cuando el usuario selecciona un elemento. Trabajando con el patrón MVVM nos encontramos con un problema. Para determinar el elemento seleccionado tenemos dos opciones:

  1. Utilizar la propiedad SelectedItem. Podemos bindear una propiedad de nuestra ViewModel a la propiedad SelectedItem del control. Es una opción válida pero en ciertas situaciones puede no ser completa. Por ejemplo, si al establecer la propiedad realizamos una navegación, al volver atrás si volvemos a intentar navegar al mismo elemento no ocurrira nada. Esto es asi, porque la propiedad SelectedItem esta ya establecida y no cambia. Debemos establecer a nulo la propiedad utilizando los eventos de navegación.
  2. Utilizar el evento ItemClick que nos facilita un argumento de tipo ItemClickEventArgs con la información del elemento seleccionado. Forma idónea pero con un gran problema, no expone una propiedad Command para bindear y utilizar nuestra ViewModel.

¿Qué podemos hacer?

En este artículo crearemos una sencilla clase que exponga una propiedad de dependencia adjunta a utilizar.

ItemClick utilizando un Comando

Añadiremos un GridView con su propiedad ItemsSource bindeada a una propiedad de la ViewModel:

<GridView
     ItemsSource="{Binding Items}"
     SelectionMode="None"
     IsSwipeEnabled="false"
     IsItemClickEnabled="True" />

Deshabilitamos los modos de selección y habilitamos el evento ItemClick. En la ViewModel tendremos definida la propiedad:

private ObservableCollection<string> _items;

public ObservableCollection<string> Items
{
     get
     {
          if (_items == null)
             LoadData();
         return _items;
     }
     set { _items = value; }
}

La propiedad la instanciará un método LoadData que rellenará la colección de sencillos datos para poder probar:

private void LoadData()
{
     _items = new ObservableCollection<string>();
     for (int i = 0; i < 100; i++)
     {
          _items.Add((i + 1).ToString());
     }
}

Llega el momento clave. Vamos a crear una clase donde definiremos una propiedad de dependencia adjunta (Attached DependencyProperty) de tipo ICommand:

public static class ItemClickCommandBehavior
{
     public static readonly DependencyProperty ItemClickCommandProperty =
            DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand),
            typeof(ItemClickCommandBehavior), new PropertyMetadata(null, OnItemClickCommandPropertyChanged));

     public static void SetItemClickCommand(DependencyObject d, ICommand value)
     {
         d.SetValue(ItemClickCommandProperty, value);
     }

     public static ICommand GetItemClickCommand(DependencyObject d)
     {
         return (ICommand)d.GetValue(ItemClickCommandProperty);
     }

     private static void OnItemClickCommandPropertyChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e)
     {
         var control = d as ListViewBase;
         if (control != null)
             control.ItemClick += OnItemClick;
     }

     private static void OnItemClick(object sender, ItemClickEventArgs e)
     {
         var control = sender as ListViewBase;
         var itemClickCommand = GetItemClickCommand(control);

         if itemClickCommand != null && command.CanExecute(e.ClickedItem))
             itemClickCommand.Execute(e.ClickedItem);
     }
}

Cuando se ejecute el evento ItemClick, se tomará el Comando definido en la propiedad, se le pasará el argumento ClickedItem y se ejecutará.

NOTA: Importante resaltar que en la clase utilizamos ListViewBase, por lo que podremos adjuntar la propiedad tanto en controles GridView como ListView.

Ya podremos adjuntar la propiedad en nuestro GridView. Debemos definir el comando a ejecutar en la ViewModel:

private ICommand _clickCommand;

public ICommand ClickCommand
{
     get { return _clickCommand = _clickCommand ?? new DelegateCommandAsync<string>(ClickCommandExecute); }
}

private async Task ClickCommandExecute(string parameter)
{
     await _dialogService.ShowAsync(parameter);
}

El comando es sencillo. En el método Execute del mismo recibimos el objeto que contiene al elemento seleleccionado como parámetro. En nuestro ejemplo, utilizamos un sencillo servicio de dialogo para mostrar un mensaje con la información del elemento seleccionado.

Todo listo!. Solo nos queda añadir la propiedad adjunta. Añadimos en la vista el namespace donde hemos definido nuestra clase ItemClickCommandBehavior:

xmlns:behaviors="using:ItemClickCommandBehavior.Behaviors"

Y en el GridView:

behaviors:ItemClickCommandBehavior.ItemClickCommand="{Binding ClickCommand}"

De esta forma, tendremos nuestro GridView:

Nuestra colección de elementos

Nuestra colección de elementos

Al pulsar sobre cualquier elemento, nuestra clase dispará el evento ItemClick donde se tomará el comando adjunto y se ejecutará:

Comando ejecutado

Comando ejecutado

Podéis descargar el sencillo ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información

[Windows 10] Novedades a nivel de desarrollo presentadas en el MWC15

Un primer vistazo a Apps Universales en Windows 10

Windows 10 ha llegado como la culminación en el viaje hacia la convergencia en el desarrollo entre plataformas Windows.

Gracias a esta convergencia, podemos ejecutar nuestra App en teléfonos, tabletas, PCs y en la Xbox One. Además, debemos unir los nuevos dispositivos que se unen a la “familia” como dispositivos IoT como la Raspberry Pi 2.

Universal Apps

Universal Apps

Ahora hablamos de Apps Universales escritas una única vez con un código comun tanto para la lógica de negocio como para la interfaz de usuario. Además, generamos un único paquete que mantendrá una interfaz consistente y familiar para el usuario pero adaptada a cada plataforma.

Novedades

Para constuir Apps Universales bajo un mismo paquete, con el mismo código pero adaptando la experiencia a cada plataforma se han añadido una serie de novedades a destacar.

UX Adaptable

El modelo de Apps ha sido mejorado en líneas generales pero además se ha mejorado mucho el ViewStateMananger para permitir crear vistas que se adapten al dispositivo con facilidad. Esto nos permitirá crear vistas comunes sin separar en ficheros diferentes por plataforma en la mayoría de casos. Aunque se permitirá seguir dividiendo vistas en caso necesario.

VisualStateManager

VisualStateManager

Además los controles en tiempo de ejecución se adaptarán a las condiciones usadas para interactuar por el usuario. De esta forma, tendremos controles que cambien tamaño u opciones dependiendo de si se usa entrada táctil o ratón por ejemplo.

Cortana

La integración de Cortana será mayor permitiendo búsqueda de Apps, lanzar Apps más frecuentes, etc.

Cloud

Seguiremos contando con opciones en Azure potentes y versátiles como Azure Mobile Services o el Hub de notificaciones además del servicio de notificaciones Windows (WNS), etc. Con Windows 10 llegan más servicios o algunos serán mejorados como OneDrive o Cortana.

Entrando en materia

En el Hall 8 del MWC, Microsoft puso a disposición del público una gran cantidad de Surfaces preparadas para permitir probar y crear Apps Universales Windows 10 mediante dos Hands on Labs guiados.

Devs creando Apps Universales Windows 10

Devs creando Apps Universales Windows 10

Tras probar a fondo todo lo posible, a nivel de desarrollo me gustaría destacar una serie de novedades bastante suculentas.

RelativePanel

Estamos ante un nuevo Panel que tiene como principal objetivo permitir crear interfaces con diseños que se adapten con facilidad a cualquier tipo de tamaño. Posiciona a los elementos que contiene de manera relativa entre ellos.

Veamos un ejemplo sencillo:

<RelativePanel>
    <TextBlock x:Name="MyTextBlock" Text="TextBlock 1" RelativePanel.AlightHorizontalCenterWithPanel="True" />
    <TextBlock Text="TextBlock 2" RelativePanel.RightOf="{Binding ElementName=MyTextBlock}" />
</RelativePanel>

En el trozo de XAML de la parte superior utilizamos el nuevo Panel para posicionar dos simples textos. El primero de los textos se situará de manera central en forma horizontal al Panel. El segundo es mucho más interesante. Su posición será a la derecha del primer texto.

Tendremos disponible una gran variedad de opciones de alineación que serán las que otorgen un potencial muy alto al Panel.

Opciones de alineación del RelativePanel

Opciones de alineación del RelativePanel

Además, podremos utilizar StateTriggers para modificar la relación entre controles segun ciertas condiciones.

SplitView

Para que nos situemos de entrada, muchos conocéis alternativas visuales similares como “Hamburger menu”. Crea un menu deslizante lateral.

SplitView

SplitView

Es impresionantemente sencillo de utilizar. Cuenta con una propiedad Pane que permite establecer el contenido del panel. Podemos mostrar el panel a izquierda o derecha mediante la propiedad PanePlacement y el ancho utilizando OpenPaneLength.

Veamos un sencillo ejemplo:

<SplitView
    x:Name="SplitView"
        OpenPaneLength="200"
        Background="Red"
        PanePlacement="Left">
        <SplitView.Pane>
            <Grid>
                <TextBlock Text="SplitView Pane" />
            </Grid>
        </SplitView.Pane>
        <Grid>
            <TextBlock Text="Content" />
        </Grid>
</SplitView>

Por último, tenemos la propiedad IsPaneOpen para determinar si el menu esta abierto o cerrado.

StateTriggers

Llegamos a una de las novedades más potentes. Hasta ahora utilizábamos ya Visual States para gestionar la apariencia de la interfaz de Apps WinRT.

Con una clara inspiración en los triggers de WPF nos llegan los StateTriggers. Su uso es muy simple, realizar un cambio en un elemento visual cuando se cumpla una condición.

Actualmente tenemos disponible AdaptiveTrigger. Basicamente la condición depende de un ancho y alto mínimo que establecemos con las propiedades MinWindowHeight y MinWindowWidth. Cuando la ventana supera el tamaño, el VisualState se activa.

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="WideState">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="600" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="MainGrid.Margin" Value="15, 50" />
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

¿Os recuerda un poco a las Media Queries en CSS?. Muchos Devs de los que probaron el SDK en alguno de los Hands On Labs me lo comentaron con frecuencia.

Tenemos otra serie de novedades en emuladores, herramientas, etc. que comentaremos con calma cuando Microsoft lo muestre con más detalles.

¿Qué es lo próximo?

Podríamos resumirlo con suma facilidad, //BUILD. Lo mostrado en este MWC es sin duda un gran adelanto que nos abre el apetito vorazmente dejandonos con ansias de mucho más. Se denota un gran esfuerzo por mejorar las herramientas de desarrollo, mejorar la plataforma y sobretodo mejorar las opciones para utilizar y compartir código entre plataformas.

Nos vemos en la próxima!

Nos vemos en la próxima!

Mientras llega la fecha, os recomiendo apuntaros al programa Insider si aun no lo hicisteis y en el caso de desarrollo, la mejor recomendación por ahora es crear Apps Universales 8.1 que serán una gran base de partida cuando la nueva plataforma este disponible.

En el próximo //BUILD se mostrará más, se darán más detalles, etc. Asi que, no se vosotros pero…¿a llegado ya el BUILD?…¿y ahora?….¿y ahora?

Más información

[Tips and Tricks] RequestTheme, cambiar el tema al vuelo!

ColorIntroducción

En Windows Phone, podemos definir un tema totalmente personalizado para nuestras aplicaciones. Por defecto, ya contamos con dos temas, claro y oscuro (“Light” y “Dark” respectivamente). Si no usamos nuestro propio tema totalmente personalizado debemos gestionar los elementos de nuestra interfaz para una correcta visualización tanto en el tema claro como en el oscuro. El usuario puede cambiar el tema a utilizar (por defecto oscuro).

Podemos forzar el utilizar un tema en concreto e incluso en tiempo de ejecución podemos modificar el tema a utilizar para cualquier elemento.

NOTA: Modificar la propiedad Application.RequestedTheme en tiempo de ejecución provoca una excepción.

RequestTheme

La propiedad RequestedTheme nos permite obtener o establecer el tema a utilizar. Al cambiar el valor de RequestedTheme de la Aplicación a nivel general o de un elemento en concreto provoca que se obtengan las plantillas y estilos del ResourceDictionary denominados “Light” o “Dark” de la colección ThemeDictionaries.

NOTA: La propiedad RequestedTheme es ignorada si el usuario tiene activo el modo de alto contraste.

Crearemos un ejemplo muy simple para realizar el cambio en tiempo de ejecución del tema. Creamos dos sencillos botones:

<Button Content="Light" />
<Button Content="Dark" />

El primero cambiara al tema claro y el segundo al tema oscuro:

Nuestra UI en el tema por defecto

Nuestra UI en el tema por defecto

En nuestra viewmodel, contaremos con un comando para cada botón:

private ICommand _lightCommand;
private ICommand _darkCommand;

public ICommand LightCommand
{
     get { return _lightCommand = _lightCommand ?? new DelegateCommand(LightCommandExecute); }
}

public ICommand DarkCommand
{
     get { return _darkCommand = _darkCommand ?? new DelegateCommand(DarkCommandExecute); }
}

private void LightCommandExecute()
{

}

private void DarkCommandExecute()
{

}

La definición de los botones quedara:

<Button Content="Light" Command="{Binding LightCommand}" />
<Button Content="Dark" Command="{Binding DarkCommand}" />

En Windows Phone 8.1, la App cuenta con una Window que contiene un único Frame que ocupa el 100% del área de la pantalla.

El Frame de la Aplicación

El Frame de la Aplicación

El Frame, se crea en el arranque de la aplicación y es el encargado de contener y gestionar cada una de las páginas (Page). La propiedad RequestedTheme establecida en un elemento FrameworkElement es heredado en sus elementos hijos. Podemos anular la herencia estableciendo la propiedad RequestedTheme en un elemento hijo concreto. Estableceremos la propiedad en el Frame de la Aplicación provocando que la misma sea heredada por sus elementos hijos, es decir, las páginas de la Aplicación:

AppFrame.RequestedTheme = ElementTheme.Light;

En el segundo comando establecemos el RequestedTheme del Frame de la Aplicación al tema oscuro:

AppFrame.RequestedTheme = ElementTheme.Dark;

Al cambiar al tema claro nuestra interfaz se vera de la siguiente forma:

Cambio de tema a claro

Cambio de tema a claro

El resultado en video:

Podéis descargar el pequeño ejemplo realizado a continuación:

Más información

[Christmas Mobile Apps Sessions] Material de “Publicación de Universal Apps”

DownloadEl evento

El pasado Miércoles 10 de Diciembre tenía lugar el cuarto webcast de las Christmas Mobile Apps Sessiones. Las Christmas Mobile Apps Sessions son un conjunto 10 webcasts sobre desarrollo de apps móviles con variada  temática.

En esta sesión, vimos formas de monetización en aplicaciones universales viendo como crear Trials, In-App Purchase o como añadir publicidad, creamos el paquete de aplicación, publicamos en la tienda con diferentes opciones y finalmente vimos como añadir analíticas avanzadas.

El material

La presentación utilizada:

Podéis descargar el conjunto de ejemplos realizados en el webcast a continuación:

Por último, podéis ver en cualquier momento el webcast en el siguiente video:

Más información

[Christmas Mobile Apps Sessions] Material de “Herramientas y toolkits para Universal Apps”

DownloadEl evento

El pasado Miércoles 10 de Diciembre tenía lugar el cuarto webcast de las Christmas Mobile Apps Sessiones. Las Christmas Mobile Apps Sessions son un conjunto 10 webcasts sobre desarrollo de apps móviles con variada  temática.

En esta sesión, vimos novedades de Visual Studio en XAML y C# relacionadas con aplicaciones universales, profundizamos en todas las posibilidades que nos brinda el emulador de Windows Phone, repasamos algunas características fundamentales de Blend, probamos la herramientas de análisis de rendimiento además de ver muchas otras herramientas variadas.

El material

La presentación utilizada:

Podéis descargar el conjunto de ejemplos realizados en el webcast a continuación:

Por último, podéis ver en cualquier momento el webcast en el siguiente video:

Más información

[Christmas Mobile Apps Sessions] Material de “Introducción a Universal Apps”

DownloadEl evento

El pasado Viernes 05 de Diciembre tenía lugar el segundo webcast de las Christmas Mobile Apps Sessiones. Las Christmas Mobile Apps Sessions son un conjunto 10 webcasts sobre desarrollo de apps móviles con variada  temática. En esta sesión, vimos en qué consisten, como crear así como los aportes de las Aplicaciones Universales. Además, profundizamos en la convergencia entre las plataformas Windows analizando distintas técnicas para compartir código.

El material

La presentación utilizada:

Podéis descargar el conjunto de ejemplos realizados en el webcast a continuación:

Por último, podéis ver en cualquier momento el webcast en el siguiente video:

Más información

[Universal App] Usando Microsoft OCR

OCR LogoIntroducción

Existen aplicaciones sumamente útiles en la Tienda. Algunas de ellas impactantes. Personalmente me impresionaron alguna aplicación que directamente de una foto o video podía capturar el texto y traducirnoslo directamente a nuestron idioma, o aplicaciones que de una captura nos obtienen el texto de la misma permitiendonos guardarlo o incluso compartirlo. Son aplicaciones muy útiles en categorías diferentes que tienen un denominador común, son capaces de obtener el texto de una imagen.

¿Cómo lo hacen?

OCR

OCR son las siglas de Optical character recognition, o en español reconocimiento óptico de carácteres. Es un proceso dirigido a la digitalización de textos, los cuales identifican automáticamente a partir de una imagen símbolos o caracteres. Las posibilidades obtenidas son muchas y variadas:

  • Obtener el texto de una imagen.
  • Traducir el texto de una imagen.
  • Verificar si una palabra o valor se encuentra en el texto de la imagen.
  • Etc.

Librería Microsoft OCR para Windows Runtime

Recientemente Microsoft ha liberado en NuGet la librería Microsoft OCR para Windows Runtime. Esta librería nos permite en aplicaciones universales digitalizar textos desde imagenes. La librería funciona completamente en local tratando imágenes desde la cámara, local o desde la red soportando hasta 21 idiomas.

Utilizando la librería

Comenzamos creando un nuevo proyecto:

Nueva Aplicación Universal

Nueva Aplicación Universal

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo obtendrá una imagen local gracias al uso del FileOpenPicker para sintetizar la imagen obtenida con la librería Microsoft OCR.

La interfaz de usuario

Comenzamos creando la interfaz de nuestro ejemplo. En la carpeta Views tenekos disponible la vista MainView. Añadimos:

<ScrollViewer>
     <StackPanel>
          <Image Source="{Binding Bitmap}" Stretch="Uniform" MaxHeight="300" Margin="10" />
          <TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="24"/>
     </StackPanel>
</ScrollViewer>

Hemos añadido dos controles básicos para nuestro ejemplo. Por un lado la imagen bindeada a una propiedad Bitmap que mostrará la imagen obtenida con el FileOpenPicker, por otro lado añadimos un sencillo TextBlock que mostrará el resultado de obtener el texto correspondiente a la imagen cargadada.

En la vista principal trendremos además dos botones. Un botón para obtener una imagen local, y otro para utilizar la imagen obtenida para extraer el texto:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="get picture" Icon="Pictures" Command="{Binding FileOpenPickerCommand}" />
          <AppBarButton Label="get text" Icon="Forward" Command="{Binding GetTextCommand}" />
     </CommandBar>
</Page.BottomAppBar>

Los añadimos en la CommandBar de la página. En la viewmodel de la vista tendremos disponibles tanto la imagen como el texto bindeados asi como los dos comandos necesarios para ejecutar las acciones requeridas:

//Variables
private WriteableBitmap _bitmap;
private string _text;

//Commands
private ICommand _fileOpenPickerCommand;
private ICommand _getTextCommand;

public WriteableBitmap Bitmap
{
     get { return _bitmap; }
     set
     {
          _bitmap = value;
          RaisePropertyChanged("Bitmap");
     }
}

public string Text
{
     get { return _text; }
     set
     {
          _text = value;
          RaisePropertyChanged("Text");
     }
        
public ICommand FileOpenPickerCommand
{
     get { return _fileOpenPickerCommand = _fileOpenPickerCommand ?? new DelegateCommandAsync(FileOpenPickerCommandDelegate); }
}

public ICommand GetTextCommand
{
     get { return _getTextCommand = _getTextCommand ?? new DelegateCommandAsync(GetTextCommandDelegate); }
}
public async Task FileOpenPickerCommandDelegate()
{

}

public async Task GetTextCommandDelegate()
{

}

Hasta ahora sencillo, ¿verdad?.

Añadiendo la librería

Continuamos añadiendo la librería.

La librería la tenemos disponible en NuGet por lo que podemos instalarlo usando Nuget Package Manager. En las referencias del proyecto hacemos clic derecho y seleccionamos la opción Manage NuGet Packages …

Añadir paquete NuGet

Añadir paquete NuGet

En la ventana modal que nos aparece, en la parte superior derecha donde podemos realizar una búsqueda, buscamos por “Microsoft OCR”:

Microsoft OCR Library for Windows Runtime

Microsoft OCR Library for Windows Runtime

Seleccionamos el elemento “Microsoft OCR Library for Windows Runtime” y pulsamos el botón Install. Tras un breve periodo donde se procede a descargar e incluir las librerías en las referencias del proyecto, tendremos lo necesario para comenzar a trabajar con OCR.

FileOpenPicker

Representa un elemento de interfaz que permite al usuario elegir y abrir ficheros. Es una API asíncrona disponible dentro del namespace Windows.Storage.Pickers. La API esta disponible tanto en Windows como en Windows Phone pero con diferencias entre ambas plataformas.

En aplicaciones Windows Store se realiza una llamada asíncrona a la API esperando el resultado directamente. Mientras que en Windows Phone el proceso es algo más complejo. En Windows Phone podemos contar con dispositivos de baja memoria. Por ese motivo, en se utilizan métodos “AndContinue”. Esto quiere decir que al realizar la llamada a la API la aplicación es suspendida e incluso podría llegar a ser terminada. Continuará el proceso al reactivar la aplicación.

Debido a las diferencias entre ambas plataformas, en nuestro ejemplo, vamos a crear una interfaz comun de un servicio FileOpenPicker en el proyecto Shared:

public interface IFilePickerService
{
     void Initialise();
     Task<StorageFile> ShowPickerAsync(FileOpenPicker picker);
}

Realizaremos la implementación adecuada de cada plataforma en el proyecto correspondiente.

Windows Store

Conenzamos por la implementacion en la aplicación Windows Store que es el caso más sencillo. El FileOpenPicker  es invocado llamando al método PickSingleFileAsync que devolverá (asíncronamente) un objeto de tipo StorageFile que representa el fichero seleccionado por el usuario:

public async Task<StorageFile> ShowPickerAsync(FileOpenPicker picker)
{
     StorageFile file = await picker.PickSingleFileAsync();
     return (file);
}

Sencillo y brutalmente potente.

Windows Phone

Continuamos con Windows Phone. En este caso, el FileOpenPicker es invocado mediante el evento PickSingleFileAndContinue. La aplicación pasa a estar suspendida.

public async Task<StorageFile> ShowPickerAsync(FileOpenPicker picker)
{
     _completionSource = new TaskCompletionSource<StorageFile>();

     picker.PickSingleFileAndContinue();

     StorageFile file = await _completionSource.Task;

     return (file);
}

Una vez resumida se obtiene el objeto de tipo StorageFile vía método Activated de la aplicación. La clase FileOpenPickerContinuationEventArgs nos proporciona información acerca del evento Activated cuando se produce por una operación de FileOpenPicker.

private void OnApplicationActivated(CoreApplicationView sender,
            IActivatedEventArgs args)
{
     var continueArgs =
                args as FileOpenPickerContinuationEventArgs;

     if (continueArgs != null)
     {
          _selectedFile = continueArgs.Files[0];

         if (_completionSource != null)
         {
              _completionSource.SetResult(_selectedFile);
              _completionSource = null;
         }
     }
}

Con esto ya tenemos la capacidad en nuestra aplicación, tanto en Windows como en Windows Phone, de permitir seleccionar una imagen al usuario.

Microsoft OCR

Llegamos al eje central del ejemplo. La librería Microsoft OCR nos permite extraer el texto de la imagen obtenida previamente con el FileOpenPicker. La librería Microsoft OCR para Windows Runtime nos permite extraer el texto de una imagen además de poder encontrar patrones como correos o números de teléfono, ideal para pooder ejecutar acciones.

Vamos a crear un servicio (al igual que hicimos con el FileOpenPicker) para inyectarlo posteriormente con inyección de dependencias. La interfaz del servicio es la siguiente:

public interface IOcrService
{
     Task<string> GetText(WriteableBitmap bitmap);
}

Muy muy simple. Tendremos un único método asíncrono que obtendra la imagen y devolverá el texto. Trabajamos con la librería OCR en el namespace WindowsPreview.Media.Ocr. La implementación del servicio para ambas plataformas es la siguiente:

public class OcrService : IOcrService
{
     private OcrEngine _ocrEngine;

     public async Task<string> GetText(WriteableBitmap bitmap)
     {
         string result = string.Empty;

         if (_ocrEngine == null)
             _ocrEngine = new OcrEngine(OcrLanguage.English);

         // Sintetizamos la imagen para extraer el texto (RecognizeAsync)
         var ocrResult = await _ocrEngine.RecognizeAsync((uint)bitmap.PixelHeight, (uint)bitmap.PixelWidth, bitmap.PixelBuffer.ToArray());

         // Si el resultado no contiene líneas no hacemos nada
         if (ocrResult.Lines != null)
             // Si hay líneas, las vamos añadiendo al resultado final
             result = ocrResult.Lines.Aggregate(result, (current1, line) => line.Words.Aggregate(current1, (current, word) => current + word.Text + " "));

         return result;
     }
}

Analicemos con calma el código anterior. Lo primero que hacemos es crear una instancia de OcrEngine. Esta clase es la encargada de proporcionar la capacidad de realizar OCR a nuestra aplicación. Al instanciar el OcrEngine podemos hacerlo sin parámetros o indicando el idioma a utilizar para detectar el texto en la imagen. Hay 21 lenguajes soportados. Dependiendo de la calidad de detección, el rendimiento y otros parámetros podemos establecer la siguiente divisón en grupos:

  • Excelente: Checo, Danés, Holandés, Inglés, Finlandés, Francés, Alemán, Húngaro, Italiano, Noruego, Polaco, Portugués, Español y Sueco.
  • Muy bueno: Chino, Griego, Japones, Ruso y Turco.
  • Bueno: Chino tradicional y Creano.

NOTA: Al incluir la librería Microsoft OCR además de la propia librería se nos añade un archivo de recursos OCR. Estos recursos son utilizados para un correcto reconocimiento del texto correspondiente en el idioma deseado. El archivo de recursos OCR añadido por defecto es en ingles. Si deseamos utilizar otros idiomas a los recursos OCR debemos utilizar la herramienta OCR Resources Generator tool. En el siguiente apartado de este mismo artículo utilizaremos la herramienta.

Continuamos analizando el código de nuestro servicio OCR. Tras instanciar el OcrEngine, utilizamos el método RecognizeAsync. A este método le pasamos la imagen junto con sus dimensiones para extraer el texto. Se devuelve un objeto de tipo OcrResult. Este objeto contiene el texto extraido asi como sus posiciones y tamaños.

El objeto OcrResult cuenta con una colección de objetos de tipo OcrLine que son cada una de las líneas de texto extraidas que a su vez contienen una colección de objetos de tipo OcrWord, que contienen la información de cada una de las palabras extraidas.

Iremos recorriendo las líneas añadiendo cada palabra en una cadena que sera el resultado devuelto por nuestro servicio.

El uso en nuestra viewmodel sera simple:

Text = await _ocrService.GetText(_bitmap);

Donde _bitmap representa la imagen local obtenida. Guardamos en Text el resultado obtenido. En nuestro ejemplo, para simplificar solo obtenemos el texto y nuestro objetivo finaliza aqui. Sin embargo, desde este punto podéis utilidad OCR para una enorme cantidad de situaciones como traducciones, extraer ciertos campos, verificar si la iamgen contiene o no cierta palabra, compartir el contenido, etc.

Continuamos el ejemplo. La librería OCR tiene límites. La dimensión de la imagen utilizada no puede ser inferior de 40 x 40px o superior a 2600 x 2600px. Debemos considerar esto en nuestras aplicaciones. Definimos los límites en constantes:

public const int MinWidth = 40;
public const int MaxWidth = 2600;
public const int MinHeight = 40;
public const int MaxHeight = 2600;

De modo que antes de utilizar nuestro servicio OCR para extraer el texto realicemos la verificación correspondiente:

public async Task GetTextCommandDelegate()
{
     // Verificamos si la imagen cumple con las características necesarias para ser procesada.
     // Las dimensiones soportadas son desde 40 a 2600 pixels.
     if (_bitmap.PixelHeight < MinHeight ||
         _bitmap.PixelHeight > MaxHeight ||
         _bitmap.PixelWidth < MinWidth ||
         _bitmap.PixelWidth > MaxHeight)   
         await _dialogService.ShowAsync("Imagen inválida", "La imagen no esta dentro del tamaño soportado.");
     else    
         Text = await _ocrService.GetText(_bitmap);
}

Todo listo!. Si ejecutamos el ejemplo y probamos:

I, Robot de Isaac Asimov sintetizado

I, Robot de Isaac Asimov sintetizado

Como generar recursos OCR

Hasta este punto tenemos lo necesario para saber como utilizar la librería Microsoft OCR y extraer texto de una imagen. Sin embargo, en el proceso de extracción como ya mencionamos previamente se utilizan unos recursos (MsOcrRes.orp) localizados por idioma que facilitan la calidad del proceso.

Hasta ahora hemos utilizado el ingles (idioma por defecto), pero… ¿que ocurre si intentamos extraer texto en japones?

Bueno, para lograr ese objetivo con l mayor calidad posible tenemos a nuestra disposición la herramienta OCR Resources Generator tool. Si nos paramos a anlizar la estructura de nuestro proyecto, tendremos algo similar a:

<solution_name>
   <project_name>
      OcrResources
         MsOcrRes.orp
   packages
      Microsoft.Windows.Ocr.1.0.0
         build
         lib
         content
            OcrResources
               MsOcrRes.orp
         OcrResourcesGenerator
            OcrResourcesGenerator.exe

Tenemos disponible la herramienta en <solution_name>\packages\Microsoft.Windows.Ocr.1.0.0\OcrResourcesGenerator\OcrResourcesGenerator.exe. Tras ejecutarla veremos una pantalla como la siguiente:

Eligiendo idiomas

Eligiendo idiomas

Nos permite elegir entre los 21 idiomas soportados.Podemos añadir o quitar idiomas y una vez esta a nuestro antojo pulsaremos el botón Generate Resources.

Guardando el recurso generado

Guardando el recurso generado

Nos aparecerá una ventana que nos permite guardar el nuevo archivo de recursos OCR. Una vez guardado bastara con reemplazar el archivo añadido por defecto en \OcrResources\MsOcrRes.orp.

Sencillo, ¿verdad?

Podéis descargar el ejemplo realizado a continuación:

Más información

[Evento CartujaDotNet] Material de “Introducción a las aplicaciones universales” en el Sevilla Mobility Day

El evento

El pasado sábado 05 de Julio tenía lugar el Sevilla Mobility Day en el Cloud Pointing de Microsoft situado en el Parque Empresarial Nuevo Torneo.  Un evento con múltiples charlas relacionadas con el desarrollo móvil de CartujaDotNet, grupo de usuarios .NET de Sevilla. Agradecer la colaboración de Guillermo Guerrero de Microsoft por hacer que todo sea tán fácil. Con todas las reservas agotadas, al llegar teníamos lleno absoluto:

Lleno absoluto!

Lleno absoluto!

Agradecer de entrada la gran acogida y asistencia de todos. Comenzamos el evento con una rápida presentación donde se mostraba la agenda del día, se presentaban a los ponentes, etc. A continuación, era mi turno con la primera sesión técnica hablando sobre aplicaciones universales:

Introduciendo los modelos de desarrollo en Windows Phone 8.1

Introduciendo los modelos de desarrollo en Windows Phone 8.1

Comenzamos repasando la situación en el desarrollo Windows & Windows Phone antes de la llegada de las aplicaciones universales asi como las opciones disponibles para compartir la mayor cantidad de código posible entre ambas plataformas. Continuamos introduciendo las aplicaciones universales llegando de el concepto a la creación de una aplicación. En la creación de la aplicación nos esforzamos para compartir la mayor cantidad posible de código y elementos comunes del proyecto centrados en el proyecto Shared. Y terminamos compartiendo XAML en el proyecto Shared en nuestra aplicación de ejemplo.

Tras la primera sesión, realizamos una pequeña parada para reponer fuerzas y volver con otra sesión de la mano de Josué Yeray. En este caso, continuábamos desarrollando para Windows y Windows Phone analizando las posibilidades de los Behaviors, Animaciones y Visual States:

Demo en directo de Behaviors, Animaciones y Visual States

Demo en directo de Behaviors, Animaciones y Visual States

Con un demo comenzada desde cero vimos que son y para que sirven los Behaviors con algunos casos prácticos útiles como por ejemplo, realizar la navegación entre páginas. Continuamos recalcando la importancia de la velocidad percibida contra la velocidad real de las aplicaciones, introduciendo las animaciones. Vimos los tipos disponibles y algun ejemplo. Terminamos con un ejemplo de una aplicación universal fotográfica donde con el uso de Visual States se controlaba la disposición de lsa fotos segun la orientación del dispositivo.

Tras otros minutos de descanso llegaba el turno de desarrollo de videojuegos multiplataforma con WaveEngine de la mano de Juan María Lao:

Desarrollo de videojuegos con WaveEngine

Desarrollo de videojuegos con WaveEngine

Tras una introducción a WaveEngine vimos las capacidades y desarrollo de videojuegos 3D. Juan María realizo un ejemplo desde cero donde nos mostraba los métodos update y draw del juego, el trabajo con componentes cargando una tetera en 3D. Continuamos viendo las posibilidades 2D del engine. Nos centramos en un juego disponibles en las stores estilo Flappy Bird bastante interesante. Aqui vimos la gestión de recursos con la herramienta Asset Exporter y como convertir el proyecto a cada una de las plataformas soportadas (Windows Phone, Windows Store, Android, iOS, Ouya, Mac OSX, etc.).

Para terminar el evento contábamos con una sesión sobre desarrollo de aplicaciones multiplataforma con Xamarin de la mano de Juan Cano y Marcos Cobeña:

¿MVVMCross o Xamarin.Forms?

¿MVVMCross o Xamarin.Forms?

De entrada, plantearon un dilema interesante, ante una aplicación multiplataforma con Xamarin, ¿que usamos?, ¿MVVMCross o Xamarin.Forms?. Con un ejemplo maestro-detalle del evento donde se mostraban el listado de ponentes y al pulsarlos en el detalle la información detallada de cada sesión fuimos viendo como se realiza el desarrollo de la aplicación tanto en MVVMCross como en Xamarin.Forms, detalles pros y contras. Al final de la charla, vimos una tabla comparativa con pros y contras y recomendaciones de cuando usar una opción u otra.

Con esta última sesión llegamos al final del evento. Un evento bastante completo con múltiples sesiones técnicas de un nivel muy alto y con una asistencia genial. Agradecer a todos los asistentes, a todos los ponentes y a patrocinadores por hacer posible el evento:

Gracias!

Gracias!

Sin duda, volveremos a tener más eventos de esta índole tras el parón veraniego!

El material

En mi caso, tuve el placer de poder “arrancar” el evento con una charla sobre aplicaciones universales.

Tenéis disponible la presentación utilizada a continuación:

En el ejemplo realizado el objetivo era crear una aplicación universal “real” (todo lo real que se puede hacer en el tiempo disponible) que conectase con un servicio para obtener las clasificaciones tanto de escuderías como de pilotos de la Formula 1. Los puntos importantes del ejemplo eran:

  • Como estructurar el proyecto y como implementar el patrón MVVM.
  • Como utilizar inyección de dependencias.
  • Ver como compartir la mayor cantidad de código y recursos posibles.
  • Ver los controles comunes y como compartir XAML.

La aplicación quedo como podéis ver a continuación:

Ejemplo Sevilla Mobility Day

Ejemplo Sevilla Mobility Day

Podéis descargar este ejemplo a continuación:

A parte de este ejemplo, vimos otros pequeños como el uso de compilación condicional en XAML o el uso de VisualStates para gestionar vistas compartidas en aplicaciones universales. Lo tenéis tambien disponible:

Más información