Migrar código de Windows 8 a Windows Phone 8

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

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

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

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

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

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

Reutilización W8 /WP8

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

Separación de Responsabilidades en la Vista

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

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

Es decir, partimos de algo parecido a esto:

image

Para terminar en algo similar a esto otro:

image

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

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

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

Abstracción de Funcionalidades del SO

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

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

Mensajes, mensajes, mensajes

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

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

Un Ejemplo de Mensaje

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

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

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

En Resumen

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

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

¿Mostrar el cursor en un TextBox?

Mucho tiempo sin poner nada en el blog. Demasiado.

Probablemente siga durante una temporada sin poder contribuir de forma regular, pero vamos a intentar de que sea información útil (subrayemos intentar).

Durante los últimos meses he estado trabajando en un proyecto personal con un grupo de amigos. Espero que pronto pueda subir una entrada en este blog, de momento diré que estoy un poco aTareado.

Al grano. ¿Qué pasa con los cursores (caret)?

Dejadme que os sitúe: Windows 8 App Store y sus pequeñas cosillas (con el tiempo estoy descubriendo que no le faltan pequeñas cosillas)

Estamos en fase de pruebas y, repentinamente me llega una pequeña incidencia (pequeña por ser trivial en apariencia): “Oye, Juan, que el cursor no se ve cuando el campo de texto coge el foco y empiezo a escribir en él”

image

Vaya, claro. ¡Cómo va a verse si tiene un feo color negro sobre un fondo azul oscuro! Nada, no nos preocupemos. Bastará con dar con la propiedad o estilo que afecte al cursor (caret), y de paso al aspa de borrado, y la cambiamos de negro a blanco. ¿Verdad?

Pues no.

Dicho sea de paso que el aspa de borrado sí puede tocarse, pero no ocurre lo mismo con el cursor, tal y como puede verse aquí.

La referencia tiene casi medio año en el momento en que esta entrada se escribe y, sigo sin encontrar nada que lo contradiga en la procelosa web.

Así que. ¿Qué hacer?

Pues bien habrá que conformarse (de momento) con una solución un tanto desagradable: Cambiemos el fondo del cuadro de texto cuando el mismo tenga el foco.

Dicho y hecho.

Vamos a hacerlo de dos formas. Una utilizando únicamente XAML y la otra con unas pocas líneas de código que escribiremos por detrás.

Con la ayuda de Blend

Esta forma es muy sencilla y rápida. Primero, con la ayuda de Visual Studio (más porque lo tengo ya abierto que porque sea la única forma). Pulsaré con el botón derecho sobre el control y crearé una copia de la plantilla del mismo.

image

Una vez creada la copia podemos seguir en Visual Studio, pero os recomiendo que paséis a Blend donde podremos editar las propiedades según el estado visual de manera más sencilla (el paso descrito en el párrafo anterior también puede realizarse en Blend, por supuesto).

image

Pues bien, ya tenemos Blend mostrándonos la plantilla del control.

image

Centrémonos en la esquina superior izquierda, donde se enumeran los estados visuales del control. Concretamente, nos fijaremos en el estado visual “Focused”.

Una vez seleccionamos dicho estado, Blend se pone a “grabar” los cambios que realicemos sobre las propiedades del control. Concretamente nos centraremos en dos elementos del control de texto: “BackgroundElement” y “ContentElement”

image

En mi caso he creado un color blanco con transparencia, he creado un recurso para dicho color (luego veremos por qué) y se lo he asignado al fondo “background”.

Lo mismo para el color del texto (originalmente blanco). He creado un color negro, he creado un recurso para dicho color (de nuevo, un poco más adelante veremos por qué) y se lo he asignado al color de la fuente “foreground”.

image

Como veis, con muy poco esfuerzo hemos conseguido el objetivo secundario (no he cambiado el color del cursor, pero al menos consigo que se vea):

image

image

Con la ayuda de unas líneas de código

Sin embargo, no todos los controles de texto en la aplicación son TextBox, en algún punto del desarrollo decidimos introducir unos controles de terceros sobre los que la creación de la plantilla con Blend no nos proporciona el resultado adecuado. ¿Qué hacer entonces?

Escribamos un poquito de código, que no se nos caerán los anillos.

Lo primero es crear una clase que herede de la clase que representa el control de texto.

