[Xamarin.Forms] Creando StateTriggers personalizados

StateTriggers

Recientemente hablábamos en el blog acerca de los StateTriggers añadidos en Xamarin.Forms. Los StateTriggers representan un conjunto de reglas que aplican VisualStates basados ciertas condiciones. Ahora, cada VisualState cuenta con una colección de StateTriggerBase que indican cuando el VisualState se debe aplicar. Si alguno de los triggers esta activo, el VisualState será aplicado.

Xamarin.Forms facilita ciertos StateTriggers para cubrir casos habituales como la orientación del dispositivos, cambios dinámicos en el tamaño de la ventana de la App, etc. Sin embargo, ¿no sería genial poder extender las posibilidades y crear StateTriggers personalizados?.

Creando StateTriggers personalizados

Podemos crear nuestros propios StateTriggers!. A continuación, vamos a aprender como crearlos. Para entender el concepto bien vamos a crear un trigger que nos permite actualizar la UI basándose en el estado de la conexión a internet.

Lo primero es comenzar creando una clase que here de StateTriggerBase:

public class NetworkConnectionStateTrigger : StateTriggerBase
{

}

Para aplicar un VisualState necesitamos establecer la propiedad IsActive a True. Podemos usar el método SetActive disponible en StateTriggerBase para ello. Necesitamos “algo” para poder gestionar que estados aplicar cuando hay conexión y cuando no, para ellos vamos a crear una BindableProperty:

public bool IsConnected
{
     get => (bool)GetValue(IsConnectedProperty);
     set => SetValue(IsConnectedProperty, value);
}

public static readonly BindableProperty IsConnectedProperty =
     BindableProperty.Create(nameof(IsConnected), typeof(bool), typeof(NetworkConnectionStateTrigger), false,
          propertyChanged: OnIsConnectedChanged);

Al cambiar la propiedad vamos a actualizar el valor de IsActive.

void UpdateState()
{
    var current = Connectivity.NetworkAccess;

    if (IsConnected)
         SetActive(current == NetworkAccess.Internet);
    else 
         SetActive(current != NetworkAccess.Internet);
}

Utilizamos las APIs disponibles para conocer el estado de la red disponibles en Xamarin.Essentials. Hasta este punto, tenemos el trigger casi completo (sencillo, ¿verdad?) pero, nos falta un detalle. Estará genial actualizar el valor de IsActive de forma automática al cambiar el estado de la red. Para ello, vamos a utilizar el evento ConnectivityChanged:

Connectivity.ConnectivityChanged += OnConnectivityChanged;

Todo preparado, vamos a ver como utilizar el StateTrigger creado.

Usar el StateTrigger creado

Lo primero que debemos hacer es declarar el namespace en XAML donde hemos creado el StateTrigger:

xmlns:statetriggers="clr-namespace:XamarinFormsStateTriggers"

Para usarlo:

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState 
                    x:Name="NoConnected">
                    <VisualState.StateTriggers> 
                        <statetriggers:NetworkConnectionStateTrigger IsConnected="False" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Red" />
                         <Setter TargetName="InfoLabel" Property="Label.Text" 
                              Value="No Internet connection available" />
                    </VisualState.Setters>
               </VisualState>
               <VisualState
                    x:Name="Connected">
                    <VisualState.StateTriggers>
                         <statetriggers:NetworkConnectionStateTrigger IsConnected="True" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Green" />
                    </VisualState.Setters>
               </VisualState>
          </VisualStateGroup>
     </VisualStateManager.VisualStateGroups>
     <Label
          x:Name="InfoLabel"
          Text="Connected"
          HorizontalOptions="Center"
          VerticalOptions="Center" />
</Grid>

Hemos creado dos VisualState, uno que aplicaremos cuando hay conexión, y otro cuando no. Usamos NetworkConnectionStateTrigger con la propiedad creada, IsConnected para automáticamente aplicar VisualStates en base a la conexión.

El resultado:

NetworkConnectionStateTrigger

Puedes encontrar el código en GitHub:

Ver GitHub

Personalmente, pienso que la posibilidad de crear StateTriggers personalizados cubre un hueco interesante facilitando en gran medida opciones anteriormente posibles. Además, una vez creado el StateTrigger es reutilizable!. Y a ti, ¿qué te parecen?. Recuerda, puedes dejar cualquier feedback en los comentarios de la entrada.

