[Windows 8] GridView

Hola a todos!

Hoy quiero que hablemos de uno de los nuevos controles que tenemos disponibles en Windows 8. Estamos hablando del control GridView, que es responsable de gran parte del look & feel de las aplicaciones metro:

image

Tanto es así que incluso la tienda oficial de Windows 8 se basa en un GridView para presentarnos la información. Como podemos observar en la imagen superior, tenemos grupos de datos (spotlight, games,…) que contienen elementos. Además todo se organiza con un scroll horizontal en vez del típico scroll vertical al que estamos acostumbrados cuando los datos no entran en la pantalla. La estructura de este elemento es algo más compleja que un simple ListBox:

image

Como podemos observar, en el GridView la información se organiza en grupos que contienen una cabecera y elementos. Lo mejor de esto es que podemos definir el aspecto de la cabecera y el aspecto de los elementos, de hecho, según podemos ver en la imagen anterior, incluso podemos definir distintos aspectos para elementos dentro de un mismo grupo. Esto nos exige que los datos que enviemos a nuestra GridView estén agrupados. Vamos a definir unos datos de ejemplo, primero una clase que contendrá cada item y otra que creará los grupos:

  1:     public class ExampleData
  2:     {
  3:         public ExampleData()
  4:         {
  5:         }
  6: 
  7:         public string Title { get; set; }
  8: 
  9:         public string Image { get; set; }
 10: 
 11:         public string Description { get; set; }
 12:     }
 13: 
 14:     public class DataGroup
 15:     {
 16:         public DataGroup()
 17: 	    {
 18: 	    }
 19: 
 20:         public string GroupTitle { get; set; }
 21: 
 22:         public ObservableCollection<ExampleData> Items { get; set; }
 23:     }

Como podemos ver, ExampleData contiene un título, una descripción y una imagen, mientras que DataGroup nos ofrece un título para el grupo (que usaremos en la cabecera del mismo) y una colección de ExampleData.

Ahora en nuestra ViewModel vamos a crear datos de ejemplo:

  1:             Data = new ObservableCollection<DataGroup>()
  2:             {
  3:                 new DataGroup()
  4:                 {
  5:                     GroupTitle = "SciFi Movies",
  6:                     Items = new ObservableCollection<ExampleData>()
  7:                     {
  8:                         new ExampleData() 
  9:                         {
 10:                             Title = "Blade Runner",
 11:                             Description = "Deckard, a blade runner, ...",
 12:                             Image = "http://pics.filmaffinity.com/Blade_Runner-351607743-large.jpg"
 13:                         },
 14:                         new ExampleData()
 15:                         {
 16:                             Title = "Terminator 2",
 17:                             Description = "The cyborg who once tried to kill ...",
 18:                             Image = "http://pics.filmaffinity.com/Terminator_2_el_juicio_final-825143697-large.jpg"
 19:                         },
 20:                         new ExampleData()
 21:                         {
 22:                             Title = "Judge Dreed",
 23:                             Description = "In a dystopian future, ...",
 24:                             Image = "http://pics.filmaffinity.com/Juez_Dredd-447589318-large.jpg"
 25:                         }
 26:                     }
 27:     
 28:                 },
 29:                 new DataGroup()
 30:                 {
 31:                     GroupTitle = "SciFi Books",
 32:                     Items = new ObservableCollection<ExampleData>()
 33:                     {
 34:                         new ExampleData() 
 35:                         {
 36:                             Title = "Foundation",
 37:                             Description = "The Foundation Series is ...",
 38:                             Image = "http://isaac-asimov.com/wp-content/uploads/2012/01/Isaac-Asimov_1951_Foundation.jpg"
 39:                         },
 40:                         new ExampleData()
 41:                         {
 42:                             Title = "Ender's Game",
 43:                             Description = "Set in Earth's future, ...",
 44:                             Image = "http://www.hung-truong.com/blog/wp-content/uploads/2009/11/ender.gif"
 45:                         }
 46:                     }
 47:                 }
 48:             };

Con estos datos tenemos dos grupos, uno para películas y otro para libros, ambos de ciencia ficción. Para que el GridView pueda usar estos grupos tenemos que usar como fuente de datos un CollectionViewSource que podemos definir en nuestra página XAML:

  1: <Grid.Resources>
  2:     <CollectionViewSource x:Name="groupedItemsViewSource" Source="{Binding Data}" 
  3:                             IsSourceGrouped="true" ItemsPath="Items" />
  4: </Grid.Resources>

Simplemente le asignamos un nombre, le indicamos el binding a la propiedad Data de nuestra ViewModel y le decimos que está agrupado y cual es la propiedad que contiene los items de cada grupo. Ahora ya solo tenemos que añadir a nuestra página un GridView e indicarle que obtenga los datos del CollectionViewSource que hemos llamado groupedItemsViewSource:

  1: <GridView ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}">
  2: </GridView>

Si ejecutamos la aplicación el resultado será bastante pobre, una lista de nuestros items sin división y sin ningún formato. Esto es así por que necesitamos indicarle como debe crear y presentar los grupos mediante tres propiedades:

ItemsPanel, que definirá el modo de colocar los grupos:

  1: <GridView.ItemsPanel>
  2:     <ItemsPanelTemplate>
  3:         <StackPanel Orientation="Horizontal"/>
  4:     </ItemsPanelTemplate>
  5: </GridView.ItemsPanel>

GroupStyle.HeaderTemplate, que definirá el estilo de la cabecera de cada grupo:

  1: <GroupStyle.HeaderTemplate>
  2:     <DataTemplate>
  3:         <Grid Margin="1,0,0,6">
  4:             <Button AutomationProperties.Name="Group Title"
  5:                     Content="{Binding GroupTitle}"
  6:                     Style="{StaticResource TextButtonStyle}"/>
  7:         </Grid>
  8:     </DataTemplate>
  9: </GroupStyle.HeaderTemplate>

Y por último, GroupStyle.Panel, que definirá el panel donde se colocarán los elementos del grupo:

  1: <GroupStyle.Panel>
  2:     <ItemsPanelTemplate>
  3:         <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
  4:     </ItemsPanelTemplate>
  5: </GroupStyle.Panel>

Si una vez definidas estas propiedades ejecutamos nuestra aplicación ya podremos observar los grupos conteniendo sus respectivos elementos:

image

 

Algo de estilo…

