Migrar código de Windows 8 a Windows Phone 8

Como he comentado en alguna entrada anterior, recientemente tuvimos la ocasión de liberar aTareado para Windows 8.

Una de las primeras tareas a llevar a cabo, incluso antes de abordar nuevas funcionalidades, consiste en re-organizar el código y dejarlo preparado para poder sacar el máximo provecho en Windows Phone 8 al trabajo realizado en Windows 8. De esta manera, las nuevas funcionalidades estarán disponibles con más rapidez en ambos sistemas y podremos disponer de una versión en ambos sistemas con la mayor cantidad de código compartido posible.

aTareado surgió como un ejercicio de aprendizaje para crear aplicaciones de la Tienda de Aplicaciones de Windows, así que algunas decisiones de diseño (y me refiero a diseño técnico de la aplicación) han sido tomadas sin tener más propósito que el mero aprendizaje. En consecuencia, tras revisar un poco dichas decisiones, hay algunos problemas que habrá que tratar (en concreto para aTareado, que es muy sencillo, la lista puede variar de una aplicación a otra)

  • El modelo de la vista está en el mismo proyecto que la vista, lo que va a dificultar la reutilización del mismo en un proyecto de Windows Phone. Será necesario sacarlo a una biblioteca portable para su reutilización en ambas versiones.
  • El almacenamiento se encuentra demasiado acoplado a la fuente de datos. En Windows 8 y Windows Phone 8 el sistema de almacenamiento es diferente, con lo que este acoplamiento es bastante poco recomendable. Habrá que crear abstracciones para el repositorio que nos permita sacar versiones diferentes para ambos sistemas operativos.
  • El modelo de la vista hace uso de clases comunes que están demasiado relacionadas con clases de utilidad de Windows 8, así que para reutilizar el modelo de la vista en una versión de Windows Phone 8 habrá que crear algún tipo de abstracción para definir funcionalidades generícas, existentes en sistemas operativos diferentes.

Por otro lado, hay algunas decisiones que pueden simplificar la reutilización del código entre las versiones de Windows 8 y Windows Phone 8.

  • Parte del aprendizaje estaba enfocado en el patrón de diseño MVVM con lo que ya existe una separación clara entre la vista y el modelo de la vista, lo cual facilitará reutilizar este último en una versión de Windows Phone 8.
  • De entre de las posibles implementaciones de MVVM escogimos MVVM Light, que tiene versiones portables que pueden reutilizarse con facilidad en Windows 8 y Windows Phone 8.
  • La implementación de MVVM hace uso extensivo del patrón de diseño de Inversión de Control (IoC) lo que simplifica enormemente aprovechar abstracciones ya creadas para implementar versiones en sistemas operativos diferentes sin necesidad de tocar demasiado el código. Además la utilización de este patrón simplifica las pruebas unitarias y con poco esfuerzo podremos aprovechar el trabajo ya hecho para probar las clases nuevas que creemos para la versión de Windows Phone 8.
  • Para la serialización de datos se ha utilizado la biblioteca Newtonsoft.Json que también tiene sus versiones portables.

Reutilización W8 /WP8

La siguiente lista no es exahustiva, de hecho se limita a enumerar las consideraciones que hemos tenido que tener en cuenta al tratar de sacar el máximo provecho al código de aTareado.

Separación de Responsabilidades en la Vista

Si quieres sacarle el máximo provecho a tu aplicación en ambos mundos, empieza planteándote que un patrón de diseño que separe claramente la presentación de la lógica de presentación va ser de enorme ayuda, al fin y al cabo el XAML de Windows 8 y el XAML de Windows Phone 8 no son iguales. En este mundillo, esto lo consigues (con más o menos facilidad) utilizando MVVM.

Es buena idea que te lleves el modelo y la vista del modelo a bibliotecas portables de clases ya que con ese sencillo planteamiento ya tienes una buena base que compartir entre tus diferentes proyectos (W8 y WP8)

Es decir, partimos de algo parecido a esto:

image

Para terminar en algo similar a esto otro:

image

En realidad, parece que la cosa se ha complicado mucho (y es cierto) pero sólo porque el punto de partida era casi trivial.

A la hora de llevarnos el modelo y el modelo de la vista a bibliotecas portables partiendo de una versión en W8, la utilización de la versión portable MVVM Light ha sido de gran ayuda, pero tampoco ha estado exenta de problemas. El mayor problema encontrado aquí deriva de un pequeño bug en NuGet.

Cuando haces referencia a la MVVMLight (PCL) a través de NuGet, se realiza un cambio en la configuración de tu biblioteca de clases introduciendo una referencia al Runtime que no debería de introducirse. La solución es sencilla, simplemente elimina dicha referencia del fichero de configuración (puedes comprobar más detalles aquí).

Abstracción de Funcionalidades del SO

Por otro lado la utilización de patrones como IoC favorece enormemente el desacoplamiento entre clases. Así que, es una magnífica idea que construyas una biblioteca de clases portable donde definir todas las abstracciones (interfaces, fundamentalemente) y una biblioteca específica por Sistema Operativo donde implementen los servicios que se definen en la biblioteca de abstracciones. Tal y como se indicada en el gráfico anterior, parece que la cosa se ha complicado bastante. Pues bien, se ha complicado, pero esta complicación no es muy grande y simplifica enormemente el mantenimiento de ambas versiones.

Si ya estás utilizando MVVM Light en tus proyectos, seguro que estás familiarizado con la clase ViewModelLocator que es la que hace un uso exahustivo de los contenedores y localizadores de dependencias; además se encarga de registrar la correcta implementación del modelo de la vista. El ViewModelLocator puede registrar también las correctas implementaciones de las abstracciones (es decir, el ViewModelLocator registrará las versiones de W8 o de WP8 de dichas abstracciones). Hay un buen ejemplo aquí, donde pueden encontrarse ejemplos simples de estos conceptos.

Mensajes, mensajes, mensajes

Como sabéis, MVVM consigue desacoplamiento mediante un par de elementos fundamentales. Uno es el DataBinding y el otro es el itercambio de Mensajes entre los diferentes elementos del diseño.