public sealed class MiTextBoxExt : TextBoxExtDeOtros
    {
        #region Constructor

        static MiTextBoxExt()
        {
            ResourceDictionary dict = new ResourceDictionary();
            dict.Source = new Uri("ms-appx:///Themes/TextBoxTemplate.xaml");

            FOCUSED_BACKGROUND_COLOR = dict["FocusedTextBoxBackground"] as SolidColorBrush;
            FOCUSED_FOREGROUND_COLOR = dict["FocusedTextBoxForeground"] as SolidColorBrush;
        }

        /// <summary>
        /// Constructor por defecto de la clase
        /// </summary>
        public MiTextBoxExt(): base()
        {
        }
        #endregion

        #region Miembros Protegidos

        protected override void OnGotFocus(Windows.UI.Xaml.RoutedEventArgs e)
        {
            base.OnGotFocus(e);

            _previousBackground = this.Background as SolidColorBrush;
            _previousForeground = this.Foreground as SolidColorBrush;

            this.Background = FOCUSED_BACKGROUND_COLOR;
            this.Foreground = FOCUSED_FOREGROUND_COLOR;
        }

        protected override void OnLostFocus(Windows.UI.Xaml.RoutedEventArgs e)
        {
            base.OnLostFocus(e);

            this.Background = _previousBackground;
            this.Foreground = _previousForeground;
        }

        #endregion

        #region Miembros Privados

        //Brush 
        private static readonly SolidColorBrush FOCUSED_BACKGROUND_COLOR;
        private static readonly SolidColorBrush FOCUSED_FOREGROUND_COLOR;

        private SolidColorBrush _previousForeground;
        private SolidColorBrush _previousBackground;
        #endregion
    }

Como se puede ver la solución es muy sencilla.

  1. Recuperamos del diccionario de recursos los colores definidos en el método anterior (así, si cambiamos el estilo no será necesario cambiar el código)
  2. Cuando el control toma el foco se almacenan los colores de fondo y fuente originales para luego poderlos restaurar con comodidad.
  3. Cuando el control pierde el foco, simplemente restaura los colores previamente almacenados.

Conclusión

Pues que no hemos solucionado el problema, pero hemos encontrado una forma de evitarlo.

¿Sabe alguien alguna forma mejor?

[WinRT] Reutilizando Paths

Recientemente me he encontrado con algún problema que todavía no he conseguido solucionar (espero que gente más sabia que yo sepa guiarme). Mientras el consejo llega, hay que seguir avanzando, así que aprovecho para contar qué bonitas sorpresas me he encontrado.

El Problema

Estoy creando un pequeño control de usuario muy sencillo, dicho control debería de mostrar información gráfica en modo de icono que represente categorías asociadas al elemento asociado al control de usuario. A modo de ejemplo nos bastaría con lo siguiente:

image

Como podéis ver se trata de algo extremadamente sencillo y su comportamiento es como cabría esperar cuando se muestran varias instancias de dicho control.

image

El siguiente paso es ser un poco más flexible: supongamos que cada instancia del control tiene asociada una categoría que puede cambiar, además deseamos que el gráfico vectorial que representa la categoría sea diferente para cada categoría.

Si queremos que nuestro control cambie de gráfico, nos bastaría con cambiar el Path (asociando el Path al modelo, lo cual no me gusta demasiado) o bien generando estilos y cambiando los estilos dependiendo de la categoría.

Esto último, por alguna razón, me sonó bien así que me puse con ello (un par de ejemplos deberían de ser suficientes para captar la idea):

 

    <Style x:Key="Calendar_Icon" TargetType="Path">
        <Setter Property="Data" Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M152.874,228.86L161.875,220.539C172.403,225.634 179.081,229.51 190.915,237.946 213.161,212.699 227.862,199.89 255.186,182.89L258.115,189.627C235.581,209.291 219.078,231.194 195.314,273.811 180.657,256.55 170.875,245.546 152.874,228.86z M152.874,335.86L161.875,327.539C172.403,332.634 179.081,336.51 190.915,344.946 213.161,319.699 227.862,306.89 255.186,289.89L258.115,296.627C235.581,316.291 219.078,338.194 195.314,380.811 180.657,363.55 170.875,352.546 152.874,335.86z M357.583,220.5L287.583,220.5 287.583,200.5 357.583,200.5 357.583,220.5z M357.583,238L287.583,238 287.583,258 357.583,258 357.583,238z M357.583,337.167L287.583,337.167 287.583,317.167 357.583,317.167 357.583,337.167z M357.583,354.667L287.583,354.667 287.583,374.667 357.583,374.667 357.583,354.667z" />
    </Style>

    <Style x:Key="Todo_Icon" TargetType="Path">
        <Setter Property="Data" Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M162.874,277.426L179.23,262.305C198.363,271.563 210.496,278.607 232.001,293.936 272.427,248.059 299.14,224.784 348.793,193.891L354.115,206.133C313.168,241.867 283.178,281.668 239.996,359.11 213.361,327.741 195.585,307.746 162.874,277.426z" />
    </Style>

Si ahora llevamos esto a nuestro control de usuario…

image

Lo primero que nos llama la atención es que el diseñador no muestra nada. Pero el comportamiento en tiempo de ejecución es todavía más raro.

image

Sólo se muestra el icono para la primera instancia del control Surprised smile

La solución

Pues para ser sinceros, todavía sigo sin dar con cuál es la causa de este comportamiento. ¿Algún error de primero de WinRT que se me haya escapado?

Evitando el Problema