Aunque todavía no se puede decir que sea demasiado espectacular, nos falta definir la propiedad ItemTemplate que indicará al GridView como mostrar cada elemento:

  1: <GridView.ItemTemplate>
  2:     <DataTemplate>
  3:         <Grid Width="200" Height="300">
  4:             <Rectangle Fill="White"></Rectangle>
  5:             <Image Source="{Binding Image}" Opacity=".5" Stretch="Fill"></Image>
  6:             <TextBlock Foreground="White" Text="{Binding Title}"
  7:                         VerticalAlignment="Bottom"></TextBlock>
  8:         </Grid>
  9:     </DataTemplate>
 10: </GridView.ItemTemplate>

Vamos a usar las propiedades Image y Title para mostrar información sobre nuestro elemento, el resultado final ahora:

image

Sin duda mucho mejor. Como hemos visto es muy fácil poder usar un elemento GridView para mostrar nuestros datos, pero… en el ejemplo de la Store, los elementos tenían diferentes aspectos, tamaño, etc… y en nuestro ejemplo son todos iguales. Esto podemos resolverlo usando una propiedad del GridView llamada ItemTemplateSelector la cual recibirá cada elemento y devolverá un DataTemplate que se usará para mostrarlo. lo primero que vamos a hacer es modificar nuestra clase ExampleData para que tenga una propiedad ItemType de tipo entero y que indicará el tipo 1 para las películas y el tipo 2 para los libros.

Ahora vamos a crear dos DataTemplate diferentes para nuestros elementos, una para películas y otra para libros:

  1: <DataTemplate x:Key="MoviesTemplate">
  2:     <Grid Width="300" Height="250" Background="#33CCCCCC">
  3:         <Grid.ColumnDefinitions>
  4:             <ColumnDefinition Width="160"></ColumnDefinition>
  5:             <ColumnDefinition></ColumnDefinition>
  6:         </Grid.ColumnDefinitions>
  7:                     
  8:         <Image Grid.Column="0" Source="{Binding Image}" Stretch="Fill"></Image>
  9: 
 10:         <TextBlock Grid.Column="1" Margin="10,10,0,0" Foreground="White" Text="{Binding Title}"
 11:                         FontWeight="Bold" FontSize="20"
 12:                         TextWrapping="Wrap">
 13:         </TextBlock>
 14:         <TextBlock Grid.Column="1" Margin="10,80,0,0" Foreground="White" Text="{Binding Description}"
 15:                         FontWeight="Light" FontSize="18"
 16:                         TextWrapping="Wrap" TextTrimming="WordEllipsis">
 17:         </TextBlock>
 18:     </Grid>
 19: </DataTemplate>
 20:             
 21: <DataTemplate x:Key="BooksTemplate">
 22:     <Grid Width="600" Height="180" Background="#CCFFD073">
 23:         <Grid.ColumnDefinitions>
 24:             <ColumnDefinition Width="100"></ColumnDefinition>
 25:             <ColumnDefinition></ColumnDefinition>
 26:         </Grid.ColumnDefinitions>
 27: 
 28:         <Image Grid.Column="0" Source="{Binding Image}" Stretch="Fill"></Image>
 29:                     
 30:         <TextBlock Grid.Column="1" Margin="10,10,0,0" Foreground="White" Text="{Binding Title}"
 31:                         FontWeight="Bold" FontSize="20"
 32:                         TextWrapping="Wrap">
 33:         </TextBlock>
 34:         <TextBlock Grid.Column="1" Margin="10,45,0,0" Foreground="White" Text="{Binding Description}"
 35:                         FontWeight="Light" FontSize="18"
 36:                         TextWrapping="Wrap" TextTrimming="WordEllipsis">
 37:         </TextBlock>
 38:     </Grid>
 39: </DataTemplate>

En la primera DataTemplate, MoviesTemplate, vamos a darle más importancia a la imagen, dejando menos espacio para texto. En la segunda DataTemplate, BooksTemplate, vamos a mostrar más información del texto del libro. Para decidir que DataTemplate asignar a cada elemento, vamos a crear una clase que herede de la base DataTemplateSelector del namespace WIndows.UI.Xaml.Controls y sobre escribimos el método SelectTemplateCore:

  1: public class TemplateSelector : DataTemplateSelector
  2: {
  3:     public DataTemplate MovieTemplate { get; set; }
  4:     public DataTemplate BookTemplate { get; set; }
  5: 
  6:     protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
  7:     {
  8:         if (item == null)
  9:             return null;
 10: 
 11:         ExampleData element = (ExampleData)item;
 12: 
 13:         if (element.ItemType == 1)
 14:             return MovieTemplate;
 15:         else
 16:             return BookTemplate;
 17:     }
 18: }

Lo primero que hacemos en esta clase es crear dos propiedades públicas de tipo DataTemplate en las que podemos indicar las DataTemplates que hemos creado previamente, luego en el método SelectTemplateCore recibimos el elemento que necesita una plantilla y el contenedor de ese elemento, es en este método en el que podemos decidir que plantilla usar.

¿El resultado? A continuación…

image

Mucho mejor así, cada tipo de elemento tiene su representación única, que podemos ajustar para resaltar las propiedades más importantes.

Respondiendo al usuario

Ahora que ya sabemos como trabajar con el estilo de nuestros elementos, es hora de ver como recibir órdenes desde los elementos. Tenemos que tener en cuenta dos situaciones: que un usuario haga un “Tap” (un click) en un elemento o que lo seleccione (con un click derecho o con el gesto de pulsar y arrastrar que usamos en el menú inicio). Es importante tener en cuenta que debemos manejar los eventos tanto con el ratón como con entrada táctil.

En este aspecto hay mucha controversia por internet en los foros sobre WinRT. El elemento GridView (y el ListView también) tiene algunos comportamientos extraños entre el evento click, la propiedad SelectedItem y el evento tapped/righttapped. Después de buscar y buscar, la solución pasa por un poco de code behind.

En primer lugar podemos indicar la propiedad SelectedItem del GridView para obtener el elemento seleccionado por el usuario haciendo uso del gesto de seleccionar o del click derecho del ratón:

  1: <GridView x:Name="grdView" ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}" SelectionMode="Single"
  2:             ItemTemplateSelector="{StaticResource GridViewTemplateSelector}"
  3:             SelectedItem="{Binding SelectedData, Mode=TwoWay}"
  4:             Margin="120,140,20,20">

De esta forma si probamos nuestra aplicación veremos que, tanto si hacemos click como click derecho, se selecciona un elemento y llega el mismo a nuestra propiedad SelectedData. Pero nosotros ahora queremos que al hacer click sobre un elemento no se seleccione, queremos que nos permita ejecutar, por ejemplo la navegación a los detalles de ese elemento. Aquí entra en funcionamiento el code behind, debemos manejar el evento Tapped de la grid del elemento (DataTemplate), ejecutar el código que deseemos y marcar el evento como Handled, de esta forma no se seguirá propagando y no se seleccionará ese mismo elemento:

  1: private async void element_tapped(object sender, TappedRoutedEventArgs e)
  2: {
  3:     e.Handled = true;
  4: 
  5:     MessageDialog dlg = new MessageDialog("Clicked!");
  6:     await dlg.ShowAsync();
  7: }