No solo se utilizan como parte del DataBinding para avisar a la Vista que una propiedad ha cambiado, también pueden (deben) ser utilizados para comunicar algunos objetos sin que haya acoplamiento entre ellos.

Un Ejemplo de Mensaje

Supongamos que necesito un temporizador que dispare un evento de forma periódica. Cada sistema operativo suele disponer de clases que implementan estos temporizadores, además estas clases suelen venir por “duplicado” según estén diseñadas para interactuar con las vistas (ya que se ejecutan en el hilo de UI) o bien si están diseñadas para temporizar alguna acción que ha de ejecutarse en un hilo separado de la presentación.

Para evitar un diseño complicado y reutilizable en dos sistemas operativos diferentes, es perfectamente posible definir una clase que represente un mensaje de temporización. MVVM Light contiene una clase encargada de propagar mensajes de manera muy sencilla y desacoplada. Así pues, si nuestro modelo de vista tiene que reaccionar a mensajes del temporizador, simplemente lo subscribiremos a dichos mensajes y en la implementación específica del sistema operativo, nos limitaremos a lanzar dicho mensaje en cada pulsación del temporizador. De esta manera, el temporizador estará definido en una biblioteca de clases específica para el sistema operativo y la comunicación con el modelo de la vista se realizará, de forma desacoplada, a través del Messenger.

Este tipo de diseños permiten el desacoplamiento, pero vienen a un coste en la mantenibilidad ya que es muy difícil dar seguimiento a qué objetos se suscriben/desuscriben a qué mensajes y bajo qué condiciones lo hacen. De esta manera realizara pruebas unitarias que validen este comportamiento no es trivial.

En Resumen

Conseguir una versión “portable” de aTareado no ha sido complicado, pero ha requerido cierto esfuerzo de pruebas y refactorización. Las mayores dificultades han venido de un par de problemas relacionados uno con el diseño del código de aTareado y otro con un bug en NuGet, la mayor parte del trabajo ha venido a la hora de probar todo de nuevo.

Si tienes previsto crear una aplicación para WP8 o para W8, antes de comenzar hazte una sencilla pregunta. ¿La quiero para ambos sistemas operativos? Si la respuesta es sí, toma como referencia estos sencillos pasos a la hora de hacer el diseño y te simplificará la vida. Si la respuesta es no, hazte otra pregunta: ¿Seguro?

Windows Phone 7.5–Manejando llamadas a Servicios

Mientras preparo un post de un tamaño bastante considerable (ya me está llevando algo de tiempo documentarme para completarlo) he decidido sacar una pequeña entrada con algo de información que me ha resultado bastante útil en el proyecto personal que voy llevando a cabo.

Accediendo a FourSquare

Una parte fundamental del proyecto es el acceso al API de FourSquare. Este API está basada en un conjunto de servicios REST muy bien diseñados y fáciles de comprender (bueno, al menos en cierta parte).

image

Uno (entre muchos) de los aspectos interesantes del API es el soporte a varios idiomas. Según indica la documentación, existen dos formas para solicitar las respuestas en uno de los idiomas soportados por FourSquare.

Una de ellas es especificar un parámetro (locale=xx) dentro de la petición HTTP, sin embargo el método favorito es utilizar una cabecera HTTP (Accept-Language). Así que, sin dudarlo, y aprovechando que la infraestructura que monté para llamar a los diferentes end-points de FourSquare me lo permitía con facilidad, me decidí a incluir la cabecera para el idioma.

Todos los end-points heredan de una clase base llamada FourSquareEndPointBase.cs, donde se gestiona de forma asíncrona las peticiones y respuestas realizadas por la aplicación. Entre las responsabilidades de esta clase está, lanzar asíncronamente la petición al end-point que especifique la clase hija, recibir la información desde FourSquare, comprobar el estado de la llamada y pasarle a la clase hija la respuesta jSon para que ella se encargue de Deserializar dicho jSon a las clases de entidad necesarias.

La siguiente porción de código pertenece al método que realiza asíncronamente la llamada al end-point de FourSquare.

   1: protected void BeginRequest(Uri url, string method)

   2: {

   3:    HttpWebRequest request = WebRequest.CreateHttp(url);

   4:  

   5:    request.Headers["Accept-Language"] = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;

   6:    request.Method = method;

   7:  

   8:    CallbackResult asyncResult = new CallbackResult

   9:    {

  10:        Request = request,

  11:        JsonResponse = null

  12:    };

  13:  

  14:    request.BeginGetResponse(_callback, asyncResult);

  15: }

Como podéis ver, la cabecera HTTP “Accept-Language” se informa con el idioma actual del hilo de interfaz de usuario (el método de la petición puede ser GET o POST en esta versión del framework de acceso a FourSquare que he montado).

Hasta aquí no hay ningún misterio. Mirando el código me acordé de un proyecto que realicé años atrás en el antiguo PocketPC Phone Ed. 2003. Y, me lancé a mirar si:

  1. ¿Me permitirá FourSquare me permitiría pedir respuestas comprimidas?
  2. ¿Me permitirá Windows Phone descomprimir respuestas en caso de que me lleguen comprimidas?

Solicitando Compresión en las respuestas de FourSquare (y de quien quiera darlas)

El proceso para solicitar una respuesta comprimida es muy similar al descrito un poco más arriba.

   1: protected void BeginRequest(Uri url, string method)

   2: {

   3:     HttpWebRequest request = WebRequest.CreateHttp(url);

   4:  

   5:     request.Headers["Accept-Language"] = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;

   6:     request.Headers["Accept-Encoding"] = "gzip";

   7:     request.Method = method;

   8:  

   9:     CallbackResult asyncResult = new CallbackResult

  10:     {

  11:         Request = request,

  12:         JsonResponse = null

  13:     };

  14:  

  15:     request.BeginGetResponse(_callback, asyncResult);

  16: }

Como podéis ver, he añadido una segunda cabecera HTTP en la que indico que acepto como codificación de la respuesta, una codificación comprimida como “gzip”.

El siguiente paso es ver si FourSquare responde comprimiendo o no…

image