Si bien es cierto que todavía no he dado con la causa del problema, me resisto a abandonar mi idea de utilizar gráficos vectoriales ya que escalaran mucho mejor que los iconos cuando cambie la resolución de la pantalla, así que vamos con una posible forma de “evitar el problema”

El fichero StandarStyles.xaml que encontraréis en cualquier solución no vacía de Visual Studio 2012 para aplicaciones del Store de Windows contiene una gran cantidad de recursos de estilos que podemos utilizar de salida en cualquiera de nuestras soluciones.

Concretamente, para la barra de tareas hay un buen montón de botones disponibles y todos ellos se basan en la misma idea.

  1. Crear un botón que defina fundamentalmente la estructura y composición del botón (de forma resumida consiste en un StackPanel, con un contenido interior, un marco circular que rodea dicho contenido y un texto)
  2. Un conjunto de estilos que lo único que hacen es cambiar el icono que se muestra en el contenido interno.

Siguiendo esa misma idea, voy a crear un estilo para botón que defina la estructura común (esencialmente, un botón sin marco) para luego crear un conjunto de estilos que definan el Path que dibuje el icono deseado.

<Style x:Key="NoBorderButtonStyle" TargetType="ButtonBase">
        <Setter Property="MinWidth" Value="0"/>
        <Setter Property="MinHeight" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonBase">
                    <Grid Background="Transparent">
                        <ContentPresenter x:Name="Text" Content="{TemplateBinding Content}" />
                        <Rectangle
                            x:Name="FocusVisualWhite"
                            IsHitTestVisible="False"
                            Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="1.5"/>
                        <Rectangle
                            x:Name="FocusVisualBlack"
                            IsHitTestVisible="False"
                            Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="0.5"/>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPointerOverForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualWhite" Storyboard.TargetProperty="Opacity"/>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualBlack" Storyboard.TargetProperty="Opacity"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused"/>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="CheckStates">
                                <VisualState x:Name="Checked"/>
                                <VisualState x:Name="Unchecked">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationSecondaryForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Indeterminate"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Este ejemplo todavía no está muy trabajado, así que puede tener errores (avisados quedáis).