De esta forma al hacer Tap, tanto con ratón como con interface táctil obtendremos este mensaje y no se seleccionará el elemento:

image

Podemos evitar el uso de Code behind en cierta medida implementando un EventToCommand. En Silverlight o WPF era realmente sencillo mediante el uso de Behaviors y Triggers, pero en WinRT nada de esto está disponible. Esto hace que la implementación de EventToCommand sea algo más complicada, pero podemos encontrar un ejemplo muy bueno en el blog de Joost van Schaik, NET By example, que nos permitirá enlazar a un comando de nuestra ViewModel y pasar el elemento pulsado como parámetro del comando. Aunque tendremos que modificarlo para que controle que el evento que llegue sea un evento Tapped y lo marque como manejado.

Conclusión

El elemento GridView es la piedra central sobre la que podremos construir nuestras aplicaciones metro, es importante implementarlo y obtener todo su potencial, espero que este artículo os sirva para ello, aunque todavía quedan cosas muy potentes como es el zoom semántico que no hemos visto y que está íntimamente relacionado con el GridView. Como siempre, aquí tenéis un ejemplo de todo lo que hemos visto para que podáis jugar con el y tenerlo como base y referencia para vuestros desarrollos.

Un saludo y Happy Coding!

[Windows 8] Acceso al hardware (II): Sensores

Hola a todos!

Hace unos días, vimos como obtener y usar en un mapa de Bing la información del GPS de nuestra tablet Windows 8. Hoy vamos a dar un repaso al resto de sensores que tenemos a nuestra disposición en una aplicación metro. En otras plataformas, por ejemplo en Windows Phone, tenemos normalmente acceso a tres sensores: Acelerómetro, Brújula y Giroscopio. En Windows 8 sin embargo tendremos hasta 7 sensores a nuestra disposición:

  • Acelerómetro: Detecta la aceleración en los ejes X, Y, Z.
  • Brújula: Detecta cambios en la orientación del dispositivo.
  • Giroscopio: Detecta cambios en la velocidad angular.
  • Sensor de inclinación: Detecta cambios en el ángulo de inclinación
  • Sensor de luz: Detecta cambios en la luz ambiental.
  • Sensor de orientación: Combina las lecturas del acelerómetro, la brújula y el giroscopio para obtener datos de movimiento más precisos.
  • Sensor de orientación simple: Usa solo el acelerómetro para obtener la orientación del dispositivo como rotación.

Todos estos sensores se encuentran reunidos en el namespace Windows.Devices.Sensors y se usan de forma idéntica. Todos ellos tienen un método GetDefault, que nos devuelve la instancia por defecto de cada sensor o null si el sensor no se encuentra en el sistema:

  1: Windows.Devices.Sensors.Accelerometer acelerometer = Accelerometer.GetDefault();
  2: if (acelerometer != null)
  3: { 
  4:     //TODO: Add code to handle sensor here.
  5: }

Una vez que hemos obtenido la instancia de nuestro sensor, todos, a excepción del sensor de orientación simple, tienen una propiedad ReportInterval que se indica en milisegundos y que representa el tiempo que pasará entre actualizaciones. Internamente cada sensor convertirá este intervalo de tiempo en unidades propias de cambio, siguiendo esta tabla:

Sensor ReportInterval

1ms – 16ms

17ms – 32ms

>= 33ms

Acelerómetro

0,01G

0,02G

0,05G

Brújula

0,01 grados

0,5 grados

2 grados

Giroscopio

0,1 DPS (grados por segundo)

0,5 DPS (grados por segundo)

1,0 DPS (grados por segundo)

Sensor de inclinación

0,01 grados

0,5 grados

2 grados

Sensor de luz

1%

1%

5%

Sensor de orientación

0,01 grados

0,5 grados

2 grados

Todos además incluyen una propiedad de solo lectura llamada MinimumReportInterval que contiene el valor mínimo de intervalo soportado por el sensor.

Una vez configurado el intervalo, podemos obtener la lectura del sensor directamente, llamando al método GetCurrentReading, que nos devolverá la última lectura disponible del mismo:

  1: //Acelerometer
  2: Windows.Devices.Sensors.Accelerometer acelerometer = Accelerometer.GetDefault();
  3: if (acelerometer != null)
  4: {
  5:     AccelerometerReading read = acelerometer.GetCurrentReading();
  6: }
  7: 
  8: //Light Sensor
  9: Windows.Devices.Sensors.LightSensor lightSensor = LightSensor.GetDefault();
 10: if (lightSensor != null)
 11: {
 12:     LightSensorReading read = lightSensor.GetCurrentReading();
 13: }

O podemos suscribirnos al evento ReadingChanged del sensor, el cual nos devolverá datos cada vez que la lectura del mismo cambie, teniendo en cuenta el intervalo que le hayamos establecido. El segundo argumento de este evento nos devolverá la lectura del sensor y un timestamp indicando el momento exacto en el tiempo en el cual fue obtenida esa lectura:

  1: Windows.Devices.Sensors.OrientationSensor orientation = OrientationSensor.GetDefault();
  2: if (orientation != null)
  3: {
  4:     orientation.ReportInterval = orientation.MinimumReportInterval;
  5:     orientation.ReadingChanged += orientation_ReadingChanged;
  6: }
  7: 
  8: void orientation_ReadingChanged(OrientationSensor sender, OrientationSensorReadingChangedEventArgs args)
  9: {
 10:     DateTime readingDateTime = args.Reading.Timestamp.DateTime;
 11: 
 12:     var rotationX = args.Reading.Quaternion.X;
 13:     var rotationY = args.Reading.Quaternion.Y;
 14:     var rotationZ = args.Reading.Quaternion.Z;
 15:     var rotationW = args.Reading.Quaternion.W;
 16: }

En este aspecto hay cambios en dos sensores: El sensor de orientación simple nos devuelve un enumerado con la orientación de nuestro dispositivo, que puede ser:

  • NotRotated: El dispositivo está en vertical hacia arriba.
  • Rotated90DegreesCounterclockwise: El dispositivo está en horizontal hacia la izquierda.
  • Rotated180DegressCounterclockwise: El dispositivo está en vertical hacia abajo.
  • Rotated270DegressCounterclockwise: El dispositivo está en horizontal hacia la derecha.
  • Faceup: El dispositivo está colocado con la pantalla mirando al usuario.
  • Facedown: El dispositivo está colocado con la pantalla al lado contrario del usuario.

