[Windows 10] Experiencias multipantalla utilizando ProjectionManager

Introducción

Entre el conjunto de posibilidades nuevas
disponibles con Windows 10, sin duda alguna, hay una brilla fuertemente
sobre las demas, Continuum. Esta característica permite
conectar un teléfono a un monitor externo permitiendo interactuar con
la App en modo escritorio mientras podemos continuar utilizando el
teléfono.

Continuum
Continuum

Es
vital utilizar los nuevos AdaptiveTriggers, RelativePanel además de
controlar el modo de interacción y otros detalles para adaptar la
interfaz y usabilidad a cada posible situación. De esta forma
conseguimos aplicaciones adaptativas pudiendo ofrecer la experiencia
idónea en cada familia de dispositivo soportado.

En Continuum
podemos tener una única App en la pantalla secundaria de forma
simultánea. Sin embargo, podemos crear experiencias con múltiples
pantallas. ¿Os imagináis ver listado de restaurantes cercanos en el
teléfono mientras que en pantalla grande vemos mapa mostrando cercanía a
nuestra posisión y críticas?, ¿ tener detalles de una película y ver el
trailer de la misma en pantalla completa?. Escenarios donde sacar
partido de la proyección de información a una pantalla
secundaria hay muchos tanto en aplicaciones como en juegos. En este
artículo vamos a sacarle todo el partido a la clase ProjectionManager y el trabajo multipantalla.

ProjectionManager
ProjectionManager

Proyección de vistas

Crearemos un nuevo proyecto UAP:

Nueva App UAP
Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

El objetivo del artículo será proyectar una pantalla secundaria para aprender a:

  • Proyectar pantalla secundaria.
  • Detener la proyección.
  • Hacer un intercambio de la pantalla donde se proyecta.

Detectar pantalla secundaria

ProjectionManager
nos permite proyectar una ventana de nuestra App en una pantalla
secundaria. A nivel de desarrollo, el proceso es similar a trabajar con múltiples ventanas en la misma App. Para proyectar en otra pantalla lo primero que debemos verificar es si disponemos de esa pantalla.

En nuestro ejemplo, mostraremos en la interfaz si contamos o no con la pantalla donde proyectar:

<TextBlock 
     Text="{Binding IsProjectionDisplayAvailable}"/>

Usaremos una sencilla propiedad bool en la viewmodel:

private bool _isProjectionDisplayAvailable;

public bool IsProjectionDisplayAvailable
{
     get { return _isProjectionDisplayAvailable; }
     set
     {
          _isProjectionDisplayAvailable = value;
          RaisePropertyChanged();
     }
}

En la clase ProjectionManager contamos con el evento ProjectionDisplayAvailableChanged que se lanza cada vez que la pantalla secundaria sobre la que proyecta pasa a estar disponible o no disponible:

ProjectionManager.ProjectionDisplayAvailableChanged += ProjectionManager_ProjectionDisplayAvailableChanged;

También podemos realizar la verificación de si tenemos disponible la pantalla secundaris utilizando la propiedad ProjectionDisplayAvailable:

IsProjectionDisplayAvailable = ProjectionManager.ProjectionDisplayAvailable;

NOTA: Si no contamos con pantalla secundaria, la
vista proyectada se mostrará en la misma pantalla donde se encuentra la
vista principal.

Proyectar

Conocemos como verificar si contamos con pantalla secundaria sobre la que proyectar, veamos como realizar la proyección.

En nuestra interfaz tendremos un botón que nos permitirá proyectar una vista específica:

<Button
     Content="Project"
     Command="{Binding ProjectCommand}"/>

Nuestra interfaz principal:

Vista principal

Vista principal

El comando a ejecutar:

private ICommand _projectCommand;
 
public ICommand ProjectCommand
{
     get { return _projectCommand = _projectCommand ?? new DelegateCommandAsync(ProjectCommandExecute); }
}
 
public async Task ProjectCommandExecute()
{
     App.MainViewId = await _projectionService.ProjectAsync(typeof(ProjectionView));
}

Hemos creado un servicio ProjectionService en el que tenemos agrupada toda la lógica de proyección. Para proyectar utilizamos el siguiente método:

public async Task<int> ProjectAsync(Type viewType, DeviceInformation device = null)
{
     int mainViewId = ApplicationView.GetForCurrentView().Id;
     int? secondViewId = null;
 
     var view = CoreApplication.CreateNewView();
     await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
     {
          secondViewId = ApplicationView.GetForCurrentView().Id;
          var rootFrame = new Frame();
          rootFrame.Navigate(viewType, null);
          Window.Current.Content = rootFrame;
          Window.Current.Activate();
     });
 
     if (secondViewId.HasValue)
     {
          if(device == null)
              await ProjectionManager.StartProjectingAsync(secondViewId.Value, mainViewId);
          else
              await ProjectionManager.StartProjectingAsync(secondViewId.Value, mainViewId, device);
     }
 
     return mainViewId;
}

Para realizar la proyección utilizamos el método StartProjectingAsync(Int32,Int32) al que le pasamos como parámetros:

  • ProjectionViewId: El  identificador de la ventana que se va a mostrar en la pantalla secundaria.
  • AnchorViewId: El identificador de la ventana original.

Comenzamos creando una nueva vista vacía en blanca. En esta vista
navegamos a la vista que deseamos proyectar y la asignamos como
contenido. Podemos pasar los parámetros necesarios en este punto.

NOTA: Es totalmente necesario realizar la llamada a Window.Current.Activate para que la vista pueda visualizarse.

La vista no aparecerá hasta lanzar el método StartProjectingAsync.
Tras lanzarlo, colocamos una vista existente en una pantalla
secundaria, en caso de detectar una. De lo contrario, la vista se sitúa
en el monitor principal.

Proyectar seleccionando la pantalla

Lo visto hasta este punto es sencillo y efectivo. Sin embargo,
podemos tener situaciones más complejas con múltiples pantallas
sencundarias.

¿Podemos elegir sobre que pantalla proyectar?

Si, podemos. Vamos a ver como realizar este proceso. Creamos en la
interfaz otro botón de modo que al ser pulsado nos muestre todas las
pantallas disponibles. Una vez seleccionada una pantalla específica
proyectaríamos sobre la misma:

<Button
     Content="Select Target and Project"
     Command="{Binding SelectTargetCommand}"/>