A continuación un estilo que aplica también a botones pero que define un Path en el contenido interior del botón (podría haber fusionado ambos en uno, pero de momento lo dejaré en dos estilos por separado).

    <Style x:Key="PathBasedButtonStyle" BasedOn="{StaticResource NoBorderButtonStyle}" TargetType="ButtonBase">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid>
                        <Path 
                            Stretch="Uniform"
                            Fill="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                            Data="{Binding Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Por último, ya podemos definir un estilo diferente por cada Path que queramos reutilizar en los botones de nuestra aplicación.

    <Style 
        x:Key="CalendarIconPathBasedButtonStyle" 
        BasedOn="{StaticResource PathBasedButtonStyle}"  
        TargetType="ButtonBase">
        
        <Setter 
            Property="Content" 
            Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M152.874,228.86L161.875,220.539C172.403,225.634 179.081,229.51 190.915,237.946 213.161,212.699 227.862,199.89 255.186,182.89L258.115,189.627C235.581,209.291 219.078,231.194 195.314,273.811 180.657,256.55 170.875,245.546 152.874,228.86z M152.874,335.86L161.875,327.539C172.403,332.634 179.081,336.51 190.915,344.946 213.161,319.699 227.862,306.89 255.186,289.89L258.115,296.627C235.581,316.291 219.078,338.194 195.314,380.811 180.657,363.55 170.875,352.546 152.874,335.86z M357.583,220.5L287.583,220.5 287.583,200.5 357.583,200.5 357.583,220.5z M357.583,238L287.583,238 287.583,258 357.583,258 357.583,238z M357.583,337.167L287.583,337.167 287.583,317.167 357.583,317.167 357.583,337.167z M357.583,354.667L287.583,354.667 287.583,374.667 357.583,374.667 357.583,354.667z" />
    </Style>

Ahora podemos reutilizar en nuestro control el botón que hemos definido:

<UserControl
    x:Class="Controls.BlogSamplePathProblem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    
    <Grid Background="CornflowerBlue">
        <StackPanel Orientation="Vertical">
        <Path 
            x:Name="CheckMarkPath" 
            Data="F1 M389.814,253.305L389.814,426.354 50,426.354 50,86.537 366.809,86.537 325.568,127.777 91.24,127.777 91.24,385.114 348.575,385.114 348.575,294.365 389.814,253.305z M409.469,85.646L245.997,249.117 175.19,178.291 122.683,230.808 245.998,354.16 462,138.143 409.469,85.646z"
            Fill="White"
            Stretch="Uniform"
            Margin="10,10,10,0" Height="100" UseLayoutRounding="False" Width="100" />

            <Button 
                Margin="10,10,10,0" 
                Height="100" 
                UseLayoutRounding="False" 
                HorizontalAlignment="Stretch"
                Width="100" 
                Style="{StaticResource CalendarIconPathBasedButtonStyle}"/>
        </StackPanel>
    </Grid>
</UserControl>

Lo primero de todo es que nuestro diseñador SÍ que muestra el botón con el gráfico vectorial.

image

Por último, podemos ver nuestra aplicación en tiempo de ejecución…

image

Y… ¡Ahí está! Los tres controles mostrando sus gráficos vectoriales tal y como cabría esperar.

Si consigo un poco más de información al respecto procuraré actualizar la entrada con la misma.

Windows 8 – Control de Usuario

Mientras espero pacientemente a que pueda seguir programando mi teléfono móvil con Windows 8 y Visual Studio 2012

image

(Sí, ya sé que Windows 8 trae Hyper-V y podría instalarme una máquina virtual con Windows 7 y Visual Studio 2010).

Como decía, mientras tanto, he optado por empezar a explorar las posibilidades que tengo para programar aplicaciones XAML sobre Windows 8.

XAML (mi desconocimiento sobre…)

Lo primero es decir que no tengo mucha experiencia con WPF, así que con la idea de hacer una aplicación sencilla en la cabeza me he lanzado a ello.

Visual Studio 2012 permite comenzar con varias plantillas de aplicación para la tienda de Windows

  • Una aplicación vacía formada por una única pantalla sin contenido.

image

  • Una aplicación de cuadrícula organizada en tres pantallas y pensada en mostrar desde agrupaciones de elementos hasta los detalles de un elemento perteneciente a uno de los grupos

image

  • Una aplicación dividida que está organizada en dos pantallas, muy similar a la anterior en la que se muestran grupos de elementos en una pantalla y los elementos que pertenecen a un grupo junto con el detalle del elemento seleccionado en la otra pantalla.

image

Mi elección (con la idea de evitar complejidades en el primer contacto) ha sido la aplicación vacía.

Bibliotecas Portables

Mi objetivo es crear una aplicación donde el usuario puede ir creando de forma dinámica nodos que pueden tener relaciones entre sí (a modo de grafo).

Para ello he creado una biblioteca de clases donde he implementado la estructura de datos del grafo (así recuerdo un poco los estudios de la Universidad).

Una de las nuevas características que trae consigo el .NET Framework 4.5 es la Biblioteca Portable de Clases que te permite crear un ensamblado que puede ser reutilizado en diferentes tecnologías y dispositivos. Con este tipo de bibliotecas, puedo disponer de una colección de componentes que podré utilizar tanto en mis proyectos de Windows Phone (7.x y 8) y en mis proyectos basados en Windows 8. ¡Por fin!

image

Mi Primer Control de Usuario

El siguiente paso consiste en crear un control personalizado que me permita representar los nodos de un grafo.

Nada más sencillo ¿Verdad? (Pues, cosas de lo más triviales me han dado ciertos dolores de cabeza).

Lo primero, añadir a mi proyecto de Windows 8 un nuevo control. ¿Cuál escoger?

image

Tenemos dos tipos de controles.

  1. Controles con plantilla (Templated Controls)
  2. Controles de usuario (User Controls)

Bien, no voy a entrar ahora en diferencias entre unos y otros (todavía las estoy digiriendo). Como de lo que se trata es de aprender he optado por el camino del medio. ¡He implementado los dos tipos!

Mi objetivo es mostrar (en primera instancia) un rectángulo con un título, un área de texto descriptiva y una url que permita navegar a otro punto con más información (ese otro punto puede ser una web o una página de la propia aplicación. ¿Quién sabe?)

Algo de este estilo.

image

El proceso ha sido relativamente sencillo. Primero crear la estructura XAML que lo defina (esencialmente un grid con tres filas)

   1: <UserControl

   2:     x:Class="Jdmveira.Controls.NodeControl"

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

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

   5:     xmlns:local="using:Jdmveira.Controls"

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

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

   8:     mc:Ignorable="d"

   9:     d:DesignHeight="400"

  10:     d:DesignWidth="300">

  11:

  12:     <UserControl.Resources>

  13:         <Style x:Key="MainBorderDefaultNodeStyle" TargetType="Border">

  14:             <Setter Property="BorderThickness" Value="3" />

  15:             <Setter Property="CornerRadius" Value="1" />

  16:         </Style>

  17:

  18:         <Style x:Key="InnerBorderDefaultNodeStyle" TargetType="Border">

  19:             <Setter Property="BorderThickness" Value="2" />

  20:             <Setter Property="CornerRadius" Value="1" />

  21:         </Style>

  22:     </UserControl.Resources>

  23:

  24:     <Grid x:Name="CtrlMainLayout">

  25:         <Border x:Name="MainBorder" Style="{StaticResource MainBorderDefaultNodeStyle}">

  26:

  27:             <Border.BorderBrush>

  28:                 <SolidColorBrush x:Name="MainBorderColor" Color="#FF0067BD" />

  29:             </Border.BorderBrush>

  30:

  31:             <Border Style="{StaticResource InnerBorderDefaultNodeStyle}">

  32:

  33:                 <Border.BorderBrush>

  34:                     <SolidColorBrush x:Name="InnerBorderColor" Color="#FF84C7FF" />

  35:                 </Border.BorderBrush>

  36:

  37:                 <Grid MinHeight="150"

  38:                   MinWidth="150">

  39:                     <Grid.RowDefinitions>

  40:                         <RowDefinition Height="10*"/>

  41:                         <RowDefinition Height="50*"/>

  42:                         <RowDefinition Height="10*"/>

  43:                     </Grid.RowDefinitions>

  44:

  45:                     <TextBlock

  46:                         x:Name="TitleBlock"

  47:                         Margin="0"

  48:                         Text="Prueba"

  49:                         FontSize="24"

  50:                         Padding="6,2,0,0"

  51:                         Foreground="Black"

  52:                         Grid.Row="0">

  53:

  54:                     </TextBlock>

  55:

  56:                     <Rectangle

  57:                         Grid.Row="0"

  58:                         Fill="{StaticResource NodeCaptionBackground}"

  59:                         Canvas.ZIndex="-1"/>

  60:

  61:                     <TextBlock

  62:                         x:Name="DescriptionBlock"

  63:                         Text="Texto descriptivo que podría ocupar perfectamente más de una línea"

  64:                         TextWrapping="Wrap"

  65:                         Padding="4,4,4,0"

  66:                         Foreground="Black"

  67:                         Grid.Row="1"/>

  68:

  69:                     <Rectangle Grid.Row="1" Fill="White" Canvas.ZIndex="-1" />

  70:

  71:                     <HyperlinkButton

  72:                         x:Name="LinkMoreInfo"

  73:                         VerticalAlignment="Bottom"

  74:                         Grid.Row="2" HorizontalAlignment="Right">www.bing.es</HyperlinkButton>

  75:

  76:                         <Rectangle Grid.Row="2" Fill="White" Canvas.ZIndex="-1" />

  77:                 </Grid>

  78:             </Border>

  79:         </Border>

  80:         <Grid x:Name="GridShadows" Margin="-2,-2,-6,-6">

  81:             <Rectangle Stroke="Black" Margin="5" RadiusX="7" RadiusY="7" Opacity="0.3"/>

  82:             <Rectangle Stroke="Black" Margin="4" RadiusX="8" RadiusY="8" Opacity="0.25"/>

  83:             <Rectangle Stroke="Black" Margin="3" RadiusX="9" RadiusY="9" Opacity="0.2"/>

  84:             <Rectangle Stroke="Black" Margin="2" RadiusX="10" RadiusY="10" Opacity="0.15"/>

  85:             <Rectangle Stroke="Black" Margin="1" RadiusX="11" RadiusY="11" Opacity="0.1"/>

  86:             <Rectangle Stroke="Black" Margin="-37,0,37,0" RadiusX="12" RadiusY="12" Opacity="0.05"/>

  87:         </Grid>

  88:     </Grid>

  89: </UserControl>

Las diferencias para ambos han sido que el control de usuario tiene el XAML fuertemente asociado a la clase que implementa el control (el code behind, vamos), mientras que en el caso del control de plantilla define un fichero aparte con la correspondiente plantilla para cambiar el aspecto del control (Template)

   1: <Style x:Name="EstiloNodoGridView" TargetType="local:NodeControl2">

   2:     <Setter Property="Template">

   3:         <Setter.Value>

   4:             <ControlTemplate TargetType="local:NodeControl2">

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

   6:                     <Border x:Name="MainBorder" Style="{StaticResource MainBorderDefaultNodeStyle}">

   7:

   8:                         <Border.BorderBrush>

   9:                             <SolidColorBrush x:Name="MainBorderColor" Color="#FF0067BD" />

  10:                         </Border.BorderBrush>

  11:

  12:                         <Border Style="{StaticResource InnerBorderDefaultNodeStyle}">

  13:

  14:                             <Border.BorderBrush>

  15:                                 <SolidColorBrush x:Name="InnerBorderColor" Color="#FF84C7FF" />

  16:                             </Border.BorderBrush>

  17:

  18:                             <Grid MinHeight="150" MinWidth="150">

  19:                                 <Grid.RowDefinitions>

  20:                                     <RowDefinition Height="10*"/>

  21:                                     <RowDefinition Height="50*"/>

  22:                                     <RowDefinition Height="10*"/>

  23:                                 </Grid.RowDefinitions>

  24:

  25:                                 <TextBlock

  26:                                     x:Name="TitleBlock"

  27:                                     Margin="0"

  28:                                     Text="Prueba"

  29:                                     FontSize="24"

  30:                                     Padding="6,2,0,0"

  31:                                     Foreground="Black"

  32:                                     Grid.Row="0">

  33:

  34:                                 </TextBlock>

  35:

  36:                                 <Rectangle

  37:                                     Grid.Row="0"

  38:                                     Fill="{StaticResource NodeCaptionBackground}"

  39:                                     Canvas.ZIndex="-1"/>

  40:

  41:                                 <TextBlock

  42:                                     x:Name="DescriptionBlock"

  43:                                     Text="Texto descriptivo que podría ocupar perfectamente más de una línea"

  44:                                     TextWrapping="Wrap"

  45:                                     Padding="4,4,4,0"

  46:                                     Foreground="Black"

  47:                                     Grid.Row="1"/>

  48:

  49:                                 <Rectangle Grid.Row="1" Fill="White" Canvas.ZIndex="-1" />

  50:

  51:                                 <HyperlinkButton

  52:                                     x:Name="LinkMoreInfo"

  53:                                     VerticalAlignment="Bottom"

  54:                                     Grid.Row="2" HorizontalAlignment="Right">www.bing.es</HyperlinkButton>

  55:

  56:                                 <Rectangle Grid.Row="2" Fill="White" Canvas.ZIndex="-1" />

  57:                             </Grid>

  58:                         </Border>

  59:                     </Border>

  60:                     <Grid x:Name="GridShadows" Margin="-2,-2,-6,-6">

  61:                         <Rectangle Stroke="Black" Margin="5" RadiusX="7" RadiusY="7" Opacity="0.3"/>

  62:                         <Rectangle Stroke="Black" Margin="4" RadiusX="8" RadiusY="8" Opacity="0.25"/>

  63:                         <Rectangle Stroke="Black" Margin="3" RadiusX="9" RadiusY="9" Opacity="0.2"/>

  64:                         <Rectangle Stroke="Black" Margin="2" RadiusX="10" RadiusY="10" Opacity="0.15"/>

  65:                         <Rectangle Stroke="Black" Margin="1" RadiusX="11" RadiusY="11" Opacity="0.1"/>

  66:                         <Rectangle Stroke="Black" Margin="-37,0,37,0" RadiusX="12" RadiusY="12" Opacity="0.05"/>

  67:                     </Grid>

  68:                 </Grid>

  69:             </ControlTemplate>

  70:         </Setter.Value>

  71:     </Setter>

  72: </Style>

(Os recuerdo que estoy aprendiendo, así que encontraréis probablemente cosas que haya que definir de mejor manera: estoy pensando en estilos fundamentalmente…)

Dándole un poco de interactividad

El objetivo es que el usuario pueda seleccionar nodos para editar sus contenidos, arrastrarlos por la ventana para ajustar su disposición, relacionarlos entre sí…)