Esta lectura es muy útil, entre otras cosas, si queremos que nuestra aplicación deje de trabajar o, por ejemplo, de reproducir sonido o video cuando el usuario gira el tablet poniendo la pantalla contra la superficie.

También el acelerómetro nos ofrece un evento extra, llamado Shaken, que se lanza cuando el usuario ha sacudido el dispositivo, solo nos informa de que ha sucedido y en que momento de forma que solo tengamos que responder a esta acción de la forma que necesitemos sin tener que comprobar por nosotros si se está sacudiendo a partir de las lecturas individuales del acelerómetro:

  1: Windows.Devices.Sensors.Accelerometer acelerometer = Accelerometer.GetDefault();
  2: if (acelerometer != null)
  3: {
  4:     acelerometer.Shaken += acelerometer_Shaken;
  5: }   
  6: 
  7: void acelerometer_Shaken(Accelerometer sender, AccelerometerShakenEventArgs args)
  8: {
  9:     DateTime readingDateTime = args.Timestamp.DateTime;
 10: }

Como podéis ver, trabajar con los sensores es realmente sencillo, Microsoft ha unificado la forma de acceder a todos ellos para que nuestro trabajo se centre en crear una experiencia que responda a sus cambios, no en reinventar la rueda para poder integrarlos en nuestro código.

Espera, espera… ¿Como probamos esto? Pues mucho me temo que la respuesta es: comprándote una tablet que soporte 1º) Windows 8 y 2º) Todos estos sensores o al menos el que te interese. Lamentablemente el simulador de Windows 8 que se incluye con Visual Studio 2012 no nos aporta un emulador de los sensores, por lo que, al menos en mi PC que no tiene ni acelerómetro ni nada, todos los sensores devuelven null. Esperemos que esto se solucione y al menos incluyan algún emulador sencillo de acelerómetro y orientación que pueden ser los más interesantes.

Bueno, esto ha sido todo por hoy, en esta ocasión el ejemplo que os dejo es realmente todo el código que he puesto en el post, dentro del code behind de una página XAML para que podáis tenerlo como referencia, el ejemplo en sí no realiza ninguna función, porque no tengo sensores con los que jugar y enseñaros cosas chulas. Como sea, aquí podéis descargarlo para jugar con el.

Un saludo y Happy Coding!

[Windows 8] Acceso al hardware (I): GPS y Bing Maps SDK

Hola a todos!

Hoy vamos a comenzar a examinar el acceso al hardware del que disponemos en las aplicaciones metro para Windows 8. En primer lugar hablaremos del GPS, de como acceder a el y utilizar los datos que nos brinda.

Para que nuestra aplicación pueda hacer uso del Gps, lo primero que necesitamos hacer es declarar en el appxmanifest de nuestra aplicación, en la pestaña de capacidades, que deseamos hacer uso de la localización del usuario:

image

De esta forma tendremos los permisos adecuados para poder acceder a los datos de localización del sistema. Tenemos que tener en cuenta que aun así, por defecto es el usuario quien tendrá que hacer efectivo este permiso activando el acceso a la localización, desde los permisos de la aplicación:

image

Una vez que tenemos estas dos cosas claras, usar el Gps en nuestra aplicación es increíblemente sencillo. Para los ejemplos necesitaremos el SDK de Bing Maps para aplicaciones Metro, podemos instalarlo desde el menú TOOLS > Extensions and Updates, buscando en la sección Visual Studio Gallery por Bing Maps:

image

Una vez instalado, simplemente tenemos que ir a nuestra solución y añadir una referencia en la pestaña Windows > Extensions a Bing Maps y al runtime de Visual C++ para poder desplegar la aplicación en un dispositivo. Después de añadir las referencias, veremos que tenemos varios Warnings indicándonos que el processor architecture “AnyCPU” no está soportado y debemos cambiarlo a x86, x64 o ARM. Esto se debe a que el control de Bing Maps usa código no manejado y tenemos que compilar para cada arquitectura, en nuestro ejemplo lo pondremos en x86 accediendo al menú BUILD > Configuration Manager:

image

¿Porqué x86 y no x64? No es un capricho, el diseñador de XAML de Visual Studio no muestra la vista previa para las arquitecturas x64 y ARM, por lo que, por comodidad al trabajar, usamos x86.

Ahora ya solo nos queda agregar la referencia en XAML y añadir el mapa a nuestra página:

  1: <Page
  2:     x:Class="Win8Gps.MainPage"
  3:     IsTabStop="false"
  4:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  5:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  6:     xmlns:local="using:Win8Gps"
  7:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  9:     xmlns:map="using:Bing.Maps"
 10:     mc:Ignorable="d">
 11: 
 12:     <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
 13:         <map:Map x:Name="GpsMap"></map:Map>
 14:     </Grid>
 15: </Page>

Y con esto, si ejecutamos tendremos ya nuestro mapa de Bing, aunque con un feo mensaje en la pantalla:

image

Esto es porque necesitamos registrarnos y obtener una clase de desarrollador en https://www.bingmapsportal.com/, una vez registrados podremos ir al menú create or view keys y añadir nuestra aplicación para obtener una clave:

image

Al pinchar en Submit nos dará nuestra Key que podremos añadir a la propiedad Credentials del mapa para que no muestre el mensaje anterior:

  1: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
  2:     <map:Map x:Name="GpsMap" Credentials="TU_KEY_AQUI"></map:Map>
  3: </Grid>

Ahora que ya tenemos un punto en el que mostrar nuestra posición, para obtener la posición actual usaremos la clase Geolocator dentro del namespace Windows.Devices.Geolocation, que nos ofrece dos formas de trabajar. Un método async GetGeopositionAsync que podemos llamar y nos devuelve un objeto GeoCoordinates con la posición actual:

  1: Geolocator location = new Geolocator();
  2: 
  3: private async void GetActualPosition()
  4: {
  5:     Geoposition actualPosition = await location.GetGeopositionAsync();
  6: }

O suscribiéndonos al evento PositionChanged, que nos enviará las coordenadas actuales cada vez que estas cambien:

  1: Geolocator location = new Geolocator();
  2: 
  3: public MainPage()
  4: {
  5:     this.InitializeComponent();
  6: 
  7:     location.PositionChanged += location_PositionChanged;
  8: }
  9: 
 10: void location_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
 11: {
 12:     Geoposition actualPosition = args.Position;
 13: }

