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.

Anuncios

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.

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 XIV) – Asignando Comandos a Eventos.

En anteriores posts hemos visto que algunos controles de presentación, como la Barra de Aplicación, no son sencillos de montar con un patrón MVVM (fundamentalmente porque son objetos del shell y no objetos Silverlight).

En este caso vamos a ver como otros controles Silverlight que no se ajustan exactamente al modelo y, aun así, podremos manejar eventos con facilidad utilizando el patrón.

Para ello vamos a basarnos en una página de configuración que estoy montando en mi próximo proyecto.

 

image

Como podéis ver, los dos controles inferiores permiten activar y desactivar notificaciones (Live Tiles y Toast Notifications). Ambos controles son de la clase ToggleSwitch.

La clase ToggleSwitch describe eventos para manjar (entre otros) el Click, Checked y Unchecked. Sin embargo, no dispone de la posibilidad de invocar un comando (como sí pasa con Button).

Aquí es donde vamos a trabajar con una funcionalidad incorporada en el SDK 7.1 que nos permitirá asociar fácilmente (y de forma declarativa) eventos a comandos.

Lo primero es hacer referencia en la página al siguiente espacio de nombres.

   1:  

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

   3:  

A continuación, en cada uno de los controles declararemos “disparadores” (Triggers) que invocarán un comando.

   1: <tool:ToggleSwitch Grid.Column="1" x:Name="PermitirTiles" Header="{Binding Source={StaticResource LocationHelper}, Path=Recursos.PermitirActiveTiles}" Content="{Binding Path=TextoActivarTiles}" IsChecked="{Binding Path=ActivarTiles}">

   2:    <i:Interaction.Triggers>

   3:        <i:EventTrigger EventName="Checked">

   4:            <i:InvokeCommandAction Command="{Binding Path=ComandoActivarTiles}" CommandParameter="{Binding ElementName=PermitirTiles, Path=IsChecked}" />

   5:        </i:EventTrigger>

   6:  

   7:        <i:EventTrigger EventName="Unchecked">

   8:            <i:InvokeCommandAction Command="{Binding Path=ComandoActivarTiles}" CommandParameter="{Binding ElementName=PermitirTiles, Path=IsChecked}" />

   9:        </i:EventTrigger>

  10:    </i:Interaction.Triggers>

  11: </tool:ToggleSwitch>

Como podéis ver, en este caso se disparan “Triggers” en los eventos Checked y Unchecked del control, en ambos casos se invoca al mismo comando y, en ambos casos pasando como parámetro la propiedad "IsChecked” del propio control. La diferencia es que, dependiendo del evento lanzado, dicha propiedad valdrá true o false.

