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.

Anuncios

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.

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

Empezando con Windows Phone 7.1 (y XVI)–Manejando Eventos con Comandos

En otros posts he probado a manejar un evento de un control Silverlight y redirigirlo a una propiedad del tipo ICommand en mi VistaModelo.

Actualmente estoy trabajando en una página de configuración en la que, una de las acciones a llevar a cabo es registrar la aplicación en Foursquare. El mecanismo de autenticación utilizado es OAuth v2 (un estándar en la web, si echáis por ahí un vistazo). En el caso de Foursquare, el mecanismo recomendado para autenticación desde dispositivos móviles viene indicado aquí.

Esencialmente el proceso es muy simple y consiste en lo siguiente.

  1. Navegamos a la página de autenticación proporcionando el código de nuestra aplicación y una url de vuelta “callback”. Esta url no tiene por que ser una url real para una aplicación en desarrollo (algo del estilo http://localhost te va a funcionar perfectamente).
  2. Foursquare recibe la petición, comprueba el ID de la aplicación y la url de vuelta. Si el usuario está ya autenticado pasa al paso 4, si no.
  3. Si el usuario no está autenticado, pide sus credenciales de Foursquare.
  4. Una vez autenticado el usuario, Foursquare le pregunta si quiere registrar la aplicación.
  5. Si el usuario acepta, se redirige a la url de vuelta (callback) con un token específico para el usuario, que es el que utilizaremos para hacer llamadas al API de Foursquare desde la aplicación, en nombre del usuario.

En una primera aproximación me voy a limitar a crear un control de tipo WebBrowserControl con el que realizar este proceso.

image

Nada muy complicado.

   1: <!--ContentPanel - place additional content here-->

   2: <Grid x:Name="ContentPanel"

   3:       Grid.Row="1"

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

   5:     <phone:WebBrowser 

   6:         HorizontalAlignment="Stretch" 

   7:         Name="webBrowser1" 

   8:         VerticalAlignment="Stretch" 

   9:         IsScriptEnabled="True" 

  10:         Source="{Binding Path=FourSquareAuthUrl}">

  11:         

  12:     </phone:WebBrowser>

  13:  

  14: </Grid>

Atención a la propiedad IsScriptEnabled, sin ella no he conseguido hacer funcionar el ejemplo ya que, por defecto, el valor está a falso y no permite ejecutar scripts.

Como con el resto de la aplicación, estoy tratando de mantener un patrón MVVM con lo que:

  • La url (propiedad Source) la he de obtener de la VistaModelo
  • Cuando navegemos a la URL de callback y deba de obtener el token, debo de hacerlo desde la VistaModelo también.

El primer punto no supone dificultad, el problema viene cuando queremos manejar los eventos de navegación. Veamos los intentos que he realizado.

Navegando con Code-behind

El primer intento evidente es crear un manejador en el “code behind” de la página.

   1: <phone:WebBrowser 

   2:     HorizontalAlignment="Stretch" 

   3:     Name="webBrowser1" 

   4:     VerticalAlignment="Stretch" 

   5:     IsScriptEnabled="True" 

   6:     Source="{Binding Path=FourSquareAuthUrl}" 

   7:     Navigating="webBrowser1_Navigating" />

Y el manejador…

   1: namespace Lcdad.SquareMatrix.ViewModel

   2: {

   3:     using GalaSoft.MvvmLight.Ioc;

   4:     using Microsoft.Phone.Controls;

   5:  

   6:     /// <summary>

   7:     /// Description for FourSquareLogin.

   8:     /// </summary>

   9:     public partial class FourSquareLogin : PhoneApplicationPage

  10:     {

  11:         /// <summary>

  12:         /// Initializes a new instance of the FourSquareLogin class.

  13:         /// </summary>

  14:         public FourSquareLogin()

  15:         {

  16:             InitializeComponent();

  17:         }

  18:  

  19:         private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)

  20:         {

  21:             // Manejar aquí el evento de navegación para ver si estamos en la 

  22:             // uri de callback.

  23:         }

  24:     }

  25: }

Sin embargo habíamos quedado en que esta no es la forma adecuada.