<ListView
     ItemsSource="{Binding Devices}"
     SelectedItem="{Binding SelectedDevice, Mode=TwoWay}"
     Height="300"
     Width="300"
     HorizontalAlignment="Left">
     <ListView.ItemTemplate>
          <DataTemplate>
               <TextBlock Text="{Binding Name}" />
          </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

En la viewmodel:

private ICommand _selectTargetCommand;
 
public ICommand SelectTargetCommand
{
     get { return _selectTargetCommand = _selectTargetCommand ?? new DelegateCommandAsync(SelectTargetCommandExecute); }
}
 
public async Task SelectTargetCommandExecute()
{
     try
     {
          Devices = new ObservableCollection<DeviceInformation>(await _projectionService.GetProjectionDevices());
     }
     catch (Exception ex)
     {
          Debug.WriteLine(ex.Message);
     }
}

Utilizamos el siguiente método:

public async Task<IEnumerable<DeviceInformation>> GetProjectionDevices()
{
     // List wired/wireless displays
     String projectorSelectorQuery = ProjectionManager.GetDeviceSelector();
 
     // Use device API to find devices based on the query
     var projectionDevices = await DeviceInformation.FindAllAsync(projectorSelectorQuery);
 
     var devices = new ObservableCollection<DeviceInformation>();
     foreach (var device in projectionDevices)
          devices.Add(device);
 
     return devices;
}

Utilizamos el método GetDeviceSelector disponible en ProjectionManager
que nos devuelve una cadena con la enumeración de dispositivos
disponibles. Utilizamos la cadena para obtener una colección de
dispositivos (DeviceInformation) en los cuales tenemos toda la información necesaria.

La colección obtenida es la que bindeamos a nuestra interfaz. Una vez seleccionado un dispositivo concreto:

private async Task Project(DeviceInformation device)
{
     try
     {
          // Show the view on a second display (if available)
          App.MainViewId = await _projectionService.ProjectAsync(typeof(ProjectionView), device);
 
          Debug.WriteLine("Projection started in {0} successfully!", device.Name);
     }
     catch (Exception ex)
     {
          Debug.WriteLine(ex.Message);
     }
}

Utilizamos un método al que le pasamos el dispositivo y se encarga de
realizar la proyección. En este caso, en nuestro servicio utilizamos el
método StartProjectingAsync(Int32,Int32,DeviceInformation)
donde además de los identificadores de la nueva vista y de la original,
indicamos el dispositivo, es decir, la pantalla específica sobre la que
proyectar.

También podemos de forma muy sencilla permitir elegir el dispositivo sobre el que proyectar utilizando el método RequestStartProjectingAsync(Int32,Int32,Rect,Placement).
Utilizando este método se mostrará un flyout con el listado de
dispositivos, de modo que, una vez seleccionado uno, comenzamos la
proyección. Para indicar la posición del flyout podemos utilizar un
parámetro de tipo Rect.

public async Task<int> RequestProjectAsync(Type viewType, Rect? position = null)
{
     int mainViewId = ApplicationView.GetForCurrentView().Id;
     int? secondViewId = null;
 
     var view = CoreApplication.CreateNewView();
     await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
     {
          secondViewId = ApplicationView.GetForCurrentView().Id;
          var rootFrame = new Frame();
          rootFrame.Navigate(viewType, mainViewId);
          Window.Current.Content = rootFrame;
          Window.Current.Activate();
     });
 
     if (secondViewId.HasValue)
     {
          var defaultPosition = new Rect(0.0, 0.0, 200.0, 200.0);
          await ProjectionManager.RequestStartProjectingAsync(secondViewId.Value, mainViewId, position.HasValue ? position.Value : defaultPosition);
     }
 
     return mainViewId;
}

La vista proyectada

¿Y que ocurre con la vista proyecta?. Nada en especial, puede ser
cualquier vista de la aplicación. Sin embargo, puede llegar a
interesarnos realizar algunas interacciones con la API de proyección
como:

  • Detener la proyección.
  • Modificar el dispositivo donde proyectamos.

La interfaz de usuario contará con dos botones, uno para detener la proyección y otro para modificar el dispositivo utilizado.

<Grid>
     <StackPanel>
          <TextBlock
              Text="Projection View"
              FontWeight="SemiBold"/>
          <Button
              Content="Swap"
              Command="{Binding SwitchViewCommand}"/>
          <Button
              Content="Stop"
              Command="{Binding StopCommand}"/>
     </StackPanel>
</Grid>

El resultado:

Vista proyectada

Vista proyectada

En la viewmodel:

private ICommand _switchViewCommand;
private ICommand _stopCommand;
 
public ICommand SwitchViewCommand
{
     get { return _switchViewCommand = _switchViewCommand ?? new DelegateCommandAsync(SwitchViewCommandExecute); }
}
 
public ICommand StopCommand
{
     get { return _stopCommand = _stopCommand ?? new DelegateCommandAsync(StopCommandExecute); }
}
 
public async Task SwitchViewCommandExecute()
{
     try
     {
          await _projectionService.SwitchProjection(App.MainViewId);
     }
     catch (Exception ex)
     {
          Debug.WriteLine(ex.Message);
     }
}
 
public async Task StopCommandExecute()
{
     try
     {
          await _projectionService.StopProjection(App.MainViewId);
     }
     catch (Exception ex)
     {
          Debug.WriteLine(ex.Message);
     }
}

Detener proyección

Para detener la proyección tenemos a nuestra disposición el método StopProjectingAsync que oculta la vista mostrada en proyector o pantalla secundaria.

public async Task StopProjection(int mainViewId)
{
     await ProjectionManager.StopProjectingAsync(       
                ApplicationView.GetForCurrentView().Id,
                mainViewId);
}

Cambiar pantalla

Podemos cambiar al vuelo la pantalla donde se realiza la proyección utilizando el método SwapDisplaysForViewsAsync de la clase ProjectionManager:

public async Task SwapProjection(int mainViewId)
{
     await ProjectionManager.SwapDisplaysForViewsAsync(
                ApplicationView.GetForCurrentView().Id,
                mainViewId);
}

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información

[Material] Talks4Kids 2015: Consejos Heisenberg para conseguir Apps Windows 10 con 99% de pureza

 

ChildrenEl evento

Todos los eventos de comunidad son especiales ya por el sencillo
hecho de compartir, comunicar y hacer networking entre todos. Sin
embargo, en ocasiones hay eventos que marcan. Lo pueden hacer por muchas
razones, un evento grande de comunidad donde compartir y aprender
mucho, el primer evento de una nueva comunidad, uno donde por temática o
novedad te encuentras especialmente ilusionado. Hay muchos motivos.