Más información

[Tips and Tricks] Windows 10: Custom State Trigger para gestionar cambios en el estado de la batería

Battery - 03Introducción

Entre la lista de factores importantes que marcan diferencia en practicamente cualquier dispositivo móvil encontramos la batería. Hemos ido viendo una mejora paulatina pero gradual en la calidad y duración de las mismas. Sin embargo, las exigencias requeridas también han ido incrementando. Mayor cantidad de aplicaciones usadas por los usuarios, más sensores, etc.

A pesar de las posibilidades ofrecidas por el sistema para poder gestionar el uso de la batería, reducir opciones activas cuando empieza a tener un nivel escaso, en nuestras propias aplicaciones y como desarrolladores, contamos con APIs para poder trabajar con la batería; detectar capacidad, porcentaje y otras opciones.

En este artículo, vamos a crear varios Adaptive Triggers personalizados con el objetivo de obtener información del estado y porcentaje de la batería.

¿AdaptiveTriggers?

Con la llegada del SDK de Windows 10 tenemos la posibilidad de crear Apps Universales con un único binario que funcione en múltiples plataformas. Es un paso importante pero que conlleva realizar una acción que sera comun, diferenciar entre las diferentes plataformas donde correrá dicho binario para poder adaptar la interfaz de usuario. Con ese objetivo llegan los Adaptive Triggers. Recibimos potentes novedades en los Visual States:

  • Podemos modificar propiedades sin necesidad de animaciones. Anteriormente, la síntaxis era mucho más verbosa y necesitábamos utilizar StoryBoards para cambiar cualquier propiedad por simple que fuese.
  • Contamos con los StateTrigger. Podemos lanzar Triggers cuando se aplica un estado visual sin necesidad de código adyacente en el code-behind. El concepto es muy similar a los Triggers que tenemos disponible en WPF y Silverlight y el objetivo es el mismo, realizar un cambio cuando una condición cambia.

Actualmente contamos con un tipo de StateTrigger por defecto, el Adaptive Trigger que cuenta con los siguientes tipos:

  • MinWindowWidth
  • MinWindowHeight

Ambos nos permiten detectar cambios dinámicos en el tamaño de la pantalla de modo que se adapte el tamaño de la pantalla entre pantallas pequeñas y grandes. El funcionamiento es similar a las media queries en CSS por ejemplo. Se crearán diferentes estados estableciendo unos altos y anchos mínimos, de modo que, cuando la pantalla es más grande que el tamaño asignado se activará el estado visual.

Custom State Triggers relacionados con estado de batería

Los Triggers disponibles son lo suficientemente potentes como para permitirnos un trabajo correcto adaptando la interfaz de usuario pero… ¿podemos llegar más lejos?, ¿y si no es suficiente?

Podemos crear state triggers propios creando una clase que herede de la clase base llamada StateTriggerBase disponible dentro del namespace Windows.UI.Xaml.

En esta ocasión vamos a crear dos state triggers:

  • BaterryStatusTrigger: Nos permitirá detectar el estado de la batería, es decir, si esta cargándose, descargándose, etc.
  • BatteryPercentageTrigger: Nos indicará si el nivel de la batería es alto, medio o bajo para poder actuar en consecuencia.

BatteryStatusTrigger

Comenzamos creando el primer StateTrigger. Creamos una clase que hereda de StateTriggerBase:

public class BatteryStatusTrigger : StateTriggerBase
{

}

Para facilitar la verificación del estado, evitando errores con cadenas vamos a crear una enumeración llamada BatteryChargingStatus:

public enum BatteryChargingStatus        
{            
     Charging = 0,
     Discharging = 1,
     Unknown = 2        
}

Y una propiedad del tipo de la enumeración que usaremos para verificar el valor actual:

private static BatteryChargingStatus _charging;

En nuestro StateTrigger crearemos una propiedad de dependencia llamada BatteryStatus que nos permitirá pasar el estado de la batería, de modo que podamos lanzar la condición idónea en cada caso:

public BatteryChargingStatus BatteryStatus
{            
     get { return (BatteryChargingStatus)GetValue(BatteryStatusProperty); }            
     set { SetValue(BatteryStatusProperty, value); }
}
        