Voy a empezar por algo sencillo (y que sólo tiene sentido si se está utilizando un ratón). Destacar el control que se encuentre bajo el cursor cambiando simplemente el color del borde (de momento a un rojo chillón para destacar, más adelante intentaremos mejorar el aspecto visual).

Mi primera tentación fue (tirando de experiencias previas desarrollando con aplicaciones Windows puras y duras) tirar de manejadores de eventos. Sin embargo, unas pocas visitas a la red parecieron indicar que esa era una de las peores ideas que se me podrían haber ocurrido.

   1: protected override void OnPointerEntered(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)

   2: {

   3:     base.OnPointerEntered(e);

   4:

   5:     // ¿Qué tal si cambio aquí el aspecto del borde de mi control?

   6: }

   7:

   8: protected override void OnPointerExited(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)

   9: {

  10:     base.OnPointerExited(e);

  11:

  12:     // Y aquí me limito a volverlo como estaba. ¿No?

  13: }

Así que a seguir investigando.

Tras algunas vueltas más salieron a flote algunos conceptos. Triggers y el VisualStateManager.

Resulta que los Triggers no funcionan en el XAML de Windows 8, así que me quedé con la otra opción.

El VisualStateManager

En esencia, se trata de un componente que me permite definir estados visuales de un control, grupos de estados visuales y (opcionalmente) transiciones para cambiar de un estado a otro. En resumen, ayuda a dejar en la capa de presentación la lógica de presentación asociada al cambio del estado visual de un control. Resumiendo: “evita” que tengas que escribir código en el Code Behind (ya veremos que he tenido que meter una línea, al menos).

