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 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.

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.

Empezando con Windows 7.1 (y XVII) – Comunicación entre diferentes VistaModelo

El problema

A medida de que voy avanzando en la aplicación, veo que poco a poco se van cubriendo escenarios comunes. En este post me voy a centrar en la comunicación entre diferentes VistaModelo.

En vez de dibujar aburridos diagramas de clases, vamos a tratar de ilustrarlo con un par de pantallas.

El programa

Disponemos de una primera pantalla de configuración. Esta pantalla permite activar notificaciones y, además, muestra información sobre la configuración del usuario en FourSquare (extraída de una entidad que consume información de la base de datos)

configuracion_1

Así mismo, el “tile” sobre los campos informativos “ID de Usuario” y “Nombre Completo” se puede pulsar para acceder a la página que nos permite registrar la información del usuario.

configuracion_2

Esta página (bueno, la página no, la vistamodelo que hay detrás), una vez el usuario autoriza el acceso de la aplicación, guarda la información en base de datos y vuelve atrás (a la página anterior).

configuracion_1

Que, no se ha enterado de que ha habido cambios en la entidad de configuración que consume para mostrar la información de FourSquare (lógico, al fin y al cabo, estos cambios han tenido lugar fuera de su ámbito).

La solución

La solución al problema es sencilla (aproximación prácticamente idéntica a las que hay en otro patrones de diseño como MVC o MVP).

En pocas palabras:

  1. La página de configuración se registra a posibles cambios de la entidad (o entidades) en que se encuentra interesada.
  2. La página de registro, que actúa sobre dicha entidad, lanza un mensaje “al mundo” cuando realiza algún cambio sobre la misma.
  3. La página de configuración se puede dar por enterada del cambio e inicia sus tareas de avisar, a su vez, a la página de que ha habido cambios y que tiene que refrescar la información que está mostrando en pantalla.

El ejemplo

MVVM Light nos proporciona un conjunto de clases que nos ayuda en estas tareas. Se encuentran bajo el espacio de nombres Messaging.

image

Aunque veremos algunos de los tipos definidos en este espacio de nombres en algún futuro post, me voy a centrar ahora en un par de clases.

La clase Messenger se encarga de gestionar el envío y recepción de mensajes. Los mensajes se envían “a quien quiera oírlos”. Los receptores de los mensajes tienen que haberse suscrito para poder recibir mensajes de un tipo.

Como explicaba antes, la VistaModelo de Registro se encarga de actualizar las preferencias de usuario tras el proceso de registro en FourSquare

   1: /// <summary>

   2: /// Registra las preferencias de usuario a partir de la información obtenida del mismo de FourSquare

   3: /// </summary>

   4: private void RegistrarPreferenciasDeUsuario()

   5: {

   6:     try

   7:     {

   8:         Entities.Preferencias pref = _dataService.ObtenerPreferenciasConfiguracion();

   9:         Entities.Preferencias prevPref = pref.Clone();

  10:  

  11:         pref.FourSquareFullName = String.Format("{0} {1}", _user.firstName, _user.lastName);

  12:         pref.FourSquareUserId = _user.id.ToString();

  13:         pref.FourSquareToken = _fsContexto.Token;

  14:  

  15:         _dataService.ActualizarPreferencias(pref);

  16:  

  17:         RaisePropertyChanged<Entities.Preferencias>("Preferencias", prevPref, pref, false);

  18:     }

  19:     catch (Exception ex)

  20:     {

  21:         // Hacer algo con la excepción

  22:         throw;

  23:     }

  24: }

Como podéis ver en el código, nada fuera del otro mundo. Se invoca el método RaisePropertyChanged. En este caso, no estamos interesados en ver qué propiedad de la entidad ha cambiado (ya que cambian varias y, a todos los efectos, no estoy interesado en obtener este grado de detalle) y he decidido pasar como primer parámetro el nombre de la clase. Se pasa también el valor original, el valor actualizado (para poder realizar esto, he tenido que montar un método para clonar la entidad) y se indica si el mensaje será lanzado “a quien quiera oírlo” (broadcast). Si este último parámetro no se pone a true, otra VistaModelo no recibirá esta mensaje.