En ambos casos debemos tener en cuenta que el usuario puede activar o desactivar nuestro acceso a la localización o que podemos perder la señal Gps y no obtener datos, para estar al tanto de estos cambios, es necesario controlar el evento StatusChanged de la clase Geolocator:

  1: public MainPage()
  2: {
  3:     this.InitializeComponent();
  4:     location.StatusChanged += location_StatusChanged;
  5: }
  6: 
  7: void location_StatusChanged(Geolocator sender, StatusChangedEventArgs args)
  8: {
  9:     if (args.Status == PositionStatus.Ready)
 10:     {
 11:         location.PositionChanged += location_PositionChanged;
 12:     }
 13:     else if (args.Status == PositionStatus.Disabled)
 14:     {
 15:         location.PositionChanged -= location_PositionChanged;
 16:     }
 17: }

Para poder probar que todo esté funcionando correctamente, el simulador de Windows 8 nos da un simulador de Gps, donde podremos introducir latitud, longitud, altitud y radio de error y establecer la posición como si fuese el GPS Real:

image

Para incluir el marcador de posición que vemos en la imagen (el punto azul) necesitamos usar la clase Pushpin del namespace Bing.Maps, añadiéndolo como hijo del mapa:

  1: Pushpin pin = new Pushpin();
  2: public MainPage()
  3: {
  4:     this.InitializeComponent();
  5: 
  6:     GpsMap.Children.Add(pin);
  7: }

Y en el evento PositionChanged actualizar su posición en el mapa:

  1: void location_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
  2: {
  3:     Geoposition actualPosition = args.Position;
  4: 
  5:     this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => 
  6:     {
  7:         Location loc = new Location(args.Position.Coordinate.Latitude, args.Position.Coordinate.Longitude);
  8:         MapLayer.SetPosition(pin, loc);
  9:         GpsMap.SetView(loc, 15.0f);
 10:     }));
 11: }

Tenemos que tener en cuenta que el evento PositionChanged se ejecuta en un hilo distinto al de la interface de usuario, por lo que debemos hacer uso del Dispatcher para poder actualizar cualquier control, como en este caso hacemos con el mapa.

Si ejecutamos y jugamos con las coordenadas, veremos como se va moviendo la posición de la vista del mapa y el marcador que hemos incluido.

Conclusión

Y esto es todo por hoy, ahora ya sabemos como usar el control de Bing Maps, añadirle marcadores de posición y como obtener y usar nuestra propia posición, teniendo en cuenta los cambios de estado que se puedan provocar por acción del usuario o del ambiente. Como siempre, aquí podéis obtener este ejemplo en funcionamiento para que juguéis con el.

Un saludo y Happy Coding!

[Windows 8] IoC & MVVM

Hola a todos!

Uno de los puntos clave del patrón MVVM y donde he visto más formas de aplicarlo es en lo referente a la unión entre Views y ViewModels. Hay gente que las asigna en code behind, otros lo hacen referenciando directamente a la ViewModel en XAML o Cargan las ViewModels y hacen que las Views sean DataTemplates que se resuelvan mediante converters… realmente hay mil formas de hacerlo, algunas más acertadas que otras, pero todas válidas.

Tradicionalmente a mi me gusta usar una clase Locator, que se encargue de resolver la instancia del ViewModel y sus dependencias y exponerla mediante una propiedad pública para poder enlazarnos a ella mediante un Binding en XAML. Esto además contando con el apoyo de Unity para resolver las dependencias del ViewModel, se convierte en un sistema muy ágil y rápido, ofreciéndonos un punto único dentro de nuestra aplicación donde resolver todo lo necesario para que el ViewModel funcione.

Lamentablemente hasta hace relativamente poco tiempo, en WinRT no contábamos con ningún mecanismo de IoC o DI que nos ayudase con esta tarea, teniendo que relegar el trabajo en implementar nuestra clase Locator de una forma más “manual”. Supongamos que tenemos un ViewModel que depende de un servicio que contiene los métodos de acceso al Api del GPS, este sería:

  1: public class VMMainPage : VMBase
  2: {
  3:     private IGpsService gpsService = null;
  4: 
  5:     public VMMainPage(IGpsService gpsSrv)
  6:     {
  7:         gpsService = gpsSrv;
  8:     }
  9: 
 10:     public double ActualAltitude
 11:     {
 12:         get
 13:         {
 14:             return gpsService.GetActualAltitude();
 15:         }
 16:     }
 17: }

Y el servicio de GPS sería este:

  1: public interface IGpsService
  2: {
  3:     double GetActualAltitude();
  4: }
  5: 
  6: public class GpsService : IGpsService
  7: {
  8:     public double GetActualAltitude()
  9:     {
 10:         return 100.0;
 11:     }
 12: }

El usar la interface desde nuestro ViewModel nos va a permitir aislarnos del código del GPS (aunque en este caso es un simple “fake”) cuando realicemos pruebas unitarias o si decidimos compartir la ViewModel con una aplicación, por ejemplo, Windows Phone.

Para un ViewModel de este tipo, nuestra clase Locator tendría este aspecto:

  1: public class VMLocatorWithoutIoC
  2: {
  3:     private Lazy<VMMainPage> viewModelMainPage;
  4: 
  5:     public VMLocatorWithoutIoC()
  6:     {
  7:         viewModelMainPage = new Lazy<VMMainPage>(() => { return new VMMainPage(new GpsService()); });
  8:     }
  9: 
 10:     public VMMainPage ViewModelMainPage
 11:     {
 12:         get
 13:         {
 14:             return viewModelMainPage.Value;
 15:         }
 16:     }
 17: }

Con este código nuestro ViewModel se comportará como un Singleton, pues el tipo Lazy ejecutará el constructor la primera vez que accedamos a la propiedad ViewModelMainPage y a continuación siempre devolverá esa instancia.

Pero ahora tenemos disponible para nuestras aplicaciones WinRT MetroIoC, un contenedor de IoC, portado a WinRT a partir de MicroIoC y con una sintaxis muy cómoda. Lo más interesante de MetroIoC es que al ser un port de MicroIoC, el modo de uso de ambos es muy parecido y hasta que Microsoft se decida a portar oficialmente Unity, es la forma más comoda de incluir IoC en nuestra aplicación metro.

Para usar MetroIoC solo necesitamos acceder al Administrador de paquetes de NuGet e instalarlo desde allí:

image

Una vez que hayamos instalado MetroIoC, integrarlo en nuestro Locator será realmente sencillo, en primer lugar creamos nuestro contenedor:

  1: private MetroContainer container = new MetroContainer();

Y a continuación le añadimos nuestra ViewModel y sus dependencias:

  1: public VMLocatorWithMetroIoC()
  2: {
  3:     container.Register<VMMainPage>()
  4:         .RegisterInstance<IGpsService>(new GpsService());
  5: }