public static readonly DependencyProperty BatteryStatusProperty =
       DependencyProperty.Register("BatteryStatus", typeof(BatteryChargingStatus), typeof(BatteryStatusTrigger),
       new PropertyMetadata(false, OnBatteryStatusPropertyChanged));
        
private static void OnBatteryStatusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)       
{           
     var obj = (BatteryStatusTrigger)d;
     var val = (BatteryChargingStatus)e.NewValue;

     obj.SetActive(val == _charging);       
}

Para acceder a información relacionada con la batería utilizaremos el namespace Windows.Devices.Power donde tendremos acceso a la clase AggregateBattery. Esta clase agrupa todas las baterías que pueda tener el dispositivo (podrían ser más de una) otorgando acceso a información y reportes de forma agrupada.

Utilizamos AggregateBattery para obtener información de la batería utilizando el método GetReport que nos devolverá una instancia de tipo BatteryReport.

Accederemos a la propiedad Status (enumeración BatteryStatus) para verificar el estado de la batería:

public BatteryStatusTrigger()       
{
     UpdateStatus();

     Windows.Devices.Power.Battery.AggregateBattery.ReportUpdated += (s, a) =>
     {
          UpdateStatus();
     };        
}

Dependiendo del valor de Status:

  • NotPresent: No se encuentra batería.
  • Discharging: Batería descargándose.
  • Idle: Inactiva.
  • Charging: Batería cargándose.

Estableceremos el valor adecuado de nuestra propia enumeración, BatteryChargingStatus.

private void UpdateStatus()
{
     var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();

     switch (batteryReport.Status)
     {
          case Windows.System.Power.BatteryStatus.Charging:
               _charging = BatteryChargingStatus.Charging;
               break;
          case Windows.System.Power.BatteryStatus.Discharging:
               _charging = BatteryChargingStatus.Discharging;
               break;
          default:
               _charging = BatteryChargingStatus.Unknown;
               break;            
     }        
}

De esta forma, podemos facilmente notificar al usuario o realizar ajustes en la interfaz dependiendo del estado de la batería.

BatteryPercentageTrigger

Continuamos con el segundo de los StateTrigger. En esta ocasión nuestra intención es verificar el porcentaje de carga para poder realizar ajustes en la interfaz con facilidad dependiendo de la capacidad de batería disponible.

De nuevo, creamos una clase que hereda de StateTriggerBase:

public class BatteryPercentageTrigger : StateTriggerBase    
{

}

Creamos enumeración personalizada para saber el estado de la batería:

public enum BaterryPercentageState        
{            
     VeryHight = 0,
     Hight = 1,
     Medium = 2,
     Low = 3,
     Verylow = 4,
     Unknown = 5        
}

Desde valores muy elevados a muy bajos en función del nivel de batería.

private static BaterryPercentageState _batteryPercentageState;

En nuestro StateTrigger crearemos una propiedad de dependencia llamada BatteryPercentage que nos permitirá pasar el estado de carga de la batería, de modo que podamos lanzar la condición idónea en cada caso:

public BaterryPercentageState BaterryPercentage        
{            
     get { return (BaterryPercentageState)GetValue(BaterryPercentageProperty); }            
     set { SetValue(BaterryPercentageProperty, value); }        
}
        
public static readonly DependencyProperty BaterryPercentageProperty =
       DependencyProperty.Register("BaterryPercentage", typeof(BaterryPercentageState), typeof(BatteryPercentageTrigger),
       new PropertyMetadata(BaterryPercentageState.Unknown, OnBaterryPercentagePropertyChanged));
        
private static void OnBaterryPercentagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)        
{            
     var obj = (BatteryPercentageTrigger)d;
     var val = (BaterryPercentageState)e.NewValue;

     obj.SetActive(val == _batteryPercentageState);        
}

Al igual que en StateTrigger anterior utilizaremos los métodos de obtención de reportes y evento de cambio en reporte para verificar de manera exacta el porcentaje de la batería:

public BatteryPercentageTrigger()        
{            
     UpdatePercentage();

     Windows.Devices.Power.Battery.AggregateBattery.ReportUpdated += (s, a) =>
     {
          UpdatePercentage();
     };        
}

Utilizaremos las propiedades FullChargeCapacityInMilliwattHours, que nos indica la capacidad de la batería junto la propiedad RemainingCapacityInMilliwattHours, que nos indica la capacidad disponible ambas en mW-h para calcular el porcentaje de la batería:

private void UpdatePercentage()        
{
     var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();

     if (batteryReport.FullChargeCapacityInMilliwattHours != null && batteryReport.RemainingCapacityInMilliwattHours != null)
     {
          var percentage = (batteryReport.RemainingCapacityInMilliwattHours.Value /
              (double)batteryReport.FullChargeCapacityInMilliwattHours.Value) * 100;

          if (percentage <= 100 && percentage > 80)
          {
              _batteryPercentageState = BaterryPercentageState.VeryHight;
          }
          else if (percentage <= 80 && percentage > 60)
          {
              _batteryPercentageState = BaterryPercentageState.Hight;
          }
          else if (percentage <= 60 && percentage > 40)
          {
              _batteryPercentageState = BaterryPercentageState.Medium;
          }
          else if (percentage <= 40 && percentage > 20)
          {
               _batteryPercentageState = BaterryPercentageState.Low;
          }
          else if (percentage <= 20 && percentage > 1)
          {
              _batteryPercentageState = BaterryPercentageState.Verylow;
          }
          else
          {
             _batteryPercentageState = BaterryPercentageState.Unknown;
          }
     }        
}   

Segun porcentaje de la batería estableceremos el valor adecuado de nuestra enumeración de tipo BatteryPercentageState.

Utilizando los StateTriggers creados

Con ambos StateTriggers a nuestro servicio ha llegado el momento de utilizarlos. Contaremos con una interfaz sumamente simple:

<TextBlock x:Name="BatteryText"               
           Text="Battery Status"/>

Mostraremos un texto diferente según el porcentaje de la batería utilizando BatteryPercentageTrigger y el texto en color verde si el dispositivo esta cargando la batería o rojo si al contrario, la batería se esta descargando utilizando BatteryStatusTrigger:

<VisualStateManager.VisualStateGroups>
     <VisualStateGroup>
          <VisualState x:Name="Charging">
              <VisualState.StateTriggers>
                  <customStateTriggers:BatteryStatusTrigger BatteryStatus="Charging" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                  <Setter Target="BatteryText.Foreground" Value="Green" />
              </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="Discharging">
              <VisualState.StateTriggers>
                   <customStateTriggers:BatteryStatusTrigger BatteryStatus="Discharging" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Foreground" Value="Red" />
               </VisualState.Setters>
           </VisualState>
       </VisualStateGroup>
       <VisualStateGroup>
           <VisualState x:Name="PercentageVeryHight">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="VeryHight" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                  <Setter Target="BatteryText.Text" Value="Very hight" />
               </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="PercentageHight">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Hight" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Hight" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageMedium">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Medium" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Medium" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageLow">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Low" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Low" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageVeryLow">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Verylow" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Very low" />
               </VisualState.Setters>
           </VisualState>
     </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Si ejecutamos la App:

Custom State Triggers de batería

Custom State Triggers de batería

Nuestra aplicación se ejecuta en un dispositivo móvil con la batería descargándose y a nivel bajo, es decir, por debajo de 40% y por encima de 20%.

Podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más Información

 

[Windows 10] Adaptive Triggers

Code - 02Introducción

En la plataforma Windows contámos con una gran variedad de dispositivos, PCs, tabletas, Phablets, teléfonos. Contamos con una gran cantidad diversa de tamaños de pantalla, resoluciones y otros factores que como desarrolladores debemos tener en cuenta. El usuario es exigente, debe serlo. Es vital poder disfrutar de la mejor experiencia posible en cada uno de los dispositivos, y ese, es uno de nuestros retos.

Adaptando la interfaz

Adaptar la interfaz de usuario a distintos tamaños, orientaciones y otros cambios no es algo nuevo. Con la llegada de las Apps Modern UI en Windows 8.0 debíamos gestionar la vista Narrow. Con las Apps Universales en Windows 8.1 desarrollábamos Apps con Windows Phone y Windows en mente. Ante este tipo de situaciones, hasta ahora hemos utilizado diferentes técnicas. Las más habituales son:

  • Vistas XAML separadas por plataforma.
  • Diseño flexible. Utilizar posiciones y tamaños relativos para permitir escalar facilmente.
  • Utilizar diferentes estados visuales (Visual States) para gestionar vistas en diferentes dispositivos, pantallas e incluso orientaciones.