A continuación en el ModeloVista declaremos las siguientes propiedades (he eliminado algo de código para facilitar el seguimiento).

   1: public class ConfiguracionViewModel : ViewModelBase

   2: {

   3:     private ICommand _comandoActivarTiles;

   4:     private ICommand _comandoActivarToast;

   5:  

   6:     /// <summary>

   7:     /// Initializes a new instance of the ConfiguracionViewModel class.

   8:     /// </summary>

   9:     /// <param name="dataService">Instancia de <see cref="IConfiguracionDataService"/> que proporciona acceso al servicio de datos</param>

  10:     public ConfiguracionViewModel(IConfiguracionDataService dataService)

  11:     {

  12:         if (dataService != null)

  13:             _dataService = dataService;

  14:     

  15:         // Creamos el comando para activar /desactivar tiles y el de activar /desactivar toast

  16:         _comandoActivarTiles = new RelayCommand<bool>((bool p) => ActivarTiles = p);

  17:         _comandoActivarToast = new RelayCommand<bool>((bool p) => ActivarToast = p);

  18:     

  19:         LoadData();

  20:     }

  21:  

  22:     private static readonly string TextoActivarTilesNombreProp = "TextoActivarTiles";

  23:  

  24:     /// <summary>

  25:     /// Texto que se mostrará dependiendo del estado actual de la activación de tiles

  26:     /// </summary>

  27:     public string TextoActivarTiles

  28:     {

  29:         get { return _textoActivarTiles; }

  30:         private set

  31:         {

  32:             _textoActivarTiles = value;

  33:             RaisePropertyChanged(TextoActivarTilesNombreProp);

  34:         }

  35:     }

  36:  

  37:     private static readonly string TextoActivarToastNombreProp = "TextoActivarToast";

  38:  

  39:     /// <summary>

  40:     /// Texto que se mostrará dependiendo del estado actual de la activación de tiles

  41:     /// </summary>

  42:     public string TextoActivarToast

  43:     {

  44:         get { return _textoActivarToast; }

  45:         private set

  46:         {

  47:             _textoActivarToast = value;

  48:             RaisePropertyChanged(TextoActivarToastNombreProp);

  49:         }

  50:     }

  51:  

  52:     /// <summary>

  53:     /// Propiedad que permite acceder al comando de activación /desactivación de tiles

  54:     /// </summary>

  55:     public ICommand ComandoActivarTiles

  56:     {

  57:         get { return _comandoActivarTiles; }

  58:     }

  59:  

  60:     /// <summary>

  61:     /// Propiedad que permite acceder al comando de activación /desactivación de notificaciones Toast

  62:     /// </summary>

  63:     public ICommand ComandoActivarToast

  64:     {

  65:         get { return _comandoActivarToast; }

  66:     }

  67:  

  68:     private static readonly string ActivarTilesNombreProp = "ActivarTiles";

  69:  

  70:     /// <summary>

  71:     /// Permite acceder al valor que indica si los tiles activos están o no habilitados

  72:     /// </summary>

  73:     public bool? ActivarTiles

  74:     {

  75:         get { return _preferencias.EnableTileNotif; }

  76:         set

  77:         {

  78:             if (_preferencias.EnableTileNotif == value)

  79:                 return;

  80:  

  81:             _preferencias.EnableTileNotif = value;

  82:             TextoActivarTiles = ObtenerCaptionToggleSwitch(value);

  83:  

  84:             RaisePropertyChanged(ActivarTilesNombreProp);

  85:         }

  86:     }

  87:  

  88:     private static readonly string ActivarToastNombreProp = "ActivarToast";

  89:  

  90:     /// <summary>

  91:     /// Permite acceder al valor que indica si las notificaciones toast están o no habilitadas

  92:     /// </summary>

  93:     public bool? ActivarToast

  94:     {

  95:         get { return _preferencias.EnableToastNotif; }

  96:         set

  97:         {

  98:             if (_preferencias.EnableToastNotif == value)

  99:                 return;

 100:  

 101:             _preferencias.EnableToastNotif = value;

 102:             TextoActivarToast = ObtenerCaptionToggleSwitch(value);

 103:  

 104:             RaisePropertyChanged(ActivarToastNombreProp);

 105:         }

 106:     }

 107:  

 108:     /// <summary>

 109:     /// Devuelve una cadena con el título de un ToggleSwitch en función del valor pasado como parámetro.

 110:     /// </summary>

 111:     /// <param name="param">Valor booleano asociado al estado del ToggleSwitch</param>

 112:     /// <returns>Cadena con el título que debe de mostrar el ToggleSwitch</returns>

 113:     private static string ObtenerCaptionToggleSwitch(bool? param)

 114:     {

 115:         return (param.HasValue && param.Value) ? Resources.CaptionActivo : Resources.CaptionInactivo;

 116:     }

 117: }

Esencialmente se crean comandos que cambian el valor del modelo y los literales asociados al estado del control (buscando dichos literales en los recursos).

La clase RelayCommand está declarada en el framework MVVM Light.

El código no está limpio pero probado y funciona.

image

Empezando con Windows Phone 7.1 (y XIII) – Creando una nueva vista. Navegación entre páginas.

¡El horror, oh el horror!

En el anterior post nos quedamos con una forma de crear un nuevo lugar un tanto deleznable.

image

Así pues, creo que lo mejor es hacer que la nueva interfaz aproveche la barra de aplicación de Windows Phone.