El pasado 4 de Diciembre, en Microsoft Ibérica, tenía lugar el Talks4Kids. Evento de comunidad con sesiones técnicas relacionadas con tecnologías Microsoft pero diferente por varios motivos.

Por un lado, el evento era solidario buscando ayudar en la cantidad de lo posible a la fundación Theodora
en su gran labor alegrando la estancia en hospitales tanto a pacientes
como a sus familiares sobretodo en estas fechas señaladas. Nos complace
de lleno saber que gracias a patrocinadores, ponentes y asistentes
conseguimos aproximadamente unas 190 visitas de los doctores sonrisa.

Por otro lado, lanzamos un reto sencillo y directo. Si llegábamos a una cifra mínima de recaudación, todos los ponentes saldríamos disfrazados. Y vaya si lo hicimos…

Momento "Di mi nombre"

Momento “Di mi nombre”

Entre el distentido climax logrado gracias a la visita de un doctor sonrisa en la keynote, los
momentos claves de aparición de cada ponente disfrazado, los
irremediables momentos llenos de risas, la cercanía en las sesiones y el
compañerismo absoluto, logramos uno de los eventos de comunidad más destacables del año.

Gran evento, gran comunidad!

Gran evento, gran comunidad!

El material

En cuanto al material del evento, participé con una sesión sobre desarrollo Windows 10 donde asociando en todo momento con el ya mítico personaje de la serie de ficción “Breaking Bad” vimos:

  • El ciclo de vida en aplicaciones Windows 10.
  • Ejecución de modo extendido.
  • Tareas de fondo, nuevos triggers.
  • Application Trigger.
  • Comunicación entre Apps.
  • App Services.

Tenéis disponible la presentación utilizada a continuación:

En cuanto a las demos técnicas realizadas, las tenéis disponible en GitHub:

Ver GitHub

Quisiera terminar añadiendo algunos agradecimientos a
todos aquellos que han hecho posible el evento y toda la ayuda lograda.
Comienzo agradeciendo a todos los patrocinadores que apoyaron al
evento, en especial en mi caso particular, a Plain Concepts por no dudar ni un segundo en colaborar además de permitirme participar; gracias a todos los ponentes por su compromiso, sesiones y por no dudar en disfrazarse o realizar lo necesario; a Microsoft por dejarnos sala y toda la ayuda necesaria y por supuesto, muchas gracias a todos los asistentes y los que no pudieron pero ayudaron.

One mor thing…

Todas las sesiones del evento fueron grabadasy estarán disponibles en Channel 9.

Más información

[Tips and Tricks] DeviceFamily Adaptive XAML Tool

Introducción

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 utilizamos entre otras opciones
los Adaptive Triggers de los que ya hemos hablado.

Sin embargo, en ocasiones la misma vista en diferentes dispositivos puede que sea totalmente diferente.

En estos casos podemos crear vistas diferentes por familias de
dispositivos. Tras añadir una vista de la forma habitual, creamos una
carpeta siguiente la siguiente nomenclatura:

  • DeviceFamily-[Family]

Donde Family es la familia del dispotivo para el que
deseamos sobrescribir la vista.Dentro de esta carpeta añadimos la vista
XAML deseada en esa plataforma.

NOTA: Añadimos la sobreescritura de la vista. Es
importante un detalle del fichero añadido, no añadimos code behind.
Esta vista utilizará el mismo code behind que la que teníamos
previamente.

DeviceFamily Adaptive XAML Tool

Gestionar carpetas o vistas por familia de dispositivo se vuelve aun más sencillo gracias a la extensión Device Family Adaptive XAML Tool creada por Olivier Matis.

Podemos hacer clic derecho sobre la solución para acceder a un menu
contextual llamado “Add single folder” para añadir carpeta de vistas
específica para una plataforma concreta. Además, haciendo clic sobre una
vista existente tendremos acceso a otro menu contextual “Add device
family XAML” que nos creará la vista XAML específica para la familia de
dispositivos seleccionada.

DeviceFamily Adaptive XAML Tool

DeviceFamily Adaptive XAML Tool

Sencillo pero extremadamente útil, ¿cierto?.

Más información

[Tips and Tricks] Diccionario de recursos por plataforma en Windows 10

Introducción

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 utilizamos entre otras opciones
los Adaptive Triggers de los que ya hemos hablado.

Sin embargo, en ocasiones la misma vista en diferentes dispositivos puede que sea totalmente diferente.

En estos casos podemos crear vistas diferentes por familias de
dispositivos. Tras añadir una vista de la forma habitual, creamos una
carpeta siguiente la siguiente nomenclatura:

  • DeviceFamily-[Family]

Donde Family es la familia del dispotivo para el que
deseamos sobrescribir la vista.Dentro de esta carpeta añadimos la vista
XAML deseada en esa plataforma.

NOTA: Añadimos la sobreescritura de la vista. Es
importante un detalle del fichero añadido, no añadimos code behind.
Esta vista utilizará el mismo code behind que la que teníamos
previamente.

Lo visto hasta ahora es genial para poder crear con facilidad vistas diferentes, pero…¿y a nivel de recursos y estilos?

Utilizando diccionarios de recursos por plataforma de dispositivos

En diccionarios de recursos, archivos XAML, organizamos los recursos y
estilos utilizados por la aplicación. Si deseamos recursos o estilos
diferentes por plataformas podemos crear diferentes elementos con
etiquetas distintas de modo que utilicemos en cada plataforma uno
diferente (utilizando Adaptive Triggers por ejemplo). El
problema de la situación anterior es que en la mayoría de casos, los
cambios son mínimos y pasamos a agrandar el conjunto de recursos
dificultando el mantenimiento de la aplicación.

¿Podemos mejorar esta situación?

Si. Podemos crear diccionarios de recursos compartidos con todo el
conjunto de recursos comunes junto a diccionarios de recursos
específicos por plataforma con estilos concretos a utilizar en las
mismas.

¿Cómo sería?

Vamos a crear un ejemplo simple donde tendremos un rectángulo
enlazado a un color. Utilizaremos un color definido en un diccionario de
recursos genérico usado en todas las plataformas. Sin embargo, en
teléfonos modificaremos el color añadiendo un diccionario de recursos
específico para teléfonos donde sobreescribiremos el color.