Adaptive Triggers

Con la llegada del SDK de Windows 10 Preview tenemos la posibilidad de crear Apps Universales con un único binario que funcione en múltiples plataformas. Es un paso importante pero que conlleva realizar una acción que sera comun, diferenciar entre las diferentes plataformas donde correrá dicho binario para poder adaptar la interfaz de usuario. Con ese objetivo llegan los Adaptive Triggers. Recibimos potentes novedades en los Visual States:

  • Podemos modificar propiedades sin necesidad de animaciones. Anteriormente, la síntaxis era mucho más verbosa y necesitábamos utilizar StoryBoards para cambiar cualquier propiedad por simple que fuese.
  • Contamos con los StateTrigger. Podemos lanzar Triggers cuando se aplica un estado visual sin necesidad de código adyacente en el code-behind. El concepto es muy similar a los Triggers que tenemos disponible en WPF y Silverlight y el objetivo es el mismo, realizar un cambio cuando una condición cambia.

Actualmente contamos con un tipo de StateTrigger por defecto, el Adaptive Trigger que cuenta con los siguientes tipos:

  • MinWindowWidth
  • MinWindowHeight

Ambos nos permiten detectar cambios dinámicos en el tamaño de la pantalla de modo que se adapte el tamaño de la pantalla entre pantallas pequeñas y grandes. El funcionamiento es similar a las media queries en CSS por ejemplo. Se crearán diferentes estados estableciendo unos altos y anchos mínimos, de modo que, cuando la pantalla es más grande que el tamaño asignado se activará el estado visual.

NOTA: Los valores se especifican en pixeles.

Para probar las posibilidades de los Adaptive Triggers crearemos un nuevo proyecto UAP:

Nueva App UAP

Nueva App UAP

Nuestro objetivo en el ejemplo serán:

  • Aprender a utilizar AdaptiveTrigger de la forma más sencilla posible. Utilizaremos un texto que modificaremos segun ciertas condiciones del ancho de la ventana.
  • Crearemos un StateTrigger personalizado.

Añadimos en nuestra infertaz un control para mostrar texto:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
     <TextBlock x:Name="Info"
                HorizontalAlignment="Center"
        VerticalAlignment="Center" />
</Grid>

Sera el control que modificaremos mediante el VisualState:

<!-- Using AdaptiveTrigger -->
<VisualState x:Name="Small">
     <VisualState.StateTriggers>
          <AdaptiveTrigger MinWindowWidth="0" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Pantalla pequeña" />
          <Setter Target="Info.FontSize" Value="32" />
          <Setter Target="Info.Foreground" Value="Red" />
     </VisualState.Setters>
</VisualState>
<VisualState x:Name="Big">
     <VisualState.StateTriggers>
          <AdaptiveTrigger MinWindowWidth="600" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Pantalla grande" />
          <Setter Target="Info.FontSize" Value="48" />
          <Setter Target="Info.Foreground" Value="Blue" />
     </VisualState.Setters>
</VisualState>

Analicemos el trozo de XAML de la parte superior. Hemos definido dos estados visuales donde utilizamos la propiedad MinWindowWidth del StateTrigger AdaptiveTrigger para aplicar una serie de cambios en las propiedades del control TextBlock Info. Podemos encadenar tantos Setters como sean necesarios y no es necesario la gestión por medio de StoryBoards lo que nos permite definirlo todo de una manera menos verbosa.

Si lanzamos nuestra App y el tamaño de la ventana es superior a 600 px veremos algo como lo siguiente:

Estado Visual "Big"

Estado Visual “Big”

Mientras que si reducimos el tamaño:

Estado Visual "Small"

Estado Visual “Small”

Muy potente, ¿verdad?. De esta forma podemos hacer distintos cambios en la organización de elementos, estilos y otros detalles para ajustar la interfaz de usuario segun el tamaño de la ventana.

Creando Adaptive Triggers personalizados

Hasta ahora hemos aprendido que:

  • Tenemos potentes novedades en los VisualStates.
  • Como gestionar los elementos de la interfaz de usuario para adaptar la interfaz de usuario a cada dispotivo.
  • El uso de Adaptative Triggers para controlar el ancho y/o alto de la pantalla y adaptar la UI.

