Application UWP : Optimisation des pages multi résolutions

Les applications Windows 10 doivent être conçues pour réagir dynamiquement aux différentes diagonales d’écran.
Une solution disponible est de « remodéliser » son application :

par exemple un affichage sur téléphone nécessitera une navigation pour accéder au détail alors que sur un écran plus large le détail sera affiché sur la même page.

blog ai3 IC795418-280x300 Application UWP : Optimisation des pages multi résolutions

Pour plus d’informations c’est sur msdn : https://msdn.microsoft.com/fr-fr/library/windows/apps/dn958435.aspx

Le plus simple, modifier la visibility du détail dans le visualstatemanger

Voici une première possibilité de mise en place de ce type de page :

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
 <VisualStateManager.VisualStateGroups>
 <VisualStateGroup>
 <VisualState x:Name="wideState">
 <VisualState.StateTriggers>
 <AdaptiveTrigger MinWindowWidth="700" />
 </VisualState.StateTriggers>
 <VisualState.Setters>
 <Setter Target="detailNoLazy.Visibility" Value="Visible"/>
 </VisualState.Setters>
 </VisualState>
 <VisualState x:Name="narrowState">
 <VisualState.StateTriggers>
 <AdaptiveTrigger MinWindowWidth="0" />
 </VisualState.StateTriggers>
 <VisualState.Setters>
 <Setter Target="secondColumn.Width" Value="0"/>
 <Setter Target="detailNoLazy.Visibility" Value="Collapsed"/>
 </VisualState.Setters>
 </VisualState>
 </VisualStateGroup>
 </VisualStateManager.VisualStateGroups>

 <Grid.ColumnDefinitions>
 <ColumnDefinition MaxWidth="700" />
 <ColumnDefinition x:Name="secondColumn" />
 </Grid.ColumnDefinitions>
 <ListView>
 <ListViewItem>
 1st Item
 </ListViewItem>
 <ListViewItem>
 2nd Item
 </ListViewItem>
 <ListViewItem>
 3rd Item
 </ListViewItem>
 <ListViewItem>
 4th Item
 </ListViewItem>
 </ListView>

 <Grid x:Name="detailNoLazy" Visibility="Collapsed" Grid.Column="1">
 <Grid.RowDefinitions>
 <RowDefinition />
 <RowDefinition />
 </Grid.RowDefinitions>
 <maps:MapControl Width="300"/>
 <Image Grid.Row="1" Source="/Assets/paresseux.jpg" />
 </Grid>
 </Grid>

Et voici le résultat à l’écran :
blog ai3 lazysample-1024x429 Application UWP : Optimisation des pages multi résolutions

Lorsque la largeur d’écran est inférieur à 700 pixels le contenu affichant le détail est alors caché en passant sa visibility à collapsed.

Cette solution est simple et fonctionnelle, mais les éléments inutiles du détail sont tout de même chargés en mémoire.

blog ai3 nolazy-1024x340 Application UWP : Optimisation des pages multi résolutions

Pour éviter ce problème nous allons utiliser une des nouveautés des applications Windows 10 : le lazy loading.

En ajoutant x:DeferLoadStrategy= »Lazy » à la définition d’un contrôle on peut retarder la création de celui-ci jusqu’à son appelle explicite en code behind.

Pour plus d’informations c’est sur msdn : https://msdn.microsoft.com/fr-fr/library/windows/apps/mt204785.aspx

Le LazyTemplatePresenter à la rescousse

J’ai ainsi créé un usercontrol permettant de différer le chargement de son contenu avec le code ci dessous.


<UserControl
x:Class="lazycontroller.LazyTemplatePresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:lazycontroller"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">

<Grid x:Name="gridRoot" x:DeferLoadStrategy="Lazy">
<ContentControl ContentTemplate="{x:Bind LazyTemplate, Mode=OneWay}"
VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"/>
</Grid>
</UserControl>

 

</pre>
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace lazycontroller
{
public sealed partial class LazyTemplatePresenter : UserControl
{
public LazyTemplatePresenter()
{
this.InitializeComponent();
}

public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}

// Using a DependencyProperty as the backing store for Visibility. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.Register("IsVisible", typeof(bool), typeof(LazyTemplatePresenter), new PropertyMetadata(false, OnIsVisiblePropertyChanged));

private static void OnIsVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = (LazyTemplatePresenter)d;
var value = (bool)e.NewValue;
if (value)
{
if (uc.gridRoot == null)
{
uc.FindName("gridRoot");
}

if (uc.gridRoot.Visibility == Visibility.Collapsed)
{
uc.gridRoot.Visibility = Visibility.Visible;
}
}
else
{
if (uc.gridRoot != null && uc.gridRoot.Visibility == Visibility.Visible)
{
uc.gridRoot.Visibility = Visibility.Collapsed;
}
}
}

public DataTemplate LazyTemplate
{
get { return (DataTemplate)GetValue(LazyTemplateProperty); }
set { SetValue(LazyTemplateProperty, value); }
}

// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LazyTemplateProperty =
DependencyProperty.Register("LazyTemplate", typeof(DataTemplate), typeof(LazyTemplatePresenter), new PropertyMetadata(null));
}
}

Ce contrôle ne charge et n’affiche son contenu (défini dans sa propriété LazyTemplate) uniquement lorsque IsVisible est définie à true.

 

Voici le même exemple avec cette fois-ci l’utilisation de ce contrôle.


<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="wideState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="700" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="detailLazy.IsVisible" Value="True" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="narrowState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="secondColumn.Width" Value="0"/>
<Setter Target="detailLazy.IsVisible" Value="False" />

</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="700" />
<ColumnDefinition x:Name="secondColumn" />
</Grid.ColumnDefinitions>
<ListView>
<ListViewItem>
1st Item
</ListViewItem>
<ListViewItem>
2nd Item
</ListViewItem>
<ListViewItem>
3rd Item
</ListViewItem>
<ListViewItem>
4th Item
</ListViewItem>
</ListView>

<local:LazyTemplatePresenter x:Name="detailLazy" IsVisible="False" Grid.Column="1">
<local:LazyTemplatePresenter.LazyTemplate>
<DataTemplate>
<Grid Background="Red">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<maps:MapControl Width="300"/>
<Image Grid.Row="1" Source="/Assets/paresseux.jpg" />
</Grid>
</DataTemplate>
</local:LazyTemplatePresenter.LazyTemplate>
</local:LazyTemplatePresenter>

</Grid>

Cette fois ci tant que l’attribut IsVisible n’a pas été défini une fois à true le contenu du détail n’est pas chargé en mémoire.

Et voici le résultat !

Voici la consommation mémoire de mon application :

  • Grille détail non existante 36Mo
  • Grille détail en Collapsed 52Mo
  • Grille détail dans mon LazyTemplatePresenter 39Mo 🙂

J’espère que vous utiliserez ce contrôle pour une bonne optimisation de vos pages multi-résolutions !

Thierry,

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.