Aplicando algunos cambios.

Para ello, he realizado algunos cambios en la aplicación. He creado una carpeta en la solución donde podré organizar más fácilmente las páginas y he movido la página principal a dicha carpeta.

image

Esto me ha obligado a realizar algunos cambios menores en el código.

En el manifiesto de la aplilcación

image

En el cual se indica la página de inicio para la aplicación, he cambiado la ruta.

 1: <Tasks>

 2:     <DefaultTask

 3:         Name="_default"

 4:         NavigationPage="/Pages/MainPage.xaml" />

 5: </Tasks>

Y, en la propia página, he cambiado la url relativa a las imágenes.

 1: <controls:Panorama.Background>

 2:     <ImageBrush

 3:         ImageSource="../Resources/images/Panorama%20Green.png"/>

 4: </controls:Panorama.Background>

Ahora vamos a realizar algunos cambios en la página principal. De momento eliminaremos el PanoramaItem que estaba destinado a contener los controles para crear un nuevo lugar y añadiremos la posibilidad de crear un nuevo lugar desde cualquier punto de la página principal. Para ello, vamos a crear una ApplicationBar en la página principal.

 1: <phone:PhoneApplicationPage.ApplicationBar>

 2:     <shell:ApplicationBar

 3:             Mode="Minimized"

 4:             IsMenuEnabled="False"

 5:             Opacity="0.7">

 6:

 7:         <shell:ApplicationBarIconButton

 8:             x:Name="NuevoLugarButton"

 9:             IconUri="/Resources/icons/Add%20New%20Place.png"

 10:             Text="Nuevo Lugar" />

 11:     </shell:ApplicationBar>

 12: </phone:PhoneApplicationPage.ApplicationBar>

Esto nos permite disponer de una barra de aplicaciones que no siempre estará visible, de esta forma aprovecharemos al máximo el espacio vertical.

Asignando Acciones a los botones de la ApplicationBar

Maldición. El primer problema que me he encontrado es. ¿Cómo puedo utilizar Comandos con los botones de la Application Bar para aprovecharme del patrón visto antes en MVVM? El control dispone de una propiedad Click que te permite asignar código en el “Code Behind” de la Vista, pero no de la VistaModelo como vimos en el anterior post (mediante Binding).

El otro problema es. ¿Cómo puedo asignar al texto del botón una entrada de mi fichero de recursos? Ni con Visual Studio ni con Blend me permite hacer uso de mi clase para manejar los recursos de la aplicación.

Proporcionando soporte multi-idioma y comandos en un ApplicationBarIconButton

¿Queréis la respuesta rápida?

No es posible sin cierta cantidad de esfuerzo ya que la barra de aplicación no es un componente Silverlight. Se trata de un objeto controlado por el Shell de Windows Phone, con lo que no aplican las mismas reglas que a cualquier otro control que incluyas en tu aplicación.

Confused smile

Resumiento la situación actual

En fin, algo que parecía bastante trivial se está convirtiendo en un pequeño problema:

  1. La barra de aplicación no es un control Silverlight, con lo que trabajar con la misma haciendo uso del patrón MVVM resulta complicado (recordad que la idea es evitar, en la medida de lo posible) tocar el Code Behind de la página.
  2. La navegación se realiza mediante la clase NavigationService que está accesible desde la propia págia como una propiedad.
  3. Desde la VistaModelo deberíamos de evitar instanciar nuevas clases que nos introduzcan dependencias (si tienes la palabra clave “new” en tu código lo mejor que puedes hacer es revisar el código). Un patrón como IoC viene aquí que ni al pelo.
  4. Crear una nueva vista y asociar correctamente su VistaModelo de forma flexible.

Siguientes Pasos

Si bien es perfectamente posible realizar todas las tareas descritas en el punto anterior (con mayor o menor esfuerzo). Este es un buen momento para hacer un alto en el camino y revisar algunas opciones que nos ayudarán con el Modelo Vista Vista Modelo y proseguir con el desarrollo apoyándonos en alguno de los siguientes Frameworks.