Los Triggers disponibles son lo suficientemente potentes como para permitirnos un trabajo correcto adaptando la interfaz de usuario pero… ¿podemos llegar más lejos?, ¿y si no es suficiente?

Si analizamos la clase AdaptiveTrigger utilizada hasta ahora:

AdaptiveTrigger

AdaptiveTrigger

Vemos que hereda de una clase base llamada StateTriggerBase disponible dentro del namespace Windows.UI.Xaml.

Podemos crear nuestros propios StateTriggers.

Crearíamos nuevas clases heredando de la clase mencionada. Vamos a crear un nuevo StateTrigger que nos indique en que plataforma estamos (Windows o Windows Phone).

Creamos una clase que herede de StateTriggerBase:

public class PlatformStateTrigger : StateTriggerBase
{

}

Al igual que en AdaptiveTrigger contamos con dos propiedades, MinWindowWidth y MinWindowHeight que nos permiten lanzar el Trigger dependiendo de condiciones utilizando las mismas.

En nuestro StateTrigger crearemos una propiedad de dependencia llamada Platform que nos permitirá pasar el nombre de la plataforma, de modo que si estamos en dicha plataforma lanzar la condición:

public string Platform
{
     get { return (string)GetValue(PlatformProperty); }
     set { SetValue(PlatformProperty, value); }
}

public static readonly DependencyProperty PlatformProperty =
     DependencyProperty.Register("Platform", typeof(string), typeof(PlatformStateTrigger),
     new PropertyMetadata(true, OnPlatformPropertyChanged));

private static void OnPlatformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{

}

La parte más importante del Trigger es determinar con exactitud la plataforma en la que se ejecuta la App. Entre las opciones disponibles, la más sencilla es utilizar ResourceContext:

var qualifiers = ResourceContext.GetForCurrentView().QualifierValues;
_deviceFamily = qualifiers.First(q => q.Key.Equals("DeviceFamily")).Value;

Accedemos al listado de QualifierValues, un IObservableMap con claves y valor que contiene información como el idioma de la App, el contraste, el tema utilizado o la familia del dispositivo. Utilizaremos esta última para determinar si estamos en Desktop o Phone.

De modo que ante el cambio de nuestra propiedad:

private static void OnPlatformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
     var obj = (PlatformStateTrigger)d;
     var platform = (string)e.NewValue;

     if(_deviceFamily.Equals("Desktop"))
    obj.SetTriggerValue(platform == "Windows");
     else
    obj.SetTriggerValue(platform == "WindowsPhone");
}

Nuestro objetivo final es llamar al método SetTriggerValue(bool) que es el encargado de determinar si la condicion del estado se cumple o no. Dependiendo del dispositivo lanzamos la condición correcta.

Por último, queda utilizar nuestro StateTrigger personalizado. En nuestra interfaz declaramos antes que nada el namespace donde lo hemos definido:

xmlns:customStateTriggers="using:AdaptiveTriggers.CustomStateTriggers"

Podemos crear VisualStates utilizando PlatformStateTrigger:

<VisualState x:Name="WindowsPhone">
     <VisualState.StateTriggers>
          <customStateTriggers:PlatformStateTrigger Platform="WindowsPhone" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Windows Phone" />
          <Setter Target="Info.FontSize" Value="32" />
          <Setter Target="Info.Foreground" Value="Red" />
     </VisualState.Setters>
</VisualState>
<VisualState x:Name="Windows">
     <VisualState.StateTriggers>
          <customStateTriggers:PlatformStateTrigger Platform="Windows" />
     </VisualState.StateTriggers>
     <VisualState.Setters>
          <Setter Target="Info.Text" Value="Windows" />
          <Setter Target="Info.FontSize" Value="48" />
          <Setter Target="Info.Foreground" Value="Blue" />
     </VisualState.Setters>
</VisualState>

En el caso de estar en un dispositivo Windows Phone se lanzaría el segundo estado visual, en el de Windows el primero, adaptando la interfaz de usuario (en nuestro ejemplo un simple TextBlock).

De igual forma podemos crear Adaptive Triggers personalizados para gestionar aspectos como el tipo de dispositivo, el tipo de orientación, haciendo verificaciones de condiciones como datos, internet, etc.

Podéis descargar el ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información