Navegando con MVVM (no funciona como me gustaría)

El segundo intento me llevó un poco más de tiempo y lectura. El framework MVVM Light implementa un comportamiento (behavior) que puede utilizarse como mecanismo para asociar el evento con un comando en la VistaModelo. El EventToCommand Behavior

Primero de todo hemos de incluir la referencia correspondiente del espacio de nombres dentro de la página.

   1: xmlns:gse="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"

A continuación declaramos unos cuantos disparadores para capturar los eventos de navegación.

   1: <phone:WebBrowser

   2:     HorizontalAlignment="Stretch"

   3:     Name="webBrowser1"

   4:     VerticalAlignment="Stretch"

   5:     IsScriptEnabled="True"

   6:     Source="{Binding Path=FourSquareAuthUrl}">

   7:     <i:Interaction.Triggers >

   8:         <i:EventTrigger EventName="Navigating" >

   9:             <gse:EventToCommand

  10:                 Command="{Binding Path=EstaNavegando, Mode=OneWay}"

  11:                 MustToggleIsEnabled="True" 

  12:                 CommandParameter="{Binding ElementName=webBrowser1, Path=Source.AbsoluteUri}" />

  13:         </i:EventTrigger>

  14:  

  15:         <i:EventTrigger EventName="Navigated" >

  16:             <gse:EventToCommand

  17:                 Command="{Binding Path=HaNavegado, Mode=OneWay}"

  18:                 MustToggleIsEnabled="True" 

  19:                 CommandParameter="{Binding ElementName=webBrowser1, Path=Source.AbsoluteUri}" />

  20:         </i:EventTrigger>

  21:  

  22:         <i:EventTrigger EventName="NavigationFailed" >

  23:             <gse:EventToCommand

  24:                 Command="{Binding Path=ErrorNavegando, Mode=OneWay}"

  25:                 MustToggleIsEnabled="True" 

  26:                 CommandParameter="{Binding ElementName=webBrowser1, Path=Source.AbsoluteUri}" />

  27:         </i:EventTrigger>

  28:     </i:Interaction.Triggers>

  29: </phone:WebBrowser>

Como puede verse, se declaran disparadores de eventos y se incluye el comportamiento EventToCommand para manejar dicho evento. En este segundo intento, pasé la Uri del control. Sin embargo, no conseguí el objetivo deseado.

Mi intención era, aprovechar la url durante las redirecciones, analizarla y cuando estuviéramos en la url de callback, extraer el token. Para mi mala suerte, pude comprobar que la url pasada como parámetro no cambiaba en absoluto durante las redirecciones (quizá tenga que analizar esto un poco más y sea una cuestión de configuración).

En resumen, lo que yo quería era disponer del estado de la navegación. Había podido observar que, en los manejadores de evento los argumentos NavigatingEventArgs, NavigationEventArgs y NavigationFailedEventArgs sí contenían la información que buscaba, así que. ¿Cómo pasar los argumentos del evento directamente a la VistaModelo?

Navegando con MVVM (ahora sí que sí)

Si le echas un vistazo a la documentación de la MSDN se indica claramente que el evento Navigating contiene información de navegación incluso durante redirecciones, así que poder pasar los parámetros de dicho evento a mi VistaModelo es fundamental para poder obtener mi token (al menos durante las pruebas).

La respuesta es muy sencilla, eliminar el primer intento de pasara la Url del control y, simplemente, poner a true el atributo PassEventArgsToCommand.

   1: <phone:WebBrowser

   2:     HorizontalAlignment="Stretch"

   3:     Name="webBrowser1"

   4:     VerticalAlignment="Stretch"

   5:     IsScriptEnabled="True"

   6:     Source="{Binding Path=FourSquareAuthUrl}" >

   7:         <i:Interaction.Triggers >

   8:             <i:EventTrigger EventName="Navigating" >

   9:                <gse:EventToCommand

  10:                    Command="{Binding Path=EstaNavegando, Mode=OneWay}"

  11:                    PassEventArgsToCommand="True"

  12:                    MustToggleIsEnabled="True" />

  13:             </i:EventTrigger>

  14:         </i:Interaction.Triggers>

  15: </phone:WebBrowser>