Dentro del grid externo que defino en mi control (el que tiene como nombre “CtrlMainLayout”) me he limitado a introducir el siguiente código.

   1: <VisualStateManager.VisualStateGroups>

   2:     <VisualStateGroup x:Name="NodeStates">

   3:         <VisualStateGroup.Transitions>

   4:             <VisualTransition To="PointerOver" GeneratedDuration="0:0:0.5" />

   5:         </VisualStateGroup.Transitions>

   6:

   7:         <VisualState x:Name="Normal" />

   8:         <VisualState x:Name="PointerOver">

   9:             <Storyboard>

  10:                 <ColorAnimation

  11:                     Storyboard.TargetName="MainBorderColor"

  12:                     Storyboard.TargetProperty="Color"

  13:                     To="Red" />

  14:                 <ColorAnimation

  15:                     Storyboard.TargetName="InnerBorderColor"

  16:                     Storyboard.TargetProperty="Color"

  17:                     To="OrangeRed" />

  18:             </Storyboard>

  19:         </VisualState>

  20:     </VisualStateGroup>

  21:     <VisualStateGroup x:Name="FocusStates"/>

  22: </VisualStateManager.VisualStateGroups>

Lo que este código quiere decir es lo siguiente.

  1. He definido un grupo de estados visuales llamado “NodeStates”
  2. Dentro de dicho grupo he definido dos estados visuales
  1. Normal
  2. PointerOver
  • El estado visual “PointerOver” tiene definidas dos animaciones de color (una para cada uno de los bordes, interior y exterior, que defino dentro de mi control). En ambos casos pasamos del color que sea a Rojo (en el caso del borde exterior) y Naranja rojizo (en el caso del interior)
  • Una transición que indica que, cuando se transicione de cualquier estado (no se define el atributo “from”) al estado “PointerOver” la duración de la animación será de cinco segundos.