Comenzamos. Añadimos un diccionario de recursos llamado Colors:

<Color x:Key="AccentColor">DeepSkyBlue</Color>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />

Definimos el color a utilizar y registramos el diccionario en los recursos de la aplicación:

<Application.Resources>
     <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
               <ResourceDictionary Source="Resources/Colors.xaml" />
          </ResourceDictionary.MergedDictionaries>
     </ResourceDictionary>
</Application.Resources>

En nuestra interfaz contaremos con un rectángulo relleno con el color definido.

<Rectangle
     Height="200"
     Width="250"
     Fill="{StaticResource AccentBrush}"/>

Hasta aquí, si ejecutamos la aplicación en cualquiera de las plataformas soportadas, el rectángulo se vera azul cielo.

A continuación, vamos a sobreescribir el color del rectángulo en
teléfonos. Creamos un nuevo diccionario de recursos de igual nombre pero
añadiendo al nombre .DeviceFamily-[Family] donde Family es la familia del dispotivo para el que deseamos sobrescribir el recurso. En nuestro ejemplo se llamará Colors.DeviceFamily-Mobile.xaml:

<Color x:Key="AccentColor">Red</Color>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />

Definimos un nuevo color. Si ejecutamos la aplicación en Windows:

Color tomado del recurso por defecto

Color tomado del recurso por defecto

Mientras que en un teléfono:

Color tomado del recurso específico para teléfonos

Color tomado del recurso específico para teléfonos

Sencillo pero versátil, ¿cierto?.

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

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información

 

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

Introducció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

[Tips and Tricks] Windows 10: Gestión de la batería

Introducció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 repasar todas las posibilidades
relacionadas con la batería disponibles en Windows 10 dentro del
namespace Windows.Devices.Power.

Trabajando con la batería

Crearemos un nuevo proyecto UAP:

Nueva App UAP

Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

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.

var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();

Entre el conjunto de propiedades disponibles en el reporte, contamos con una propiedad llamada Status (enumeración BatteryStatus):

public string Status
{
     get { return _status; }
     set
     {
          _status = value;
          RaisePropertyChanged();
     }
}
 
Status = batteryReport.Status.ToString();

La enumeración de tipo BatteryStatus cuenta con las siguientes opciones:

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

Continuamos con otra propiedad, ChargeRateInMilliwatts:

public int ChargeRate
{
     get { return _chargeRate; }
     set
     {
          _chargeRate = value;
          RaisePropertyChanged();
     }
}
 
if (batteryReport.ChargeRateInMilliwatts != null)
     ChargeRate = batteryReport.ChargeRateInMilliwatts.Value;

Nos indica en mW el ritmo de carga o descarga en caso de valores negativos de la batería.

NOTA: El valor puede ser nulo en caso de no encontrar batería.

De igual forma que podemos saber el ritmo de carga, podemos conocer la capacidad de la batería con la propiedad FullChargeCapacityInMilliwattHours:

Y la capacidad disponible con RemainingCapacityInMilliwattHours:

public int RemainingCapacity
{
     get { return _remainingCapacity; }
     set
     {
           _remainingCapacity = value;
           RaisePropertyChanged();
     }
}
 
RemainingCapacity = batteryReport.RemainingCapacityInMilliwattHours.Value;

Ambos dados en mW-h. Con los valores anteriores, obtener el porcentaje actual de la batería es una operación sumamente simple:

public double Percentage
{
     get { return _percentage; }
     set
     {
           _percentage = value;
           RaisePropertyChanged();
     }
}
 
var percentage = (batteryReport.RemainingCapacityInMilliwattHours.Value /
                 (double)batteryReport.FullChargeCapacityInMilliwattHours.Value) * 100;
     
Percentage = percentage; 

Si ejecutamos la aplicación:

Información de la batería

Información de la batería

NOTA: Para obtener información válida en el
BatteryReport se debe ejecutar la App en un dispositivo físico. En caso
de ejecutar en emulador, se obtendrán nulos.

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

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información

[Windows 10] Novedades y consejos sobre rendimiento

Branch-EngineeringIntroducción

Además de cuidar detalles como la funcionalidad o la apariencia
visual de nuestra aplicación, nuestra aplicación debe funcionar
correctamente bajo todas las condiciones en todos los dispositivos para
la que sea lanzada.

Factores como el consumo de memoria, CPU o la gestión de tiempos, a
veces cuestan y no se tienen en cuenta hasta que salta el problema, es
decir, tarde.

¿Es importante el rendimiento?

Las críticas de la tienda así como la posibilidad de comunicación
directa con los usuarios es una vía inmejorable para realizar una mejora
continua de la misma. Las críticas habituales suelen venir en un alto
porcentaje de errores (bugs) que no permiten realizar acciones contempladas en la aplicación y también muchas de ellas, por rendimiento.

 

Motivos de críticas negativas

Motivos de críticas negativas

Que la aplicación se “congele”, respuestas lentas, consumos
muy elevados de batería, que provoquen un sobrecalentamiento del
dispositivo son algunas causas habituales de opiniones como las
siguientes:

Ejemplos de críticas negativas

Ejemplos de críticas negativas

Por lo tanto, debemos tener en cuenta en el propio desarrollo
factores de rendimiento de igual forma que prestamos atención a la
correcta visualización de la interfaz en distintas condiciones por
ejemplo.

Mejoras de rendimiento en UWP

De entrada, buenas noticias. Se han incluido en Windows 10 mejoras como:

  • Rendimiento aumentado en Listview.
  • Mejoras en redimiento en ScrollViewer.
  • Interoperatibilidad XAML/DX.
  • Casting de elementos visuales.
  • Gestión Bitmap Source.
  • Etc.

Gracias a todas estas mejoras conseguimos sin cambios en nuestra
forma de trabajar o en el código, mejoras en el consumo de CPU y de
memoria.

Mejoras rendimiento UWP

Mejoras rendimiento UWP

Novedades en XAML

Con Windows 10 nos llegan un conjunto de novedades relacionados con
la gestión de enlace a datos, nuevas etiquetas de marcado y otros
detalles que afectan al rendimiento.

x:Bind

Data binding es un mecanismo mediante el cual
podemos enlazar los elementos de la interfaz de usuario con los objetos
que contienen la información a mostrar. Cuando realizamos data binding,
creamos una dependencia entre el valor de una propiedad llamada target con el valor de otra propiedad llamada source. Donde normalmente, la propiedad target recibirá el valor de la propiedad source.