Y en la VistaModelo…

   1: namespace Lcdad.SquareMatrix.ViewModel

   2: {

   3:     public class FourSquareLoginViewModel : ViewModelBase

   4:     {

   5:         private IFourSquareHelper _fsHelper;

   6:         private ICommand _haNavegado;

   7:         private ICommand _estaNavegando;

   8:         private ICommand _errorNavegando;

   9:         private ICommand _paginaCargada;

  10:  

  11:         public FourSquareLoginViewModel()

  12:         {

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

  14:  

  15:             _fsHelper = SimpleIoc.Default.GetInstance<IFourSquareHelper>();

  16:  

  17:             _haNavegado = new RelayCommand<NavigationEventArgs>(AccionNavegado);

  18:             _estaNavegando = new RelayCommand<NavigatingEventArgs>(AccionNavegando);

  19:             _errorNavegando = new RelayCommand<NavigationFailedEventArgs>(AccionErrorNavegando);

  20:             _paginaCargada = new RelayCommand<NavigationEventArgs>(AccionPaginaCargada);

  21:         }

  22:  

  23:         private void AccionNavegado(NavigationEventArgs nea)

  24:         {

  25:         }

  26:  

  27:         private void AccionPaginaCargada(NavigationEventArgs nea)

  28:         {

  29:         }

  30:  

  31:         private void AccionNavegando(NavigatingEventArgs nea)

  32:         {

  33:             // Cuando llega la redirección del token, esta no se procesa enteramente, debemos de capturarla aquí mismo.

  34:             if (_fsHelper.EsUriCallback(nea.Uri))

  35:             {

  36:                 var token = _fsHelper.ObtenerTokenDesdeUri(nea.Uri);

  37:  

  38:                 if (!String.IsNullOrEmpty(token))

  39:                 {

  40:                     // TODO: Almacenar el token en BBDD

  41:                 }

  42:             }

  43:         }

  44:  

  45:         private void AccionErrorNavegando(NavigationFailedEventArgs nfea)

  46:         {

  47:         }

  48:     }

  49: }

Con esto ya lo hemos conseguido. ¡Nos registramos en Foursquare!

Lo siguiente: consumir los servicios expuestos.

Empezando con Windows Phone 7.1 (y XV) – Navegación con MVVM Light

En Windows Phone 7, la navegación se maneja desde la vista. Concretamente, todas las páginas de una aplicación comparten la misma instancia del servicio de navegación a través de una propiedad: Page.NavigationService

Los patrones de diseño para la interfaz de usuario como MVP, MVC o MVVM procuran sacar de la Vista toda la lógica de navegación posible. Nos encontramos, sin embargo, que en Windows Phone, el servicio de navegación se accede desde la propia PhoneApplicationPage. Concretamente, en una aplicación WP7 el responsable de la navegación es el marco principal (main frame) o RootFrame.

Existen varias alternativas para implementar navegación utilizando el patrón MVVM y, concretamente, haciendo uso de MVVM Light.

Navegación con Mensajes

Una primera forma es haciendo uso de la infraestructura de mensajes que proporciona MVVM Light. Esta infraestructura es utilizada con frecuencia en el patrón MVP para coordinar diferentes vistas. Me dejo una entrada en el futuro para los detalles de este tipo de infraestructuras. Si queréis saber cómo implementar navegación haciendo uso de esta técnica, echadle un vistazo a este artículo.

Utilizando el Servicio de Navegación desde la Vista Modelo

Para esta entrada he preferido sin embargo, otra aproximación que es hacer uso del servicio de navegación desde la Vista-Modelo.

Si le echáis un vistazo al servicio de navegación que tenemos en Windows Phone, os encontraréis con esto.

image