En la captura de pantalla se aprecia, inspeccionando el correspondiente objeto con el depurador, que la respuesta contiene una cabecera “Content-Encoding” y que su contenido es “gzip” lo que significa que: ¡FourSquare admite compresión!

Bien, acabo de estropear todo mi código. Ya que, las clases utilizadas para deserializar el jSon se estrellan irremediablemente contra un chorro de caracteres sin sentido. Es hora de descomprimir la respuesta.

Windows Phone no da soporte nativo a la compresión HTTP

Como he podido verificar después de dar unas cuantas vueltas, así que es hora de echar mano de (otra) biblioteca de terceros como SharpCompress.

Busqué el paquete en NuGet y, voilà. Ahí estaba.

image

¡Magnífico, incluso en la versión para Windows Phone 7! Ahora manos a la obra.

image

Y el código que se encarga de descomprimir la respuesta cuando corresponda.

   1: using SharpCompress.Compressor;

   2: using SharpCompress.Compressor.Deflate;

   3:  

   4: ...

   5:  

   6: response = (HttpWebResponse)request.EndGetResponse(ar);

   7:  

   8: byte[] buf = new byte[8192];

   9:  

  10: Stream respStream;

  11: if (response.Headers["Content-Encoding"] != null && response.Headers["Content-Encoding"] == "gzip")

  12: {

  13:     respStream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress);

  14: }

  15: else

  16: {

  17:     respStream = response.GetResponseStream();

  18: }

  19:  

  20: ...

Como podéis ver, muy sencillo y, de un plumazo, acabamos de ahorrarle a nuestro usuario algún que otro gasto en la factura telefónica (bueno, se lo hemos cambiado por algo más de consumo de batería)

Empezando con Windows Phone 7.1 (y XXI)–La importancia de IoC en el diseño de pantallas

¿Y qué tendrá que ver una cosa con la otra? Podrías preguntarte.

Pues lo cierto es que, por la forma en que funcionan las principales herramientas de diseño (Visual Studio y Blend) para Windows Phone, la importancia es mucha.

¿Cómo funciona?

Tanto Visual Studio como Blend compilan el código de tu solución antes de tratar de mostrarte en pantalla el diseño de la página (pantalla).

image

image

Lo que pasa es que ambas son extremadamente cuidadosas en el control de errores, así que, cuando salta una excepción tratan de mostrarte todo lo posible. Y lo que no, lo dejan sin mostrar.

En una aplicación más o menos típica se consumirán servicios web o proveedores de datos antes de mostrar la información en pantalla. Puede ocurrir que algunas de estas fuentes de datos no se encuentren disponibles en tiempo de diseño. Aquí entra IoC.

¿Cuál es la relación entre IoC y la presentación en tiempo de Diseño?

Si estamos utilizando MVVM Light, disponemos de la clase ViewModelLocator.cs que es el proveedor de datos de toda nuestra aplicación (en otros frameworks las soluciones serán más o menos similares).

Si le echas un vistazo al esqueleto que se presenta un poco más abajo verás alguna cosa interesante.

   1: public class ViewModelLocator

   2: {

   3:     static ViewModelLocator()

   4:     {

   5:         ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

   6:  

   7:         if (ViewModelBase.IsInDesignModeStatic)

   8:         {

   9:             SimpleIoc.Default.Register<IConfiguracionDataService, Design.ConfiguracionDataService>();

  10:             SimpleIoc.Default.Register<IContextoGeneralDeDatos, Design.ContextoGeneralDeDatos>();

  11:             SimpleIoc.Default.Register<IFourSquareContexto, Design.FourSquareContexto>();

  12:  

  13:             SimpleIoc.Default.Register<ILocationService, Design.LocationService>();

  14:         }

  15:         else

  16:         {

  17:             // Proveedores de datos

  18:             SimpleIoc.Default.Register<IConfiguracionDataService, ConfiguracionDataService>();

  19:             SimpleIoc.Default.Register<IContextoGeneralDeDatos, ContextoGeneralDeDatos>();

  20:             SimpleIoc.Default.Register<IFourSquareContexto, FourSquare.FourSquareContexto>();

  21:  

  22:             // Para localización

  23:             SimpleIoc.Default.Register<ILocationService, LocationService>();

  24:         }

  25:  

  26:         // Para los recursos

  27:         SimpleIoc.Default.Register<ITraductorAppBar, TraductorAppBar>();

  28:  

  29:         // Registramos VistasModelo

  30:         SimpleIoc.Default.Register<ConfiguracionViewModel>();

  31:         SimpleIoc.Default.Register<FourSquareLoginViewModel>();

  32:         SimpleIoc.Default.Register<CheckInViewModel>();

  33:         SimpleIoc.Default.Register<UsuarioViewModel>();

  34:         SimpleIoc.Default.Register<MainViewModel>();

  35:     }

  36: }

Sin entrar en mucho detalle, se puede ver como hay tres secciones importantes.

  1. La primera aplica sólo cuando estamos en modo de diseño (es decir, mostrando las páginas en el diseñador de Visual Studio o de Blend)
  2. La segunda aplica sólo cuando no estamos en modo de diseño.
  3. La tercera aplica en todos los casos.

Ya ves, utilizando IoC podemos proporcionar alternativas en tiempo de diseño y en tiempo de ejecución a servicios que, de otra manera nos darían problemas cuando no estamos ejecutando la aplicación.

El mismo esquema se utiliza para proporcionar conjuntos de pruebas.

Empezando con Windows Phone 7.1 (y XX)– Accediendo al DataSource desde un ListBoxItem

Trabajando en un escenario muy común me he encontrado con un pequeño problema. Veamos cuál es el asunto.

El Problema

El escenario es relativamente simple. Se trata de una página con un ListBox que presenta lugares (Venues) proporcionados por FourSquare.

image