De esta forma, en nuestra propiedad pública solo tenemos que pedir al contenedor que resuelva el tipo que deseamos devolver:

  1: public VMMainPage ViewModelMainPage
  2: {
  3:     get
  4:     {
  5:         return container.Resolve<VMMainPage>();
  6:     }
  7: }

Aquí podemos configurar más cosas, por ejemplo podemos indicar al contenedor si al resolver el tipo VMMainPage debe conservar una instancia (SingletonLifecycle) o crear una instancia cada vez que la solicitemos (TransientLifecycle) Lo que manualmente nos abría ocupado algunas líneas extra de código aquí podemos indicarlo simplemente usando un parámetro del método Register de nuestro contenedor.

El uso es igual de sencillo que en el primer caso, añadimos la clase VMLocatorWithMetroIoC como un recurso en nuestro archivo app.xaml:

  1: <Application.Resources>
  2:     <ResourceDictionary>
  3:         <ResourceDictionary.MergedDictionaries>
  4: 
  5:             <!-- 

  6:                 Styles that define common aspects of the platform look and feel
  7:                 Required by Visual Studio project and item templates
  8:                 -->
  9:             <ResourceDictionary Source="Common/StandardStyles.xaml"/>
 10:         </ResourceDictionary.MergedDictionaries>
 11:         <Locator:VMLocatorWithoutIoC x:Key="LocatorWithoutIoC"></Locator:VMLocatorWithoutIoC>
 12:         <Locator:VMLocatorWithMetroIoC x:Key="LocatorWithIoC"></Locator:VMLocatorWithMetroIoC>
 13:     </ResourceDictionary>
 14: </Application.Resources>

Y en nuestra página añadirlo al DataContext:

  1: <Page
  2:     x:Class="Win8IoC.MainPage"
  3:     IsTabStop="false"
  4:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  5:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  6:     xmlns:local="using:Win8IoC"
  7:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  9:     mc:Ignorable="d"
 10:     DataContext="{Binding ViewModelMainPage, Source={StaticResource LocatorWithIoC}}">
 11: 
 12:     <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
 13:         <TextBlock Text="{Binding ActualAltitude}"
 14:                    FontSize="36"
 15:                    VerticalAlignment="Center"
 16:                    HorizontalAlignment="Center">
 17:         </TextBlock>
 18:     </Grid>
 19: </Page>

De esta forma resolvemos la ViewModel mediante IoC, desde un único punto en toda nuestra aplicación y conseguimos que modificar sus dependencias y como se resuelven, sea realmente sencillo. Como siempre aquí os dejo el código del ejemplo para que juguéis con el. A partir de ahora le vamos a dar mucho uso en el blog, vamos a empezar a ver como acceder a los sensores del dispositivo desde nuestra aplicación y para ello haremos un uso intensivo de las dependencias en nuestros ViewModels.

Un saludo y Happy Coding!

[Windows 8] Bases de datos locales en aplicaciones metro (II)

Hola a todos!

En la primera parte de esta serie sobre bases de datos locales vimos como instalar y usar SQLite en nuestras aplicaciones Metro, en esta segunda parte vamos a examinar al siguiente contendiente, SiaqoDb.

SiaqoDb

image

SiaqoDb es una base de datos manejada basada en objetos para .NET que usa Linq de forma nativa como lenguaje de consulta. No tiene soporte para sentencias SQL, lo cual esta muy bien, te aleja de la tentación de usarlas, pero si por alguna razón necesitas hacer uso de nuestro viejo amigo SQL, esta base de datos no es para ti.

Otra ventaja de SiaqoDb es su multiplataforma, al estar construida en .NET soporta Silverlight/WPF/WinForms, Windows Phone 7, Compact Framework así como Mono/MonoTouch y MonoDroid.

Consideraciones

Recientemente además han publicado una versión beta para aplicaciones Metro, si bien avisan de que no está lista para entrar en producción, es una de las primeras empresas en crear una base de datos viable y comercial para WinRT.

Una vez dicho todo esto, empecemos!

Setup

Al igual que en el caso de SQLite, SiaqoDb no viene por defecto incluida en WinRT o Windows 8 por lo que tendremos que descargarnos el componente a incluir en nuestra aplicación, al igual que SQLite o SQL Server CE estamos tratando con una base de datos “in process” con lo que no usa ningún servidor externo y la configuración debería limitarse a incluir todos los requisitos necesarios en nuestra aplicación.

Para empezar, necesitarás ir a la página de descargas de SiaqoDb y descargar la versión Beta para Windows 8 Metro, aquí

Una vez tengas el zip descargado al descomprimirlo encontraremos tres carpetas: Bin, que contiene el componente WinRT que necesitaremos añadir en nuestra aplicación, MetroExample, con un ejemplo completo y funcional de como implementar SiaqoDb y por último SiaqoDbManager, un gestor de base de datos para SiaqoDb que nos permite escribir consultas Linq y consultar nuestras bases de datos. Para iniciar esta última aplicación deberemos indicar la licencia trial que podemos solicitar en la página de descargas. Recuerda que SiaqoDb es de pago y tienes una licencia trial totalmente funcional de 30 días. Esta licencia también será necesaria cuando empecemos a usar nuestra base de datos en código como veremos a continuación.

Ahora vamos a crear un nuevo proyecto metro en Visual Studio 2012 y vamos a hacer click derecho y seleccionamos la opción “Add Reference”, en la pantalla “Reference Manager” haz click en el botón “Browse…” en la parte inferior y navega hasta el directorio Bin que acabamos de descomprimir, donde encontrarás un componente de WinRT llamado SiaqoDbWinRT.dll, selecciónalo y haz click en aceptar, a continuación ya lo tendrás listo en la pantalla de referencias y solo te quedará presionar “OK”:

image

Esta es toda la configuración que necesitamos para usar SiaqoDb en nuestro proyecto, a continuación vamos a crear nuestra base de datos.

Usando SiaqoDb

Para comenzar a trabajar con toda base de datos, lo primero que necesitamos es definir los objetos que vamos a almacenar. SiaqoDb no es una base de datos relacional al uso, con un esquema sobre el cual insertamos líneas en tablas, en lugar de esto nos permite almacenar objetos de cualquier tipo que luego podremos consultar y recuperar mediante LinQ, para ello usaremos clases decoradas con atributos que podemos encontrar en el namespace Sqo.Attributes:

  1: public class Customer
  2: {
  3:     public int OID { get; set; }
  4: 
  5:     [MaxLength(100)]
  6:     public string Name { get; set; }
  7: 
  8:     [MaxLength(256)]
  9:     public string Address { get; set; }
 10: }