De toda la firma me voy a quedar con lo justo declarando una interfaz muy sencilla.

 1: /// <summary>

 2: /// Interfaz del servicio de navegación.

 3: /// </summary>

 4: public interface INavigationService

 5: {

 6:     /// <summary>

 7:     /// Evento de navegación

 8:     /// </summary>

 9:     event NavigatingCancelEventHandler Navigating;

 10:

 11:     /// <summary>

 12:     /// Permite navegar a la página indicada como parámetro

 13:     /// </summary>

 14:     /// <param name="pageUri">Instancia de <see cref="Uri"/> que hace referencia a la página a la que se navega</param>

 15:     void NavigateTo(Uri pageUri);

 16:

 17:     /// <summary>

 18:     /// Permite navegar hacia atrás en la pila de navegación

 19:     /// </summary>

 20:     void GoBack();

 21: }

Ahora, crearemos una clase para el servicio de navegación que implemente esta interfaz (acordaos de lo que os decía más arriba sobre el hecho de que es el RootFrame de una página el que se encarga de acceder al servicio).

En mi caso, estas clases se están yendo a una biblioteca de utilidades con lo que tendré que añadir una referencia al Assembly Microsoft.Phone.

 1: namespace Jdmveira.WindowsPhone.Mvvm.Navigation

 2: {

 3:     using System;

 4:     using System.Windows;

 5:     using Microsoft.Phone.Controls;

 6:

 7:     /// <summary>

 8:     /// Clase encargada de implementar el servicio de navegación

 9:     /// </summary>

 10:     public class NavigationService : INavigationService

 11:     {

 12:         #region Constructores

 13:

 14:         /// <summary>

 15:         /// Constructor por defecto de la clase

 16:         /// </summary>

 17:         public NavigationService()

 18:         {

 19:             // No podemos estar seguros de que en cualquier momento existe el RootFrame.

 20:             EnsureMainFrame();

 21:         }

 22:

 23:         #endregion Constructores

 24:

 25:         #region Miembros Públicos

 26:

 27:         /// <summary>

 28:         /// Evento de navegación

 29:         /// </summary>

 30:         public event System.Windows.Navigation.NavigatingCancelEventHandler Navigating;

 31:

 32:         /// <summary>

 33:         /// Método para navegar a la página indicada por la <see cref="Uri"/> pasada como parámetro

 34:         /// </summary>

 35:         /// <param name="pageUri">Instancia de <see cref="Uri"/> que indica la página a la que se navega</param>

 36:         public void NavigateTo(Uri pageUri)

 37:         {

 38:             if (EnsureMainFrame())

 39:             {

 40:                 _mainFrame.Navigate(pageUri);

 41:             }

 42:         }

 43:

 44:         /// <summary>

 45:         /// Navega hacia atrás en la pila de navegación

 46:         /// </summary>

 47:         public void GoBack()

 48:         {

 49:             if (EnsureMainFrame())

 50:             {

 51:                 _mainFrame.GoBack();

 52:             }

 53:         }

 54:

 55:         #endregion Miembros Públicos

 56:

 57:         #region Miembros Privados

 58:

 59:         /// <summary>

 60:         /// Se asegura de que existe una referncia al frame principal de una página

 61:         /// </summary>

 62:         private bool EnsureMainFrame()

 63:         {

 64:             if (_mainFrame != null)

 65:                 return true;

 66:

 67:             _mainFrame = Application.Current.RootVisual as PhoneApplicationFrame;

 68:             if (_mainFrame != null)

 69:             {

 70:                 // Adjuntamos nuestro propio manejador de eventos

 71:                 _mainFrame.Navigating += (s, e) =>

 72:                     {

 73:                         if (Navigating != null)

 74:                             Navigating(s, e);

 75:                     };

 76:

 77:                 return true;

 78:             }

 79:

 80:             return false;

 81:         }

 82:

 83:         private PhoneApplicationFrame _mainFrame;

 84:

 85:         #endregion Miembros Privados

 86:     }

 87: }

A partir de aquí, registrar el servicio en nuestro IoC y utilizarlo desde cualquier Vista-Modelo es un asunto trivial.

Echadle un vistazo a este post, de donde he sacado esta solución.

Pegas a esta implementación

Fundamentalmente, que en el fondo, la implementación depende de la clase PhoneApplicationFrame, pero no podemos evitar esto. ¿Verdad? Guiño