Bueno, pues ya está. ¿No? La web está llena de ejemplos que aplican a la plantilla de un botón y no hace falta nada más…

Craso error: compilo, paso mi cursor por encima del control y nada de nada.

¿Cómo cambio entre estados visuales?

Otra vez a internet y a darle vueltas. Después de cierta cantidad de esfuerzo (acompañado de bizquera) descubrí por qué funcionaba para un botón y no para mi control y es que, el botón ya tiene definidos una serie de estados visuales (Default, PointerOver, Pressed, Disabled) que gestiona internamente. Pero mi control no.

Así que, aquí viene la línea de código en mi code-behind (vale, en realidad son dos).

   1: protected override void OnPointerEntered(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)

   2: {

   3:     base.OnPointerEntered(e);

   4:

   5:     VisualStateManager.GoToState(this, "PointerOver", true);

   6: }

   7:

   8: protected override void OnPointerExited(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)

   9: {

  10:     base.OnPointerExited(e);

  11:

  12:     VisualStateManager.GoToState(this, "Normal", true);

  13: }

Sencillamente: capturo los eventos de entrada y salida del cursor sobre y desde el control y utilizo el VisualStateManager para indicar el cambio de estado (hacia “PointerOver” cuando el cursor entra y hacia “Normal” cuando el cursor sale).

¡Y ya está, funcionando!

image

(Los controles de los extremos son controles de plantilla y el central un control de usuario)

¿Alguien conoce una mejor forma de manejar situaciones como estas?

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

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

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

¿Cómo funciona?

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

image

image

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

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

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

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

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

   1: public class ViewModelLocator

   2: {

   3:     static ViewModelLocator()

   4:     {

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

   6:  

   7:         if (ViewModelBase.IsInDesignModeStatic)

   8:         {

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

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

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

  12:  

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

  14:         }

  15:         else

  16:         {

  17:             // Proveedores de datos

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

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

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

  21:  

  22:             // Para localización

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

  24:         }

  25:  

  26:         // Para los recursos

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

  28:  

  29:         // Registramos VistasModelo

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

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

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

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

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

  35:     }

  36: }

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

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

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

El mismo esquema se utiliza para proporcionar conjuntos de pruebas.

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

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

El Problema

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