Es el mecanismo base que nos permite utilizar el patrón MVVM en nuestras Apps móviles logrando:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

Sin embargo, además de toda la potencia mencionada teníamos ciertas
limitaciones. Los errores de Binding no se producían en tiempo de
compilación de la App además de tener diferentes mejoras relacionadas
con el rendimiento. Limitaciones existentes hasta ahora…

x:Bind es una nueva síntaxis en XAML que cubre un
objetivo similar a Binding. Permite crear un enlace a datos pero con
significativas diferencias. Mientras que con Binding se utiliza
reflexión en tiempo de ejecución para resolver el enlace a datos, con
x:Bind se realiza una validación en tiempo de ejecución ya que son
fuertemente tipados y compilados. Además, ofrece potentes mejoras en el rendimiento.

Veamos unas simples comparativas entre enlace a datos clásico y precompilado y su impacto en el rendimiento.

Uso de CPU utilizando enlace a datos clásico:

Uso de CPU en Bindings clásicos

Uso de CPU en Bindings clásicos

Uso de CPU utilizando bindings compilados:

Uso de CPU en binding compilado

Uso de CPU en binding compilado

También se reduce el consumo de memoria en comparación con Bindings clásicos:

Comparativa de consumo de memoria entre Bindings

Comparativa de consumo de memoria entre Bindings

Vista las visibles mejoras a nivel de rendimiento, ¿cómo se usan?.

En esta ocasión, crearemos un listado de casas donde utilizaremos
x:Bind en la plantilla que representará cada elemento de la lista.

Nuestra interfaz sera muy simple en esta ocasión contando con un sencillo ListView:

<ListView
     ItemsSource="{Binding Houses}" />

Cargaremos el listado de casas con un método creando datos falsos en local de manera aleatoria:

private void LoadHouses()
{
     _houses = new ObservableCollection<House>();
     Random random = new Random();
     for (int i = 0; i < 100; i++)
     {
          _houses.Add(new House
          {
               Place = Places[random.Next(0, Places.Count)],
               Photo = string.Format("ms-appx:///Assets/{0}.png", random.Next(1, 4)),
               Price = string.Format("${0}", random.Next(10000, 100000).ToString())
          });
     }
}

La definición del template de cada casa:

<DataTemplate x:Key="HouseTemplate" x:DataType="model:House">
     <Grid Width="200"
           Height="80">
          <Grid.ColumnDefinitions>
               <ColumnDefinition Width="75" />
               <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
               <RowDefinition Height="Auto" />
               <RowDefinition Height="Auto" />
           </Grid.RowDefinitions>
           <Image Grid.RowSpan="2"
              Source="{x:Bind Photo}"
              MaxWidth="70"
              MaxHeight="70" />
           <TextBlock Text="{x:Bind Place}"    
                  Grid.Column="1"
                  FontSize="18"/>
           <TextBlock Text="{x:Bind Price}"  
                  Grid.Column="1"  
                  Grid.Row="1"
                  FontSize="12" />
     </Grid>
</DataTemplate>

Utilizamos x:Bind para enlazar cada elemento visual de la plantilla a
la propiedad deseada. Importante resaltar además de compilados, son
fuertemente tipados. Es obligatorio para no tener errores de compilación
indicar el tipo de los datos a los que accedemos por enlace a datos.
Esto lo realizamos utilizando x:DataType.

Nuestro listado quedara:

<ListView
     ItemsSource="{Binding Houses}"
     ItemTemplate="{StaticResource HouseTemplate}" />

DataTemplate utilizando x:Bind

DataTemplate utilizando x:Bind

 

Lo visto hasta ahora nos indica que:
  • Tenemos la posibilidad de tener bindings compilados obteniendo errores en tiempo de compilación.
  • Son fuertemente tipados por lo que debemos indicar el tipo de la información.
  • Obtenemos mejoras en el rendimiento tanto en consumo de CPU como de memoria.

Por lo tanto, ¿lo usamos siempre?

La respuesta corta es no. Entrando en profundidad:

  • Los bindings compilados, en ocasiones,  tienen un comportamiento
    diferente al de los bindings clásicos existiendo situaciones no válidas
    para los primeros.
  • Los bindings compilados como indicamos al nombrarlos se compilan
    permitiéndonos obtener errores en tiempo de compilación pero tambien nos
    aporta limitaciones. No podemos crear bindings compilados dinámicamente
    (añadir o quitar bindings en runtime).
  • Con los bindings clásicos podemos crear un mismo template para
    entidades diferentes siempre y cuando el nombre de las propiedades
    coincida. Con los bindings compilados como hemos visto, estan
    fuertemente tipados y no podemos realizar lo mismo.

Podéis descargar el ejemplo utilizando x:Bind a continuación:

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

Ver GitHub

x:Phase

Otra etiqueta nueva incluida en Windows 10, en este caso destinada a
permitir realizar renderizado por fases. Con Windows 8.1 se introdujo en
listados el evento ContainerContentChanging que nos
permitía el renderizado progresivo de elementos. Requería código para
actualizar la plantilla que dificultaba el uso de enlace a datos.

Ahora tenemos una nueva etiqueta llamada x:Phase que nos permite realizar con suma facilidad renderizado progresivo. Estableceremos valores numéricos.

NOTA: Por defecto el valor implícito es x:Phase=”0″.

<DataTemplate x:Key="XPhaseHouseTemplate" x:DataType="model:House">
     <Grid Width="200"
           Height="80">
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="75" />
                 <ColumnDefinition Width="*" />
             </Grid.ColumnDefinitions>
             <Grid.RowDefinitions>
                 <RowDefinition Height="Auto" />
                 <RowDefinition Height="Auto" />
             </Grid.RowDefinitions>
             <Image
                 Grid.RowSpan="2"
                 Source="{x:Bind Photo}"
                 x:Phase="2"
                 MaxWidth="70"
                 MaxHeight="70" />
             <TextBlock
                 Text="{x:Bind Place}"  
                 Grid.Column="1"
                 FontSize="18"/>
             <TextBlock
                 Text="{x:Bind Price}"  
                 x:Phase="1"
                 Grid.Column="1"  
                 Grid.Row="1"
                 FontSize="12" />
     </Grid>
</DataTemplate>

El uso de x:Phase esta asociado al uso de bindings compilados.

Ejemplo de x:Phase:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

x:DeferLoadStrategy