Existe una particularidad a la hora de crear nuestras clases, SiaqoDb siempre busca una propiedad pública llamada OID de tipo entero de 32bits donde almacenará el identificador de objeto único, esto no evita que nosotros creemos índices adicionales, pero este siempre debe estar presente.

Y ya podemos empezar a usar nuestra base de datos, insertando nuestros objetos:

  1: private async void InitializeDb()
  2: {
  3:     SiaqodbConfigurator.SetTrialLicense("nuestro_codigo_de_licencia");
  4: 
  5:     Siaqodb db = new Siaqodb();
  6:     await db.Open(Windows.Storage.ApplicationData.Current.LocalFolder);
  7: 
  8:     Customer customer = new Customer() { Name = "Josué Yeray", Address = "Bilbao" };
  9: 
 10:     await db.StoreObject(customer);
 11:     await db.Flush();
 12:     db.Close();
 13: }

Iniciamos la base de datos indicando el directorio en el que se guardará (en este caso el directorio de datos de la aplicación) y usamos el método StoreObject para guardar instancias de nuestros objetos. Para finalizar, usamos el método Flush para escribir nuestros datos en disco y cerramos la conexión.

Si abrimos la aplicación SiaqoDbManager e indicamos la ruta de nuestra carpeta de datos, podremos ver los objetos almacenados en ella:

image

 

Si modificamos la propiedad Name o Address podremos consultarlas en nuestro código:

  1: private async void QueryDb()
  2: {
  3:     SiaqodbConfigurator.SetTrialLicense("nuestro_codigo_de_licencia");
  4: 
  5:     Siaqodb db = new Siaqodb();
  6:     await db.Open(Windows.Storage.ApplicationData.Current.LocalFolder);
  7: 
  8:     var customer = await (from Customer c in db select c).ToListAsync();
  9: 
 10:     if (customer.Any())
 11:     {
 12:         MessageDialog dlg = new MessageDialog(customer.FirstOrDefault().Name);
 13:         await dlg.ShowAsync();
 14:     }
 15: 
 16:     db.Close();
 17: }

Si ejecutamos veremos que obtenemos el resultado esperado. En este caso uso el método ToListAsync, cuando en realidad podría usar el FirstOrDefaultAsync porque este último no funciona correctamente todavía en todas las situaciones (recordemos que nos encontramos ante una beta).

image

Conclusiones

Como hemos podido ver, SiaqoDb tiene mucho potencial, es muy sencillo de usar y está muy bien planteada. Como todo, aun no es perfecta, tiene algunas cosas que debemos tener en cuenta:

  1. Es de pago, aun no sabemos los precios de WinRT (la trial de WinRT dura 90 días en vez de 30) pero de media cuesta 189$ por desarrollador y producto, esto quiere decir que si deseamos usarla en Windows Phone, WPF y Metro, tendríamos que pagar 3 licencias por cada desarrollador. Dada la calidad que aporta y el soporte que tiene detrás no creo que este sea un precio desorbitado para cualquier desarrollador freelance que gane dinero por su trabajo o cualquier empresa. Quizás es un punto de inflexión para un desarrollador solitario que esté comenzando y no tenga ingresos fijos. Aun así, creo que está justificado el coste, no me parece excesivo y es un buen producto.
  2. Multiplataforma a medias, no tenemos clientes nativos para Linux o Mac y en Android o iOS dependemos de usar Mono. Esto puede ser un problema y conlleva un gasto extra en licencias de MonoTouch y MonoDroid.

Pero en la otra cara, hay que reconocer que tiene grandes ventajas:

  1. El proceso de empezar a trabajar es mucho más sencillo que con SQLite, además el componente es nativo de WinRT por lo que no tendremos que preocuparnos de compilar distintas versiones para x64, x86 y ARM
  2. El modelo de desarrollo, haciendo uso de async / await es realmente cómodo
  3. No tenemos que preocuparnos por caracteres especiales en el path de la base de datos como ocurre en SQLite.

En definitiva, creo que SiaqoDb es, en estos momentos, la mejor base de datos que tenemos disponible para WinRT, de pago eso si, y con menos opciones de multiplataforma. Pero si estos dos aspectos no te asustan, su forma de trabajar y lo bien que está creada hacen que valga la pena y mucho. Aquí tienes como siempre el proyecto de ejemplo que hemos usado para que puedas jugar con el. En un próximo artículo intentaré realizar una comparación de rendimiento para ver cual de nuestros dos contrincantes aguanta mejor el tipo, no te lo pierdas.

Un saludo y Happy Coding!

[Windows 8] Bases de datos locales en aplicaciones metro (I)

Hola a todos!

Hoy vamos a ver como está de resuelto el escenario de bases de datos locales en las aplicaciones metro con Windows 8. En Windows Phone disponíamos “out of the box” de SQL Server CE pero en Windows 8 no tenemos esta suerte. Ahora mismo hay dos opciones principales, cada una con sus pros y sus contras: SQLite y SiaqoDb. En este artículo no voy a entrar a consideraciones de rendimiento. Voy a introducir cada base de datos, ver como poder empezar a desarrollar con ella y luego analizar lo bueno y lo malo de lo que hayamos visto.  Tenemos que recordar que estamos ante un sistema operativo y un SDK Beta, por lo que no podemos pedirle peras al olmo y existen fallos que, espero y confío, se subsanarán en una futura versión RTM.

Dicho esto, empecemos con el primer participante, SQLite

SQLite

SQLite Logo

Con la versión 3.7.13 SQLite añadió soporte oficialmente para WinRT y las aplicaciones metro, el objetivo de este artículo es dar los primeros pasos para ver como usar bases de datos locales en nuestros proyectos Metro.

Consideraciones

 

 

 

SQLite es un componente nativo, no un ensamblado manejado de WinRT, esto nos obligará a tener que distribuir diferentes paquetes de nuestra aplicación, pues necesitaremos una dll de SQLite para ARM, otra para x64 y otra para x86. Actualmente, en la web de descargas de SQLite solo tenemos acceso a la dll de x64, si necesitamos la de ARM o la de x86 tendremos que descargarnos el código fuente y compilarla nosotros mismos, esto por si solo daría para varios largos artículos, por lo que os dejo un vídeo de Tim Heuer en el que explica muy bien como hacerlo. Una vez dicho esto, podemos empezar!

Setup

Por defecto SQLite no viene incluido en Windows 8 como pasa con SQL Server CE en Windows Phone, por lo que tendremos que descargarnos los componentes necesarios para hacerlo funcionar.

En primer lugar necesitas el binario de SQLite 3.7.13 que puedes descargar aquí, guárdalo en algún lugar de tu disco duro a buen recaudo, lo usaremos más adelante.