image

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

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

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

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

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

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

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

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

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

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

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

  11:                             mc:Ignorable="d"

  12:                             d:DesignWidth="480"

  13:                             d:DesignHeight="696"

  14:                             FontFamily="{StaticResource PhoneFontFamilyNormal}"

  15:                             FontSize="{StaticResource PhoneFontSizeNormal}"

  16:                             Foreground="{StaticResource PhoneForegroundBrush}"

  17:                             SupportedOrientations="PortraitOrLandscape"

  18:                             Orientation="Portrait"

  19:                             shell:SystemTray.IsVisible="True"

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

  21:  

  22:     <Grid x:Name="LayoutRoot"

  23:           Background="Transparent">

  24:         <Grid.RowDefinitions>

  25:             <RowDefinition Height="Auto" />

  26:             <RowDefinition Height="*" />

  27:         </Grid.RowDefinitions>

  28:  

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

  30:         <Grid x:Name="ContentPanel"

  31:               Grid.Row="1"

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

  33:             

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

  35:                 <ListBox.ItemTemplate>

  36:                     <DataTemplate>

  37:                         <StackPanel>

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

  39:                                 <Grid.ColumnDefinitions>

  40:                                     <ColumnDefinition Width="6" />

  41:                                     <ColumnDefinition Width="*" />

  42:                                 </Grid.ColumnDefinitions>

  43:                                 

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

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

  46:                             </Grid>

  47:  

  48:                             <toolkit:ContextMenuService.ContextMenu>

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

  50:                                     <toolkit:MenuItem 

  51:                                         x:Name="OpcionCheckin" 

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

  53:                                         Path=Recursos.OpcionCheckin}", 

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

  55:                                 </toolkit:ContextMenu>

  56:                             </toolkit:ContextMenuService.ContextMenu>

  57:                         </StackPanel>

  58:                     </DataTemplate>

  59:                 </ListBox.ItemTemplate>

  60:             </ListBox>

  61:         </Grid>

  62:     </Grid>

  63: </phone:PhoneApplicationPage>

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

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

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

   2:     <ListBox.ItemTemplate>

   3:         <DataTemplate>

   4:             <StackPanel>

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

   6:                     <Grid.ColumnDefinitions>

   7:                         <ColumnDefinition Width="6" />

   8:                         <ColumnDefinition Width="auto" />

   9:                         <ColumnDefinition Width="*" />

  10:                     </Grid.ColumnDefinitions>

  11:                     

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

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

  14:                 </Grid>

  15:  

  16:                 <toolkit:ContextMenuService.ContextMenu>

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

  18:                         <toolkit:MenuItem 

  19:                             x:Name="OpcionCheckin" 

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

  21:                             Path=Recursos.OpcionCheckin}"

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

  23:                     </toolkit:ContextMenu>

  24:                 </toolkit:ContextMenuService.ContextMenu>

  25:             </StackPanel>

  26:         </DataTemplate>

  27:     </ListBox.ItemTemplate>

  28: </ListBox>

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

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

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

La Solución

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

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

Efectivamente, el ViewModelLocator.

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

   1: <toolkit:ContextMenuService.ContextMenu>

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

   3:         <toolkit:MenuItem 

   4:             x:Name="OpcionCheckin" 

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

   6:             Path=Recursos.OpcionCheckin}" 

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

   8:         </toolkit:MenuItem>

   9:     </toolkit:ContextMenu>

  10: </toolkit:ContextMenuService.ContextMenu>

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

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

A vueltas con la Barra de Herramientas

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

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

El problema

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

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

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

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

Pues no.

image

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

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

La solución

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

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

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

   1: namespace Jdmveira.WindowsPhone.Mvvm

   2: {

   3:     using Microsoft.Phone.Shell;

   4:     using GalaSoft.MvvmLight.Messaging;

   5:     using System;

   6:     using System.Collections.Generic;

   7:  

   8:     /// <summary>

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

  10:     /// </summary>

  11:     public static class ApplicationBarHelper

  12:     {      

  13:         /// <summary>

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

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

  16:         /// </summary>

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

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

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

  20:         {

  21:             if (appBar.MenuItems != null)

  22:             {

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

  24:                 {

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

  26:                     if (item != null)

  27:                     {

  28:                         traslate(item);

  29:                     }

  30:                 }

  31:             }

  32:  

  33:             if (appBar.Buttons != null)

  34:             {

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

  36:                 {

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

  38:                     if (item != null)

  39:                     {

  40:                         traslate(item);

  41:                     }

  42:                 }

  43:             }

  44:         }

  45:     }

  46: }

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

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

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

   1: namespace Lcdad.SquareMatrix.Pages

   2: {

   3:     using Microsoft.Phone.Shell;

   4:     using Microsoft.Phone.Controls;

   5:     using Jdmveira.WindowsPhone.Mvvm;

   6:     using Lcdad.SquareMatrix.ViewModel;

   7:  

   8:     /// <summary>

   9:     /// Description for MainView.

  10:     /// </summary>

  11:     public partial class MainPage : PhoneApplicationPage

  12:     {

  13:         /// <summary>

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

  15:         /// </summary>

  16:         public MainPage()

  17:         {

  18:             InitializeComponent();

  19:  

  20:             MainViewModel vm = DataContext as MainViewModel;

  21:  

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

  23:             {

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

  25:             }

  26:         }        

  27:     }

  28: }

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

   1: namespace Lcdad.SquareMatrix.ViewModel

   2: {

   3:     using GalaSoft.MvvmLight;

   4:     using System.Windows.Input;

   5:     using GalaSoft.MvvmLight.Command;

   6:     using System;

   7:     using Lcdad.SquareMatrix.Model;

   8:     using Jdmveira.WindowsPhone.Mvvm.Navigation;

   9:     using Microsoft.Phone.Shell;

  10:     using Lcdad.SquareMatrix.Resources;

  11:  

  12:     public class MainViewModel : ViewModelBase

  13:     {

  14:         public void TraducirBarraDeHerramientas(IApplicationBarMenuItem elemento)

  15:         {

  16:             switch (elemento.Text)

  17:             {

  18:                 case "Configuracion":

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

  20:                     break;

  21:                 case "Checkin":

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

  23:                     break;

  24:             }

  25:         }

  26:     }

  27: }

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

appbar-esappbar-en

Otra alternativa

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