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

Anuncios