x:DeferLoadStrategy nos permite retrasar la creación
de un elemento y sus elementos hijos lo que reduce los tiempos
necesarios para la creación de la UI y por lo tanto de carga. Sin
embargo, nada en la vida es gratis. A cambio, incrementamos levemente el
consumo de memoria.

NOTA: Cada elemento que retrasamos en su inicialización con x:DeferloadStrategy añade 600 Bytes en el consumo de memoria.

Podemos deducir que a mayor cantidad de elementos que nos ahorremos
del árbol visual, en menor tiempo se realizara la inicialización de la
vista pero aumentando el consumo de memoria. Por lo tanto, el uso de la
etiqueta es recomendado aunque requiere un análisis mínimo.

Creamos un ejemplo básico donde vamos a retrasar la creación de un Grid que contiene una imagen para crearlo bajo nuestro propio interés al pulsar el botón.

<Grid x:Name="DeferredPanel"  
      x:DeferLoadStrategy="Lazy">
      <Image
           Stretch="UniformToFill"
           Source="ms-appx:///Assets/NinjaCat.jpg" />
</Grid>

Utilizamos la etiqueta x:DeferLoadStrategy=”Lazy” en nuestro Grid. De esta forma indicamos que retrasamos la creación del panel y todo su contenido. Para utilizar la etiqueta debemos:

  • Definir un nombre con x:Name. Para iniciar posteriormente la incialización utilizaremos el nombre.
  • Podemos utilizarlo con cualquier elemento visual derivado de UIElement. No podemos utilizarlo con Page o UserControl.
  • No podremos utilizar con XAML XamlReader.Load.

Nos centramos ahora en el código que se ejecutará pulsando el botón:

page.FindName("DeferredPanel");

Utilizamos el método FindName pasándole el nombre del elemento.

Al pulsar el botón, el panel que retrasamos se crea. En este momento:

  • Se lanza el evento Loaded del panel.
  • Se evalúan los Bindings establecidos en el elemento.

Utilizando x:DeferLoadStrategy

Ejemplo de x:DeferLoadStrategy:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Otras consideraciones

Virtualización

Tanto el control ListView como el control GridView soportan nativamente virtualización. Sin embargo, hay varios modos para perder la virtualización, debemos tener en cuenta:

  • Si envolvemos un control ListView o GridView sobre un ScrollViewer,
    su tamaño tiende a infinito, sin establecer límites perderíamos la
    virtualización.
  • Podemos organizar los elementos utilizando diferentes paneles. Paneles como por ejemplo WrapGrid no soporta virtualización.

Optimiza imágenes

Las imágenes de tamaños muy elevados, sobretodo si las vamos a
mostrar en tamaños mucho mas reducidos, no compensan directamente debido
a la enorme cantidad de memoria que consumen.

Se pueden utilizar las propiedades DecodePixelHeight y DecodePixelWidth de un BitmapImage para establecer el alto y ancho en el que se decodifica la imagen.

BitmapImage bi = new BitmapImage(new Uri(baseUri, path));
bi.DecodePixelHeight = 120;
bi.DecodePixelWidth = 180;

Ejemplo:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Optimiza textos

El renderizado de texto es en ocasiones hasta un 50% más rápido en Windows 10. Podemos aumentar el rendimiento usando:

  • CharacterSpacing
  • Typography
  • LineStackingStregy=BaselineToBaseline/MaxHeight
  • IsTextSelectionEnabled = true

Herramientas

Cada vez que nos llega a los desarrolladores un nuevo SDK, es un
momento especial con una mezcla de altísima curiosidad y ganas de probar
novedades. Entre las novedades principales siempre hay nuevas APIs,
controles y otros elementos para poder realizar Apps que antes no eran
posibles. Sin embargo, entre el conjunto de novedades siempre suelen
venir nuevas herramientas que facilitan ciertas tareas: obtener más analíticas, mejores medidores de rendimiento, más opciones en emuladores, etc.

Visual Tree Inspector

Desde versiones anteriores de Visual Studio, una de las herramientas más demandadas son herramientas de depuración de UI XAML.

El árbol visual dinámico es la primera de dos piezas fundamentales para depurar UI XAML.

Visual Tree Inspector

Esta herramienta nos permite ver el árbol de controles de la App en
ejecución indicando el número de elementos hijos de cada elemento ideal
para entender la estructura visual de una vista compleja y entender
problemas de rendimiento.

La segunda pieza relacionada con las herramientas de depuración de UI XAML es el explorador de propiedades dinámico.

Live Property Explorer

Esta herramienta nos permite ver todas las propiedades del elemento
seleccionado, incluso aquellas sobreescritas. Podemos ver si las
propiedades estan establecidas con valores directos, accediendo a
recursos, etc. Además, y la parte más interesante, permite cambiar los
valores de la App en ejecución directamente viendo los cambios de manera
inmediata.

PerfTips

Generalmente y a pesar de contar con herramientas de diagnóstico, no
se suelen utilizar hasta que surgen problemas, es decir, tarde. En estos
casos, una vez detectados problemas de rendimiento, además de utilizar
las herramientas de diagnóstico se suelen poner puntos de ruptura entre
diferentes bloques para tener una idea de donde se pierde tiempo.

Estas prácticas no suelen ser muy buena idea. Por un lado se “caza”
al problema cuando ya es un problema grande, y por otro lado, con puntos
de ruptura o añadiendo líneas para obtener tiempos entre dos puntos del
código, no suele ser muy exacto.

PerfTips llega para ayudar a a entender que ocurre
en su aplicación a nivel de rendimiento mientras depura. En los puntos
de ruptura aparecerán popups con información relacionada con el
rendimiento.

PerfTips

PerfTips

PerfTips indica tiempos aproximados excluyendo los tiempos de pausa
en un punto de ruptura así como la carga de símbolos y tiempo
correspondiente al debugger.

Herramientas de diagnóstico

Las herramientas de diagnóstico son un conjunto de
ventanas destinadas a ofrecer información relacionada con el rendimiento
de la aplicación. Tenemos opciones para ver problemas de renderizado y parsing en la aplicación, monitorear consumo de Memoria y CPU o detectar problemas en el consumo de red entre otras opciones.

Consumo de CPU

Muestra el uso de CPU en todos los cores disponibles:

Consumo de CPU

Consumo de CPU

Consumo de memoria

Monitorea el consumo de memoria de la aplicación mientras estamos depurando.