Nada especialmente complicado. Vamos a echarle un vistazo al XAML asociado a la página (bueno, no vamos a mirarlo todo, que es un rollo. Vamos a lo más relevante).

   1: <phone:PhoneApplicationPage x:Class="Lcdad.SquareMatrix.Pages.MainPage"

   2:                             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:                             xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

   5:                             xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

   6:                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   7:                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   8:                             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

   9:                             xmlns:c4f="clr-namespace:Coding4Fun.Phone.Controls;assembly=Coding4Fun.Phone.Controls"

  10:                             xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

  11:                             mc:Ignorable="d"

  12:                             d:DesignWidth="480"

  13:                             d:DesignHeight="696"

  14:                             FontFamily="{StaticResource PhoneFontFamilyNormal}"

  15:                             FontSize="{StaticResource PhoneFontSizeNormal}"

  16:                             Foreground="{StaticResource PhoneForegroundBrush}"

  17:                             SupportedOrientations="PortraitOrLandscape"

  18:                             Orientation="Portrait"

  19:                             shell:SystemTray.IsVisible="True"

  20:                             DataContext="{Binding Main, Source={StaticResource Locator}}">

  21:  

  22:     <Grid x:Name="LayoutRoot"

  23:           Background="Transparent">

  24:         <Grid.RowDefinitions>

  25:             <RowDefinition Height="Auto" />

  26:             <RowDefinition Height="*" />

  27:         </Grid.RowDefinitions>

  28:  

  29:         <!--TitlePanel contains the name of the application and page title-->

  30:         <Grid x:Name="ContentPanel"

  31:               Grid.Row="1"

  32:               Margin="12,0,12,0">

  33:             

  34:             <ListBox x:Name="ListaLugares" ItemsSource="{Binding Path=Lugares}">

  35:                 <ListBox.ItemTemplate>

  36:                     <DataTemplate>

  37:                         <StackPanel>

  38:                             <Grid x:Name="ItemGrid">

  39:                                 <Grid.ColumnDefinitions>

  40:                                     <ColumnDefinition Width="6" />

  41:                                     <ColumnDefinition Width="*" />

  42:                                 </Grid.ColumnDefinitions>

  43:                                 

  44:                                 <c4f:Tile x:Name="TileSeleccionar" Grid.Column="1" Margin="0,6,6,0" Width="6" />

  45:                                 <TextBlock x:Name="TextoLugar" Grid.Column="3" Text="{Binding Path=name}" />

  46:                             </Grid>

  47:  

  48:                             <toolkit:ContextMenuService.ContextMenu>

  49:                                 <toolkit:ContextMenu x:Name="MenuCheckin">

  50:                                     <toolkit:MenuItem 

  51:                                         x:Name="OpcionCheckin" 

  52:                                         Header="{Binding Source={StaticResource LocationHelper}, 

  53:                                         Path=Recursos.OpcionCheckin}", 

  54:                                         Command=""></toolkit:MenuItem>

  55:                                 </toolkit:ContextMenu>

  56:                             </toolkit:ContextMenuService.ContextMenu>

  57:                         </StackPanel>

  58:                     </DataTemplate>

  59:                 </ListBox.ItemTemplate>

  60:             </ListBox>

  61:         </Grid>

  62:     </Grid>

  63: </phone:PhoneApplicationPage>

Se trata de mostrar una lista de lugares “venues” proporcionados por Four Square, una vez disponemos de los lugares más cercanos a la actual posición, debemos de seleccionar uno para poder hacer check-in sobre el mismo.

Para ello he incluido un control “ContextMenu” incluido en el paquete SilverlightToolkitWP (lo he bajado mediante NuGet). El mecanismo es indiferente, el quid de la cuestión está en las siguientes líneas.

   1: <ListBox x:Name="ListaLugares" ItemsSource="{Binding Path=Lugares}">

   2:     <ListBox.ItemTemplate>

   3:         <DataTemplate>

   4:             <StackPanel>

   5:                 <Grid x:Name="ItemGrid">

   6:                     <Grid.ColumnDefinitions>

   7:                         <ColumnDefinition Width="6" />

   8:                         <ColumnDefinition Width="auto" />

   9:                         <ColumnDefinition Width="*" />

  10:                     </Grid.ColumnDefinitions>

  11:                     

  12:                     <c4f:Tile x:Name="TileSeleccionar" Grid.Column="1" Margin="0,6,6,0" Width="6" />

  13:                     <TextBlock x:Name="TextoLugar" Grid.Column="3" Text="{Binding Path=name}" FontSize="{StaticResource PhoneFontSizeLarge}"/>

  14:                 </Grid>

  15:  

  16:                 <toolkit:ContextMenuService.ContextMenu>

  17:                     <toolkit:ContextMenu x:Name="MenuCheckin">

  18:                         <toolkit:MenuItem 

  19:                             x:Name="OpcionCheckin" 

  20:                             Header="{Binding Source={StaticResource LocationHelper}, 

  21:                             Path=Recursos.OpcionCheckin}"

  22:                             Command={}></toolkit:MenuItem>

  23:                     </toolkit:ContextMenu>

  24:                 </toolkit:ContextMenuService.ContextMenu>

  25:             </StackPanel>

  26:         </DataTemplate>

  27:     </ListBox.ItemTemplate>

  28: </ListBox>

Como podéis ver, el comando asociado al elemento del menú contextual no tiene asociado ninguna propiedad en la vista-modelo. La razón es la siguiente: La página tiene asociada la Vista-Modelo como contexto de datos (accede a ella mediante la propiedad Main del ViewModelLocator).

El ListBox tiene asociado, a su vez, una colección de elementos como contexto de datos. Esta colección es una propiedad de la Vista-Modelo llamada Lugares.

El problema es que queremos acceder a un comando de la VistaModelo desde un elemento ubicado en el interior del ListBox (dentro del DataTemplate) y no lo conseguimos. Pensando en el tema, cometí uno de los errores que últimamente me veo cometiendo con demasiada frecuencia: me voy a buscar la solución antes de pensarla yo mismo. Pude ver un post que os indico aquí para vuestra referencia y que resulta interesante por la idea que explora. Se trata de un Proxy para el DataSource. De forma resumida: creamos un objeto que hereda de FrameworkElement y que expone el DataSource del control padre, de esta forma podemos acceder al DataSource de la página a través del DataSource del ListBox.

La Solución

Sin embargo, voy a proponer una solución más sencilla (la verdad es que mucho más sencilla).

Haciendo uso del MVVM Light disponemos de una clase encargada de proporcionar acceso a cada una de las vistas. ¿No es así?