A continuación, abre Visual Studio 2012 y crea un nuevo proyecto Metro, haz click derecho sobre él en el explorador de soluciones, escoge la opción “Manage NuGet Packages…” y busca el componente sqlite-net, un ORM creado a medida para SQLite y que ya es compatible totalmente con WinRT, además de hacer uso de la TPL (Task, async, await) para trabajar con la base de datos, lo cual es un gran extra:

image

Solo tienes que hacer click en el botón install y se añadirán dos clases a tu proyecto: SQLite.cs y SQLiteAsync.cs .

Ahora solo necesitamos incluir la dll de SQlite 3 en nuestro proyecto, como se trata de un componente nativo y no de un ensamblado, no lo añadiremos como una referencia, lo añadiremos directamente como contenido en nuestro proyecto (Add > Add Existing Item):

image

Una vez añadido, establecemos su Build Action a Content y Copy To Output Directory a Copy if newer.

Ya estamos listos para trabajar con SQLite en nuestra aplicación metro!

Usando SQLite-net

Primero definamos una tabla, ¿Como? Pues de una manera conocida, usando una clase decorada con atributos y que contenga propiedades públicas que actuarán como campos de la tabla, en nuestro caso una tabla sencilla, Customers:

  1: public class Customer
  2: {
  3:     [PrimaryKey, AutoIncrement]
  4:     public int id { get; set; }
  5: 
  6:     [MaxLength(100)]
  7:     public string Name { get; set; }
  8: 
  9:     [MaxLength(256)]
 10:     public string Address { get; set; }
 11: }

Todos estos atributos los encontramos definidos en el namespace SQLite.

Ahora es momento de realizar el código necesario para crear nuestra base de datos y sus tablas, para esto haremos uso del soporte incluido en sqlite-net para el uso asíncrono de nuestra base de datos:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5: }

De esta forma tan sencilla creamos la base de datos. Como se puede observar le pasamos la ruta completa donde deseamos crear la base de datos, usando el almacenamiento de datos de nuestra aplicación. En esta parte nos encontraremos con un grave problema, al menos bajo mi punto de vista: La ruta que usemos para la base de datos no puede contener carácteres no standard, como tildes, cedillas u otros por el estilo. Por ejemplo, en mi caso la ruta incluye mi nombre de usuario, que es “JosuéYeray” con tilde. al intentar crear la base de datos nos encontramos con el siguiente error:

image

Esto pinta feo, la excepción tampoco es muy descriptiva, pero después de bucear algunos días por internet, encontré la solución, usar una ruta sin tildes ni ningún otro carácter especial, con lo que me cree un usuario “Dummy” y todo funciono como la seda, pero depender de como esté escrito el nombre del usuario que ejecuta la aplicación es, cuanto menos, peligroso. ¿Podríamos usar otro path para la base de datos? En principio estamos restringidos al path de datos de nuestra aplicación que por defecto se encuentra bajo nuestro usuario. Esperemos que lo arreglen en siguientes versiones y si no, tenemos el código fuente a nuestra disposición, ¿Quién dijo miedo?

A continuación, si no hemos huido espantados con el error anterior, añadimos una tabla de tipo Customer. el resultado será el número de entradas generadas en el esquema de base de datos. ¿Qué pasa si la tabla ya existe? CreateTableAsync ejecuta internamente una operación “Create if not exist”, por lo que si la tabla ya existe no realizará ninguna acción.

Ahora ya podemos empezar a insertar registros en nuestra tabla Customer:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5:                 
  6:     Customer c = new Customer();
  7:     c.Name = "Josué Yeray";
  8:     c.Address = "Bilbao";
  9: 
 10:     int insertedItems = await db.InsertAsync(c);
 11: 
 12:     if (insertedItems > 0)
 13:     {
 14:         MessageDialog mDlg = new MessageDialog(string.Format("New inserted id: {0}", c.id));
 15:         await mDlg.ShowAsync();
 16:     }
 17: }

Meridianamente simple, rellenamos una instancia de nuestra tabla Customer y llamamos al método InsertAsync, que nos devolverá el número de registros insertados.

Y por último pero no menos importante, como recuperar datos guardados, gracias a Linq, más fácil todavía:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5:                 
  6:     Customer c = new Customer();
  7:     c.Name = "Josué Yeray";
  8:     c.Address = "Bilbao";
  9: 
 10:     int insertedItems = await db.InsertAsync(c);
 11: 
 12:     if (insertedItems > 0)
 13:     {
 14:         MessageDialog mDlg = new MessageDialog(string.Format("New inserted id: {0}", c.id));
 15:         await mDlg.ShowAsync();
 16: 
 17:         var customer = await db.Table<Customer>().Where(cust => cust.Name == "Josué Yeray").FirstOrDefaultAsync();
 18: 
 19:         if (customer != null)
 20:         {
 21:             mDlg.Content = string.Format("Customer Name: {0}, Address: {1}", customer.Name, customer.Address);
 22:             mDlg.ShowAsync();
 23:         }
 24:     }
 25: }

Haciendo uso de Linq podemos consultar la tabla que nos interese haciendo uso del objeto Table<T> que expone sqlite-net. También nos permite ejecutar consultas SQL si lo necesitamos:

  1: int NumCustomers = await db.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM Customer");

Pero recuerda, cada vez que usas SQL en línea en vez de usar Linq, dios mata un gatito, así que no lo uses, piensa en los gatitos.

Conclusiones

Aunque todavía tenemos que esperar a ver como se comporta SiaqoDb, hay varias cosas que no me gustan en SQLite:

  1. Nos obliga a generar un paquete de nuestra aplicación por cada plataforma (x86, x64, ARM) aunque una vez en la tienda de Windows es transparente para el usuario.
  2. El fallo de los caracteres especiales en la ruta del path de la base de datos es realmente peligroso.
  3. Permite ejecutar sentencias SQL, para algunos esto será bueno, pero para mi es una aberración que no debería permitirse.

Pero a cambio, tiene algunas cosas muy buenas también:

  1. Me gusta como han implementado el soporte para TPL, el uso de async y await hace muy cómodo trabajar con la base de datos sin bloquear la aplicación.
  2. La multiplataforma: Linux, Mac OS X, Windows, iOS, Android, Symbian… es muy posible que si estás portando una app de otra plataforma, uses SQLite, al mismo tiempo, si piensas portar tu app Windows 8 a otra plataforma, podrás usar SQLite.

La última duda que tengo, es si será compatible con Windows Phone 8, supongo que para saber algo todavía tendremos que esperar un poco más!

Como siempre, aquí os dejo el código fuente de ejemplo que he usado en el artículo para que podáis trastear con el un poco. Espero que os haya resultado interesante!

Un saludo y Happy Coding!