En cuanto a la VistaModelo correspondiente a la configuración está interesada en recibir mensajes que le informen de cambios en las preferencias, para ello en el constructor realizaremos la operación de sucripción al tipo de mensaje.

   1: /// <summary>

   2: /// Este método subscribe a la VistaModelo a cualquier cambio realizado las propiedades de las <see cref="Preferencias"/>

   3: /// más concretamente sobre las preferencias que afectan a FourSquare

   4: /// </summary>

   5: private void SubscribirseCambiosEnPreferenciasConfiguracion()

   6: {

   7:     MessengerInstance.Register<PropertyChangedMessage<Preferencias>>(this, (p) =>

   8:     {

   9:         _preferencias = p.NewValue;

  10:  

  11:         DispatcherHelper.CheckBeginInvokeOnUI(() =>

  12:         {

  13:             RaisePropertyChanged(FourSquareUserIdNombreProp);

  14:             RaisePropertyChanged(FourSquareFullNameNombreProp);

  15:         });

  16:     });

  17: }

Este código tiene un poco más de enjundia. Vamos a analizarlo.

Primero, hacemos uso de la propiedad MessengerInstance que estamos heredando de la clase base (ViewModelBase) definida en el framework. A través de esta clase nos registramos a los mensajes del tipo PropertyChangedMessage<Preferencias> (que, si os fijáis, es el mismo tipo que estábamos lanzando en la VistaModelo anterior).

A continuación utilizamos un delegado anónimo que actualiza la referencia que teníamos con el nuevo valor y hace uso de una clase llamada DispatcherHelper (también definida en el framework MVVM Light) que nos permite acceder fácilmente al Dispatcher.

Mediante este Dispatcher, estamos pidiendo que se ejecute en el hilo de UI la actualización de las propiedades para que la página pueda refrescarlas.

¿Por qué esto es importante? En Silverlight (en realidad no exclusivamente en Silverlight) se define un hilo de UI donde se ejecutan todas las acciones que afectan a la interfaz de usuario (UI). Cualquier intento de actualizar algún elemento de la UI fuera de dicho hilo se va estrellar irremediablemente con la excepción: Invalid cross-thread access.

image

Por último, es necesario inicializar el DispatcherHelper antes de que seamos capaces de hacer funcionar todo esto. ¿Cómo lo hacemos? En la documentación encontrada por la red se insiste en inicializar dicha clase en App_Startup porque este método siempre se invoca en el contexto del hilo de UI.

Desgraciadamente, en WP7, tal método no existe. ¿Dónde inicializamos el dispatcher entonces? La respuesta es: en el constructor de App, que siempre se ejecuta en el hilo de UI.

image

A partir de aquí, todo funciona como debería.

configuracion_1

Ya que al actualizar Preferencias desde la vista de Registro

configuracion_2

El mensaje de cambio llega a la vista de configuración que se encarga de avisar a su vista de que ha de refrescarse.

configuracion_3

Utilizando NuGet

Muchos ya conoceréis NuGet, que lleva dando vueltas por ahí desde hace ya algún tiempo.

El propósito de NuGet es proporcionar una herramienta sencilla dentro del entorno Visual Studio (y fuera, mediante línea de comandos) para incorporar packetes y herramientas de terceros a tus desarrollos. Y lo mejor: gestiona las dependencias, de forma que, si estás interesado en un paquete concreto A para tu desarrollo y este, a su vez, depende de  otro paquete B, NuGet se encarga por ti de descargarte ambos paquetes A y B.

En el proyecto de Windows Phone 7 que estoy llevando a cabo en estos momentos hace uso de un par de utilidades de terceros; MVVM Light y Newtonsoft Json. Ambos pueden descargarse utilizando NuGet (aquí y aquí).

Es común ver cómo los paquetes y dependencias de terceros se incluyen en una . solución de Visual Studio (las DLLs, ejecutables, etc..). No voy a meterme en soluciones que se aplican en entornos de empresa cuando hay instalada alguna herramienta o solución avanzada de Gestión de la Configuración (SCM del inglés System Configuration Management)

Volviendo al caso de andar por casa:

El proceso es muy sencillo. Una vez instalado el addin de Visual Studio NuGet Manager Package, dispondrás de una serie de complementos como la consola del gestor de paquetes.

image

Es perfectamente posible forzar a NuGet a descargar las dependencias a una ruta específica así que es muy sencillo integrar con Team Fondation Server (echa un vistazo aquí). Simplemente pulsa el botón derecho sobre la solución y activa “Enable NuGet Package Restore” de esta forma todas las personas del equipo (en mi caso sólo yo desde dos máquinas diferentes) disponen no sólo de las dependencias, también de la posibilidad de que estas dependencias se descarguen automáticamente en la compilación en caso de que no estén en la máquina de desarrollo.