Efectivamente, el ViewModelLocator.

Bueno, pues ya está. Simplemente, cambiamos el binding para acceder a la VistaModelo de la siguiente manera.

   1: <toolkit:ContextMenuService.ContextMenu>

   2:     <toolkit:ContextMenu x:Name="MenuCheckin">

   3:         <toolkit:MenuItem 

   4:             x:Name="OpcionCheckin" 

   5:             Header="{Binding Source={StaticResource LocationHelper}, 

   6:             Path=Recursos.OpcionCheckin}" 

   7:             Command="{Binding Source={StaticResource Locator}, Path=Main.RealizarCheckin}">

   8:         </toolkit:MenuItem>

   9:     </toolkit:ContextMenu>

  10: </toolkit:ContextMenuService.ContextMenu>

Quizá no sea muy limpio, pero desde luego es mucho más sencillo.

Empezando con Windows 7.1 (y XIX)–ApplicationBar en varios idiomas

A vueltas con la Barra de Herramientas

Ya lo he comentado en alguna ocasión. La barra de herramientas en Windows Phone 7.x es un pequeño engendro.

Al contrario que cualquier elemento visual no son controles, lo que nos imposibilita completamente la opción de utilizar Behaviors. Así que hay que inventarse algún que otro truquito y, sobre todo, estar dispuesto a hacer un poco la vista gorda con la solución.

El problema

Como decía, el problema es que la barra de herramientas no es un control visual. Así que, para poder dotar de capacidad de localizacion (por ejemplo) en los literales vamos a realizar un método de extensión de la Barra.

Uno podría pensar: ¿Por qué no heredo directamente de la barra de herramientas y construyo mi propia clase? Bueeeeno. Lo primero es que la clase está sellada (sealed) con lo que no podemos heredar de ella.

Bien. No pasa nada. ¿Y si implementamos nuestra propia barra de herramientas? Al fin y al cabo desde la versión 7.1 del SDK disponemos de una interfaz IApplicationBar. Si le echamos un vistazo al MSDN, veremos algunas buenas prácticas recomendadas. Entre ellas, nos recomiendan utilizar la barra de aplicación del sistema en vez de crear nuestro propio sistema de menús. Ojo a lo que dice: Nuestro propio sistema de menús.

Bueno, pues no pasa nada, no quiero montar un sistema de menús completamente nuevo cuando tengo una bonita interfaz que puedo implementar. ¿Verdad?

Pues no.

image

Si intentas crear tu propia implementación de IApplicationBar el sistema te va a mandar amablemente a freír espárragos con el mensaje: “InvalidOperationException: PhoneApplicationPage only accepts the ApplicationBar implementation of IApplicationBar

Vamos, que si quieres ApplicationBar, utiliza la que te proporciona el Shell o ninguna. Tú sabrás.

La solución

¿Desde cuando este tipo de problemas ha sido un bloqueo para seguir adelante con cabezonería?

Vamos a intentar otra aproximación (un tanto más sucia) que es una variante de otra que he visto aquí.

Primero crearemos una clase de ayuda que defina métodos de extensión en la clase ApplicationBar.

   1: namespace Jdmveira.WindowsPhone.Mvvm

   2: {

   3:     using Microsoft.Phone.Shell;

   4:     using GalaSoft.MvvmLight.Messaging;

   5:     using System;

   6:     using System.Collections.Generic;

   7:  

   8:     /// <summary>

   9:     /// Clase de extensiones para la barra de aplicaciones <see cref="ApplicationBar"/>

  10:     /// </summary>

  11:     public static class ApplicationBarHelper

  12:     {      

  13:         /// <summary>

  14:         /// Permite localizar la <see cref="ApplicationBar"/> con la ayuda de un método de traducción proporcionado por el cliente. Localiza 

  15:         /// tanto botones como elementos de menú

  16:         /// </summary>

  17:         /// <param name="appBar">Instancia de <see cref="ApplicationBar"/> a la que queremos dotar de textos localizados</param>

  18:         /// <param name="traslate">Instancia de <see cref="Action<IApplicationBarIconButton"/> que se encargará de la traducción del elemento</param>

  19:         public static void LocalizeAppBarElement(this ApplicationBar appBar, Action<IApplicationBarMenuItem> traslate)

  20:         {

  21:             if (appBar.MenuItems != null)

  22:             {

  23:                 for (int i = 0; i < appBar.MenuItems.Count; i++)

  24:                 {

  25:                     IApplicationBarMenuItem item = appBar.MenuItems[i] as IApplicationBarMenuItem;

  26:                     if (item != null)

  27:                     {

  28:                         traslate(item);

  29:                     }

  30:                 }

  31:             }

  32:  

  33:             if (appBar.Buttons != null)

  34:             {

  35:                 for (int i = 0; i < appBar.Buttons.Count; i++)

  36:                 {

  37:                     IApplicationBarMenuItem item = appBar.Buttons[i] as IApplicationBarMenuItem;

  38:                     if (item != null)

  39:                     {

  40:                         traslate(item);

  41:                     }

  42:                 }

  43:             }

  44:         }

  45:     }

  46: }

Este método de extensión recibe un delegado del tipo Action<IApplicationBarMenuItem> que es el que se encarga de traducir un elemento en función del texto original.

No nos dejemos engañar por la signatura del delegado. Ambos, botones y entradas de menú implementan la interfaz IApplicationBarMenuItem, con lo cual podemos tratar ambos de la misma manera. Si quisiéramos dar un tratamiento especial a los botones (por ejemplo, porque quieras cambiar el icono en función de alguna lógica) tendríamos que duplicar los métodos de extensión. Uno para IApplicationBarMenuItem y otro para IApplicationBarIconButton.