Consumo de Memoria

Consumo de Memoria

Ahora disponible siempre tras cada sesión de depuración.

Renderizado y parsing

Identifica problemas de rendimiento relacionados con:

  • Parsing & Layout
  • Código de la App que provoca consume alto de CPU
Línea de tiempo

Línea de tiempo

Más información

[Windows 10] Utilizando x:Bind en un diccionario de recursos

Introducción

Data binding es un mecanismo mediante el cual
podemos enlazar los elementos de la interfaz de usuario con los objetos
que contienen la información a mostrar. Cuando realizamos data binding,
creamos una dependencia entre el valor de una propiedad llamada target con el valor de otra propiedad llamada source. Donde normalmente, la propiedad target recibirá el valor de la propiedad source.

Es el mecanismo base que nos permite utilizar el patrón MVVM en nuestras Apps móviles logrando:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

Sin embargo, además de toda la potencia mencionada teníamos ciertas
limitaciones. Los errores de Binding no se producían en tiempo de
compilación de la App además de tener diferentes mejoras relacionadas
con el rendimiento. Limitaciones existentes hasta ahora…

Con la llegada de Windows 10 tenemos la posibilidad de crear bindings compilados en lugar de los bindings clásicos.

x:Bind

x:Bind es una nueva síntaxis en XAML que cubre un objetivo similar a
Binding. Permite crear un enlace a datos pero con significativas
diferencias. Mientras que con Binding se utiliza reflexión en tiempo de
ejecución para resolver el enlace a datos, con x:Bind se realiza una
validación en tiempo de ejecución ya que son fuertemente tipados y compilados. Además, ofrece potentes mejoras en el rendimiento.

Crearemos un nuevo proyecto UAP:

Nueva App UAP

Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

En esta ocasión, nuestro objetivo sera crear un listado de casas
donde utilizaremos x:Bind en la plantilla que representará cada elemento
de la lista.

Comenzamos creando la entidad casa dentro de la carpeta Models:

public class House
{
     public string Place { get; set; }
     public string Price { get; set; }
     public string Photo { get; set; }
}

Nuestra interfaz sera muy simple en esta ocasión contando con un sencillo ListView:

<ListView
     ItemsSource="{Binding Houses}" />

El control tiene la fuente de datos enlazada a una propiedad de la ViewModel:

Cargaremos el listado de casas con un método creando datos falsos en local de manera aleatoria:

private void LoadHouses()
{
     _houses = new ObservableCollection<House>();
     Random random = new Random();
     for (int i = 0; i < 100; i++)
     {
          _houses.Add(new House
          {
               Place = Places[random.Next(0, Places.Count)],
               Photo = string.Format("ms-appx:///Assets/{0}.png", random.Next(1, 4)),
               Price = string.Format("${0}", random.Next(10000, 100000).ToString())
          });
     }
}

Y llegamos a la parte más importante, la definición del template de
cada casa, en un diccionario de recursos. Creamos el diccionario de
recursos:

Creamos diccionario de recursos

Creamos diccionario de recursos

Lo registramos en los recursos de la App:

<ResourceDictionary.MergedDictionaries>
     <ResourceDictionary Source="/Styles/AppResourceDictionary.xaml" />
</ResourceDictionary.MergedDictionaries> 

Definimos el template:

<DataTemplate x:Key="HouseTemplate" x:DataType="model:House">
     <Grid Width="200"
           Height="80">
          <Grid.ColumnDefinitions>
               <ColumnDefinition Width="75" />
               <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
               <RowDefinition Height="Auto" />
               <RowDefinition Height="Auto" />
           </Grid.RowDefinitions>
           <Image Grid.RowSpan="2"
              Source="{x:Bind Photo}"
              MaxWidth="70"
              MaxHeight="70" />
           <TextBlock Text="{x:Bind Place}"    
                  Grid.Column="1"
                  FontSize="18"/>
           <TextBlock Text="{x:Bind Price}"  
                  Grid.Column="1"  
                  Grid.Row="1"
                  FontSize="12" />
     </Grid>
</DataTemplate>

Utilizamos x:Bind para enlazar cada elemento visual de la plantilla a
la propiedad deseada. Importante resaltar además de compilados, son
fuertemente tipados. Es obligatorio para no tener errores de compilación
indicar el tipo de los datos a los que accedemos por enlace a datos.
Esto lo realizamos utilizando x:DataType. En nuestro ejemplo, la entidad House.

Nuestro ListView quedara:

<ListView
     ItemsSource="{Binding Houses}"
     ItemTemplate="{StaticResource HouseTemplate}" />

Ejecutamos la App y…

Error de compilación

Error de compilación

Diccionario de recursos, el problema

x:Bind o bindings compilados, como indicamos en el nombre deben ser
precompilados. No podemos añadir recursos haciendo uso de bindings
compilados ya sea a nivel de página o de Aplicación directamente;
debemos inicializar e instanciar una clase.

La solución

Debemos añadir una clase parcial, código asociado al recurso para
poder realizar la instanciación del mismo. Creamos la clase parcial:

public partial class AppResourceDictionary
{
     public AppResourceDictionary()
     {
         InitializeComponent();
     }
}

Asociamos la clase parcial con nuestro diccionario de recursos:

x:Class="XBindResources.Resources.AppResourceDictionary"

Modificamos el diccionario de recursos añadiendo la etiqueta x:Class. A continuación, instanciamos el diccionario de recursos:

<ResourceDictionary.MergedDictionaries>
                 
     <!-- Resources -->
     <resources:AppResourceDictionary />      
                 
</ResourceDictionary.MergedDictionaries>

Nos aseguramos que se lanza la llamada a InitializeComponent del mismo.

Si ejecutamos ahora nuestra App:

DataTemplate utilizando x:Bind y x:Phase

DataTemplate utilizando x:Bind y x:Phase

Voila!

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

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

Ver GitHub

Más información

[Tips and Tricks] Windows 10: Mostrar botón atrás en la TitleBar

Introducción