A continuación, dentro de la Vista (sí, dentro de la vista) podemos invocar el método de extensión.

   1: namespace Lcdad.SquareMatrix.Pages

   2: {

   3:     using Microsoft.Phone.Shell;

   4:     using Microsoft.Phone.Controls;

   5:     using Jdmveira.WindowsPhone.Mvvm;

   6:     using Lcdad.SquareMatrix.ViewModel;

   7:  

   8:     /// <summary>

   9:     /// Description for MainView.

  10:     /// </summary>

  11:     public partial class MainPage : PhoneApplicationPage

  12:     {

  13:         /// <summary>

  14:         /// Initializes a new instance of the MainView class.

  15:         /// </summary>

  16:         public MainPage()

  17:         {

  18:             InitializeComponent();

  19:  

  20:             MainViewModel vm = DataContext as MainViewModel;

  21:  

  22:             if (ApplicationBar != null && vm != null)

  23:             {

  24:                 ((ApplicationBar)ApplicationBar).LocalizeAppBarElement(vm.TraducirBarraDeHerramientas);

  25:             }

  26:         }        

  27:     }

  28: }

En mi caso, el método que se encarga de la traducción está implementado en la VistaModelo.

   1: namespace Lcdad.SquareMatrix.ViewModel

   2: {

   3:     using GalaSoft.MvvmLight;

   4:     using System.Windows.Input;

   5:     using GalaSoft.MvvmLight.Command;

   6:     using System;

   7:     using Lcdad.SquareMatrix.Model;

   8:     using Jdmveira.WindowsPhone.Mvvm.Navigation;

   9:     using Microsoft.Phone.Shell;

  10:     using Lcdad.SquareMatrix.Resources;

  11:  

  12:     public class MainViewModel : ViewModelBase

  13:     {

  14:         public void TraducirBarraDeHerramientas(IApplicationBarMenuItem elemento)

  15:         {

  16:             switch (elemento.Text)

  17:             {

  18:                 case "Configuracion":

  19:                     elemento.Text = Resources.Lcdad_SquareMatrix.AppBarBotonConfiguracion;

  20:                     break;

  21:                 case "Checkin":

  22:                     elemento.Text = Resources.Lcdad_SquareMatrix.AppBarBotonCheckin;

  23:                     break;

  24:             }

  25:         }

  26:     }

  27: }

Y así puedo conseguir una barra de herramientas con elementos cuyo texto se muestra en función del idioma.

appbar-esappbar-en

Otra alternativa

Ya lo mencioné en otra entrada. Existe en Codeplex un proyecto llamado Phone7.Fx que, entre otras cosas, dispone de una barra de herramientas enlazable. Lo cierto es que nunca lo he utilizado, pero ahí está el código por si un día me entra la curiosidad.

A vueltas con Windows Phone 7.8

Tal y como comentaba en un post anterior, Microsoft pretende proporcionar a los actuales poseedores de Windows Phone una versión que acerque nuestros terminales a la nueva versión Windows Phone 8.

Parece que estoy equivocado.

Al menos, de momento, sólo se confirma la nueva página de inicio (que como os comentaba, parece poco más que un caramelo… )

El hecho de que haya una gran cantidad de nuevas características en WP 8 que estén atadas al hardware hace que muchos (no me atrevería a decir todos) dispositivos actuales no puedan contar con ellas (yo, desde luego, no puedo contar con NFC en mi actual HTC HD 7). Sin embargo, hay un buen número de características del nuevo sistema operativo que no se encuentran vinculadas tan directamente con el hardware (o con el nuevo kernel implantado).

Pues bien, no soy muy amigo de las cartas de petición (quizá sea un tema cultural, pero en demasiadas ocasiones sirven de muy poco), pero por esta vez voy a hacer una excepción.

En UserVoice podéis encontrar una solicitud (muy seguida, dicho sea de paso) a Microsoft para que vuestro futuro Windows Phone 7.8 contenga el mayor número posible de estas nuevas características.

Yo, desde luego, no estoy por la labor de comprarme un Smart Phone cada dos años, pero pensando en aquellos que acaben de agenciarse su flamante nuevo Nokia, imagino que poca gracia les hará y poca ayuda para el negocio de Microsoft. Dicho sea de paso, parece que Microsoft quisiera contar con los incondicionales, ques somos unos cuantos, a la hora de propagar el boca a boca.

Pues, en mi opinión, esta es una magnífica oportunidad que se les presenta para conseguir que los que estábamos contentos con nuestros teléfonos, lo estemos todavía más.

P.D. ¿Alguien ha visto durante la Eurocopa un anuncio de Windows Phone acompañando a los de iPhone y Android que hay en cada descanso de partido? Mientras sea esta la campaña de márketing de Microsoft con su Windows Phone, los incondicionales seguiremos siendo importantes a la hora de que este teléfono termine por ocupar la cuota que se merece.

Windows Phone 7.8

¿De dónde viene?

Como sabréis, se ha celebrado recientemente el Windows Phone Summit y se ha aprovechado el evento para hablar mucho del futuro Windows Phone 8.

Lo cierto es que la web está llena de artículos sobre la futura versión de WP, así que mi intención es contar aquí dos o tres cosas que me llaman la atención y que pueden hacer que WP sea una plataforma llena de posibilidades y con futuro (pese a empezar con una seria desventaja frente a sus competidores).

Para poder explicar qué es la versión 7.8 (lo poco que se sabe) antes hay que ver un poco sobre las diferencias fundamentales entre la versión 7.8 y la 8 (y no estoy hablando de funcionalidad).

Windows Phone 7.X y 8

Windows Phone 8 (Apollo) es un cambio radical en el SO. Para que os hagáis una idea: en realidad se trata de Sistema Operativo diferente y no de una evolución del actual (por mucho que el nombre y la versión hagan pensar lo contrario).

El núcleo de un sistema operativo es el corazón, las tripas, el sistema nervioso central y el sistema nervioso periférico. Lo demás son, más o menos, adornos (bueno, más o menos). La pieza más pegada a la tecnología, al hardware. Ya sea procesador, memoria, dispositivos… es, precisamente, el núcleo. Pues bien el núcleo de Windows Phone 7.X está basado en WinCE, un sistema operativo que Microsoft diseñó en su momento para sistemas embebidos, que ha tenido su evolución desde 1996 y que, desde el comienzo daba soporte a varias arquitecturas de micro-procesadores (a destacar Intel x86 y ARM).