Casi cualquier aplicación que realices tendrá más de una página. Por lo que es vital saber como navegar entre ellas. Ya vimos paso a paso como navegar entre páginas en este otro artículo. Sin embargo, Windows 10 ha llegado como la culminación en el viaje hacia la convergencia en el desarrollo entre plataformas Windows. Ahora hablamos de Apps Universales
escritas una única vez con un código común tanto para la lógica de
negocio como para la interfaz de usuario. Además, generamos un único
paquete que mantendrá una interfaz consistente y familiar para el
usuario pero adaptada a cada plataforma. Podemos crear apps que
funcionen en todo tipo de dispositivos como teléfonos, tabletas,
portátiles, dispositivos IoT, Surface Hub e incluso HoloLens.
Ante
tal cantidad de dispositivos disponibles tenemos peculiaridades
exclusivas en algunos de ellos. Un de ellas es el botón virtual (en
pantalla) para volver atrás necesario en alguna familia de dispositivo.
Cuando permitimos navegar también debemos permitir volver atrás…

Navegación

Creamos un nuevo proyecto:

Nueva App UAP

Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

En la carpeta Views añadiremos nuestras páginas. En nuestro ejemplo, tendremos dos páginas:

  • FirstView
  • SecondView

Añadimos en la primera vista (FirstView) dos botones que permita navegar a la segunda vista sin pasar y pasándo parámetro:

<Grid
     Background="LightBlue">
     <StackPanel
          HorizontalAlignment="Center"
      VerticalAlignment="Center">
      <Button
        Content="Go to Second View"
        Command="{Binding GoToSecondCommand}" />
      <Button
        Content="Pass parameter"
        Command="{Binding ParameterCommand}"
        Margin="0, 10"/>
     </StackPanel>
</Grid>

El resultado visual:

Vista principal

Vista principal

Para permitir navegar a la segunda vista, necesitamos definir dos
comandos (uno navegará sin pasar parámetro y otro pasándolo) en la ViewModel:

public class FirstViewModel : ViewModelBase
{
        private ICommand _goToSecondCommand;
        private ICommand _parameterCommand;
  
    public override Task OnNavigatedFrom(NavigationEventArgs args)
    {
        return null;
    }
  
    public override Task OnNavigatedTo(NavigationEventArgs args)
    {
        return null;
    }
  
    public ICommand GoToSecondCommand
    {
        get { return _goToSecondCommand = _goToSecondCommand ?? new DelegateCommand(GoToSecondCommandExecute); }
    }
  
    public ICommand ParameterCommand
    {
        get { return _parameterCommand = _parameterCommand ?? new DelegateCommand(ParameterCommandExecute); }
    }
  
    private void GoToSecondCommandExecute()
    {
        AppFrame.Navigate(typeof(SecondView));
    }
  
    private void ParameterCommandExecute()
    {
        var rnd = new Random();
        AppFrame.Navigate(typeof(SecondView), rnd.Next(1, 100));
    }
}

Recordamos que el control Frame hospeda controles Page
y tiene un historial de navegación que se puede utilizar para ir hacia
atrás y hacia adelante por las páginas que ya visitaste. Tras obtener el
Frame correspondiente, utilizamos el método Navigate para realizar la navegación a otra página. Tenemos dos sobrescrituras del método Navigate:

  • Navigate(TypeName). Provoca que el Frame cargue el contenido especificado por el tipo pasado como parámetro.
  • Navigate(TypeName, Object).
    En este caso, además de indicar el tipo del contenido a cargar (primer
    parámetro), podemos pasar un parámetro a la página que se navega(segundo
    parámetro).

En nuestro ejemplo hemos utilizado la segunda sobrescritura del método pasando un parámetro.

Nuestra segunda página sera similar a la primera:

<Grid
     Background="LightGreen">
     <StackPanel
         HorizontalAlignment="Center"                    
         VerticalAlignment="Center">
         <Button
             Content="Go Back"
             Command="{Binding BackCommand}" />
         <TextBlock
             Text="{Binding Parameter}"
             Visibility="{Binding Parameter, Converter={StaticResource IntToVisibilityConverter}}"
             FontSize="48" />
     </StackPanel>
</Grid>

Contará con su propia viewmodel:

public class SecondViewModel : ViewModelBase
{
    // Variables
    private int _parameter;
  
    // Commands
    private ICommand _backCommand;
  
    public override Task OnNavigatedFrom(NavigationEventArgs args)
    {
        return null;
    }
  
    public override Task OnNavigatedTo(NavigationEventArgs args)
    {
            if (args.Parameter != null)
        {
            Parameter = Convert.ToInt32(args.Parameter);
                }
  
        return null;
    }
  
    public int Parameter
    {
        get { return _parameter; }
        set
        {
            _parameter = value;
            RaisePropertyChanged();
        }
    }
  
    public ICommand BackCommand
    {
        get { return _backCommand = _backCommand ?? new DelegateCommand(BackCommandExecute); }
    }
  
    private void BackCommandExecute()
    {
        if (AppFrame.CanGoBack)
            AppFrame.GoBack();
    }
}

Utilizamos el método GoBack del
Frame que navega al elemento más inmediato del historial de navegación,
la página anterior. Por otro lado, en caso de recibir parámetro, lo
asignamos a una propiedad bindeada en la vista. En nuestro ejemplo
pasamos un valor aleatorio entero entre 1 y 100.

Añadiendo el botón volver atrás en la Title Bar

Todas nuevas ViewModels derivan de la ViewModelBase:

public abstract class ViewModelBase : INotifyPropertyChanged
{
     public event PropertyChangedEventHandler PropertyChanged;
 
     public virtual Task OnNavigatedFrom(NavigationEventArgs args)
     {
         return null;
     }
 
     public virtual Task OnNavigatedTo(NavigationEventArgs args)
     {
         return null;
     }
 
     public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
     {
         var Handler = PropertyChanged;
         if (Handler != null)
            Handler(this, new PropertyChangedEventArgs(propertyName));
     }
}

Cada vez que entramos a una página se lanza el evento OnNavigatedTo. En este método utilizamos la propiedad AppViewBackButtonVisibility del objecto SystemNavigationManager asociado a la ventana actual:

Frame rootFrame = Window.Current.Content as Frame;
 
if (rootFrame.CanGoBack)
     SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
else
     SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;

Si podemos navegar atrás, mostramos el botón volver atrás en la Title Bar. Nos faltaría gestionar el evento lanzado al pulsar el botón utilizando el evento BackRequested:

SystemNavigationManager.GetForCurrentView().BackRequested += (sender, args) =>
{
     if (rootFrame.CanGoBack)
          rootFrame.GoBack();
};

NOTA: Recordad, no sería necesario este proceso en todas las familias de dispositivos soportados en Windows 10.

El resultado:

Botón volver en la Title Bar

Botón volver en la Title Bar

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

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información