Por el contrario, Windows Phone 8 tiene un núcleo basado en Windows NT (sí, ese sistema operativo de Servidor de la segunda mitad de los 90 y que es, por decirlo de alguna manera, el abuelo directo de la mayoría de los actuales Sistemas Operativos de Microsoft. Este sistema operativo nació con la idea original de proporcionar sabores de estación de trabajo y de servidor (con más o menos éxito) pero que, fijaos cómo son las cosas, va a tener una n-ésima reencarnación dentro de un teléfono.

Vale. ¿Y qué pasa entonces con mi Windows Phone 7.X?

Las arquitecturas de ambos sistemas operativos son bastante diferentes (aunque tenían cierta base común). Así que, por resumir un poco el tema, resulta complicado que los teléfonos móviles que actualmente ejecutan Windows Phone 7.X puedan llegar a ejecutar correctamente Windows Phone 8 (no seré categórico aquí, que no me sorprendería ver poco después de la salida de WP 8 algunas ROM que pongan el nuevo Sistema Operativo en los antiguos teléfonos… ya veremos).

Así que, ya veis. Olvidaos de encontraros alguna actualización en vuestros teléfonos móviles que os instale el nuevo Sistema Operativo. Por otro lado, aunque no me haga muy feliz, lo cierto es que esto no es nada nuevo (tanto Android como iPhone se han encontrado con escenarios idénticos).

La estrategia de Microsoft está clara al respecto. Tanto el nuevo Windows 8 (que será el Sistema Operativo común para Tablets y PCs) como Windows Phone 8 tendrán en común una base muy importante. Esto sienta los principios para una única línea (es de suponer) de sistemas operativos para todos los ecosistemas de Microsoft. Algo que Apple ya hace con su iPhone y su iPad y Android con sus teléfonos y tabletas (sí, Android tiene tabletas, no sólo existe el iPad).

Para todos aquellos, pobres de nosotros, que tenemos un teléfono basado en Windows Phone 7.X, Microsoft ha asegurado que estaremos en condiciones de “acercarnos” todo lo posible al nuevo Windows Phone 8. Eso, es, precisamente la versión Windows Phone 7.8. Una versión pensada para los dispositivos actuales que no soportarán el nuevo sistema operativo Windows Phone 8.

¿Y qué cambios traerá?

Aquí entro en el ámbito de la opinión personal. Y con los datos que hay actualmente, que son muy pocos, no voy a meterme demasiado en el barro.

Una cosa es que Microsoft de un caramelo como la nueva página de inicio de Windows Phone 8 (que estará presente en Windows Phone 7.8)

Pero esto es muy poco representativo y, en mi opinión aporta entre poco y nada, habida cuenta de que Windows Phone 7.8 probablemente seguirá teniendo una única resolución de pantalla (y no como las tres que soportará la versión 8).

En definitiva, tendremos que esperar a ver qué nos proporcionará Windows Phone 7.8, pero no tengo muchas expectativas que digamos:

  • Algunas cosas podemos ir descartándolas con casi toda seguridad (ejecución de código nativo, por ejemplo que será muy útil para portar juegos de otras plataformas y extraer el máximo rendimiento a la máquina).
  • Otras me encantaría verlas en la versión 7.8 (como un Market Place privado para el mundo empresarial).
  • Por último, algunas características de red indispensables para que este sistema operativo pueda ser utilizado en el mundo de la empresa, y que estarán presentes en Windows Phone 8, tengo dudas que puedan llegar a estar en Windows Phone 7.8 pero de ilusión también se vive.

Empezando con Windows 7.1 (y XVIII) – Utilizando Behaviors

Los “comportamientos” (Behaviors) son piezas de funcionalidad relacionadas con la interacción del usuario y que pueden ser reutilizadas con facilidad.

Mucha gente asocia la utilización de un Behavior a Expression, y es más que lógica dicha asociación (échale un vistazo al link de referencia para ver dónde está clasificado dentro de la MSDN).

Sin embargo su utilización no se limita a Expression. Podemos sacarles partido en nuestro Windows Phone de manera sencila.

Montando Un Cuadro de Diálogo

Un escenario muy común en la utilización de cualquier patrón que busque desacoplar la capa de presentación de la lógica de presentación (es decir, la Vista de lo que sea que pongamos detrás) es mostrar un cuadro de diálogo. En su versión más sencilla, este cuadro muestra un mensaje de aviso al usuario y proporciona algún mecanismo para que el usuario escoga de entre un número limitado de acciones.

En este caso me voy a centrar en mostrar un mensaje de diálogo sencillo al usuario haciendo uso de un Behavior y de algunas facilidades que nos ofrece MVVM Light.

Al lío

Imagninemos que necesitamos realizar un intercambio de datos a través de la red y que no disponemos de conexión. Podríamos querer avisar al usuario de esta situación.

MVVM Light nos pone al alcance un tipo de mensaje llamado DialogMessage (desafortunadamente no es fácil encontrar documentación completa en la red sobre cada clase, afortunadamente ahí está el código para revisarlo).

Vamos a descomponer el problema en dos partes. Por un lado, necesitamos poder disponer de algún mecanismo declarativo que nos permita mostrar un cuadro de diálogo con título y texto variables (también sería bueno si pudiéramos mostrar diferentes acciones, pero eso me mete en un jardín en el que, por ahora, no quiero entrar).

Construir un Behavior para los Mensajes

Vamos a comenzar con la construcción de un Behavior que nos permita introducir declarativamente un cuadro de diálogo en la vista.

 1: using System;

 2: using System.Windows;

 3: using System.Windows.Interactivity;

 4: using GalaSoft.MvvmLight.Messaging;

 5:

 6: /// <summary>

 7: /// Clase que nos permitirá implementar un comportamiento para mostrar mensajes de forma ajustada al patrón MVVM

 8: /// </summary>

 9: public class DialogBehavior : Behavior<FrameworkElement>

 10: {

 11:     #region Miembros Públicos

 12:

 13:     public static DependencyProperty TituloProperty = DependencyProperty.Register("Titulo", typeof(string), typeof(DialogBehavior), new PropertyMetadata(String.Empty));

 14:     public static DependencyProperty IdentificadorProperty = DependencyProperty.Register("Identificador", typeof(string), typeof(DialogBehavior), new PropertyMetadata(String.Empty));

 15:     public static DependencyProperty TextoProperty = DependencyProperty.Register("Texto", typeof(string), typeof(DialogBehavior), new PropertyMetadata(String.Empty));

 16:

 17:     /// <summary>

 18:     /// Identificador

 19:     /// </summary>

 20:     public string Identificador

 21:     {

 22:         get

 23:         {

 24:             return (string)GetValue(IdentificadorProperty);

 25:         }

 26:         set

 27:         {

 28:             SetValue(IdentificadorProperty, value);

 29:         }

 30:     }

 31:

 32:     /// <summary>

 33:     /// Título a mostrar

 34:     /// </summary>

 35:     public string Titulo

 36:     {

 37:         get

 38:         {

 39:             return (string)GetValue(TituloProperty);

 40:         }

 41:         set

 42:         {

 43:             SetValue(TituloProperty, value);

 44:         }

 45:     }

 46:

 47:     //public string Titulo { get; set; }

 48:

 49:     /// <summary>

 50:     /// Texto a mostrar

 51:     /// </summary>

 52:     public string Texto

 53:     {

 54:         get

 55:         {

 56:             return (string)GetValue(TextoProperty);

 57:         }

 58:         set

 59:         {

 60:             SetValue(TextoProperty, value);

 61:         }

 62:     }

 63:

 64:     /// <summary>

 65:     /// Botones a mostrar

 66:     /// </summary>

 67:     public MessageBoxButton Botones { get; set; }

 68:

 69:     #endregion Miembros Públicos

 70:

 71:     #region Miembros Privados

 72:

 73:     private static Messenger _messenger = Messenger.Default;

 74:

 75:     /// <summary>

 76:     /// Método invocado para mostrar el mensaje

 77:     /// </summary>

 78:     /// <param name="dm"></param>

 79:     private void ShowDialog(GalaSoft.MvvmLight.Messaging.DialogMessage dm)

 80:     {

 81:         var result = MessageBox.Show(Texto, Titulo, Botones);

 82:

 83:         if (dm.Callback != null)

 84:             dm.Callback(result);

 85:     }

 86:

 87:     #endregion Miembros Privados

 88:

 89:     #region Miembros Protegidos

 90:

 91:     protected override void OnAttached()

 92:     {

 93:         base.OnAttached();

 94:

 95:         _messenger.Register<GalaSoft.MvvmLight.Messaging.DialogMessage>(this, Identificador, ShowDialog);

 96:     }

 97:

 98:     #endregion Miembros Protegidos

 99: }

De este código merede la pena destacar varias cosas.

  • Estamos heredando de la clase Behavior<FrameworkElement> que es el escenario más sencillo que podemos contemplar ahora mismo.
  • Hemos creado propiedades para el Identificador (ayuda a decidir sobre qué Diálogo estamos lanzando el mensaje), Título y Texto.
  • Como queremos que estas propiedades puedan estar enlazadas (ya sabes, DataBinding) con propiedades de la VistaModelo, hemos creado tres DependencyProperty por detrás que nos ayudarán en el soporte al DataBinding (si me limito a exponer las propiedades de Texto no es posible establecer el mencionado DataBinding)
  • Por lo demás, no mucho más. Lanzamos un MessageBox y llamamos a la función de callback (si la hay) con el resultado de la elección del usuario.

Ahora la vista

Con nuestro Behavior creado, nos vamos a la vista a declararlo.

 1: <i:Interaction.Behaviors>

 2:     <jdmv:DialogBehavior

 3:         Titulo="{Binding Path=TituloDialogo}"

 4:         Texto="{Binding Path=TextoDialogo}"

 5:         Botones="OKCancel"

 6:         Identificador="{Binding Path=IdMensajeMensaje}" />

 7: </i:Interaction.Behaviors>

Los espacios de nombres utilizados son los siguientes (he quitado unos cuantos para facilitar la lectura).

 1: <phone:PhoneApplicationPage

 2:     x:Class="Lcdad.SquareMatrix.ViewModel.FourSquareLogin"

 3:     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

 4:     xmlns:jdmv="clr-namespace:Jdmveira.WindowsPhone.Mvvm.Messaging;assembly=Jdmveira.WindowsPhone">

No hay mucho que comentar. Las propiedades están enlazadas con propiedades de la VistaModelo.

Por último, la VistaModelo

Tan solo nos queda la VistaModelo, en ella he centralizado la utilización de los mensajes y los errores a través de dos métodos. Pongo el detalle de Mensaje por ser el más completo.

 1: /// <summary>

 2: /// Muestra un mensaje con opciones en pantalla

 3: /// </summary>

 4: /// <param name="titulo">Título del diálogo de error</param>

 5: /// <param name="texto">Texto a mostrar</param>

 6: /// <param name="callback">Instancia de <see cref="Action<MessageBoxResult>"/> con el método de callback para manejar la respuesta de usuario</param>

 7: private void Mensaje(string titulo, string texto, Action<MessageBoxResult> callback)

 8: {

 9:     TituloDialogo = titulo;

 10:     TextoDialogo = texto;

 11:

 12:     var msg = new DialogMessage(this, this, texto, callback);

 13:

 14:     DispatcherHelper.CheckBeginInvokeOnUI(() => { MessengerInstance.Send<DialogMessage>(msg, IdMensajeMensaje); });

 15: }

Que recibe los argumentos y se asegura de lanzar el cuadro de diálogo en el contexto del hilo de la UI.

Como podéis ver, su utilización es muy sencilla y permite desacoplar la operativa de mostrar el mensaje en pantalla. Hay todavía espacio para mejorarlo, pero es un buen comienzo.

Desarrollo de Windows Phone 7 con Visual Studio 2012 RC

Estoy decepcionado, no lo puedo evitar. Visual Studio 2012 RC sigue sin dar soporte a Windows Phone 7 SDK.

image

image

La pregunta del millón es: ¿Lo hará? Lo cierto es que sería lógico que sí, pero quizá la empresa de Redmon vaya  a centrarse en el soporte del próximo Windows Phone 8.

Bueno. Toca esperar y, mientras tanto, seguir con Visual Studio 2010.