Nouveautés du XAML de Windows 10

Pour les applications destinées à son Store, Windows 8 a introduit un nouveau modèle de programmation ainsi qu’un ensemble d’APIs sous forme d’une hiérarchie de classes. Les interfaces graphiques des applications dites modernes peuvent être développées avec trois technologies différentes: XAML, WinJS et DirectX. Windows 8.1 a été l’occasion d’une première mise à jour majeure du XAML Moderne avec des optimisations de performances ainsi que l’introduction de nouveaux contrôles et le support de Windows Phone 8.1.

La sortie de Windows 10 va s’accompagner d’une mise à jour importante de XAML, liée à l’évolution de la plate-forme Windows elle-même:

·       Accomplissement de la vision des applications Windows universelles capables de s’exécuter aussi bien sur un téléphone portable que sur un PC avec plusieurs écrans haute définition,

·       Exécution des applications modernes en mode fenêtré,

·       Et un effort renouvelé sur les performances.

Pour les développeurs qui ont choisi les technologies basées sur XAML présentes depuis longtemps dans Windows avec WPF puis Silverlight, Windows 10 est une excellente nouvelle car l’utilisation de XAML y est omniprésente dans le système avec par exemple le menu démarrer, l’écran de connexion ou encore Spartan, le nouveau navigateur de Microsoft.

Il serait fastidieux d’énumérer toutes les modifications de XAML (quasiment tous les contrôles se voient enrichi), aussi cet article va illustrer les trois axes d’évolution de XAML cités ci-dessus à l’aide d’exemples représentatifs.

Applications universelles

Convergence des contrôles

Windows Phone 8.1 et Windows 8.1 partagent la plus grande partie des contrôles XAML mais d’une part certains contrôles ne sont disponibles que sur l’une de ces plates-formes (typiquement le Pivot qui est un héritage de Silverlight) et d’autre part, un certain nombre de templates ou de comportement diffèrent. Avec Windows 10, ces différences sont gommées, voici quelques exemples représentatifs :

·       Le Pivot n’est plus réservé au téléphone. Par ailleurs, ce contrôle illustre assez bien l’adaptabilité implémentée au sein de XAML : s’il peut afficher tous les en-têtes des PivotItems, il adopte une
apparence proche des onglets en mettant en gras l’en-tête du PivotItem sélectionné :

S’il ne peut pas afficher tous les en-têtes, la sélection est toujours alignée à gauche du pivot. Par ailleurs, des boutons apparaissent lors du survol par une souris alors qu’ils n’apparaissent jamais lors d’interactions tactiles.

 

·       La ListView et la GridView utilisant un ItemsStackPanel ou ItemsWrapGrid supportent les Sticky Headers sous Windows et Windows Phone. Il est possible de les désactiver à l’aide de la propriété  AreStickyGroupHeadersEnabled :

<ListView.ItemsPanel>

    <ItemsPanelTemplate>

        <ItemsStackPanel AreStickyGroupHeadersEnabled ="False"/>

    </ItemsPanelTemplate>

</ListView.ItemsPanel>

 

·       La réorganisation des éléments d’une liste utilisait deux approches différentes : sous Windows, une ListView ayant CanReorderItems=True et AllowDrop=True pouvait toujours être réorganisée alors que sous Windows Phone, il fallait passer la liste dans un mode spécial (annulé par Back ou dès que la liste perdait le focus) en positionnant sa propriété (non existante sous Windows)  ReorderMode à ListViewReorderMode.Enabled .

Sous Windows 10, seul le mode hérité de Windows est supporté, à la fois sur desktop et sur mobile.

 

 

VisualState Setters et Triggers

Les applications universelles doivent pouvoir s’exécuter sur différents types de matériels avec des tailles et résolutions d’écran variées. Par ailleurs, l’exécution en mode fenêtré sur le bureau demande aussi aux applications universelles de savoir s’adapter à différentes tailles de fenêtres. XAML possédait déjà la base de l’adaptabilité des interfaces à travers le VisualStateManager et les modèles Visual Studio pour les applications Windows 8.1 s’appuyaient sur celui-ci pour proposer un début de réponse à la problématique des vues à géométrie variable. Néanmoins, la syntaxe du VisualStateManager et des classes associés – VisualStateGroup et VisualState – restait très lourde.

Le XAML de Windows 10 continue à s’appuyer sur le VisualStateManager mais en simplifie la syntaxe et il réduit aussi le code nécessaire aux choix et déclenchements des changements d’état en introduisant deux notions : les Setters et les Triggers.

Imaginons une page comportant deux parties principales, que l’on veut arranger différemment selon que la fenêtre est plutôt de type paysage ou plutôt de type portrait :

Arrangement “Paysage”

Arrangement “Portrait”

 

Une manière de réaliser cet arrangement est de définir une grille de 2x2 et de modifier les propriétés Row et Column de l’élément représentant la partie 2.

Le XAML de Windows 8.1 pour modifier la position de partie 2 serait similaire à ceci :

<VisualState x:Name="Narrow">

    <Storyboard>

        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="TheBorder">

            <DiscreteObjectKeyFrame KeyTime="0">

                <DiscreteObjectKeyFrame.Value>

                    <x:Int32>1</x:Int32>

                </DiscreteObjectKeyFrame.Value>

            </DiscreteObjectKeyFrame>

        </ObjectAnimationUsingKeyFrames>

        <!-- même chose pour la colonne -->

    </Storyboard>

</VisualState>.

Les Setters de Windows 10 à la syntaxe proche de celle des styles simplifient cette écriture :

<VisualState x:Name="Narrow">

    <VisualState.Setters>

        <Setter Target ="TheListView.(Grid.Row)" Value="2"/>

        <Setter Target ="TheListView.(Grid.Column)" Value="0"/>

    </VisualState.Setters>

</VisualState>

 

La deuxième composante de cette adaptabilité est constituée par les déclencheurs (Triggers) : on peut associer des déclencheurs à chaque état. Le runtime XAML active l’état dont un des déclencheurs est actif. Dans notre exemple, l’état Paysage est par exemple déterminé par une largeur de fenêtre supérieure à une certaine valeur :

<VisualState x:Name="Wide">

    <VisualState.StateTriggers>

        <AdaptiveTrigger MinWindowWidth ="800" />

    </VisualState.StateTriggers>

</VisualState>

 

La classe StateTrigger peut être utilisée directement dans la définition des états et sa propriété IsActive liée du conteneur XAML ou du modèle associé, par exemple :

<VisualStateGroup>

    <VisualState x :Name="ConnectionDisabled"/>

    <VisualState x :Name="ConnectionEnabled">

        <VisualState.StateTriggers>

            <StateTrigger IsActive ="{x:Bind IsConnectionEnabled.Value, Mode=OneWay}"/>

        </VisualState.StateTriggers>

        <VisualState.Setters>

            <Setter Target ="ConnectedCB.IsEnabled" Value="True"/>

        </VisualState.Setters>

    </VisualState>

</VisualStateGroup>

 

Enfin il est possible de définir ses propres déclencheurs : il suffit de dériver de StateTriggerBase , d’exposer les propriétés qui seront utilisées dans la déclaration des états, puis de s’abonner aux changements permettant d’activer le déclencheur en appelant la méthode SetActive, par exemple au sein de l’événement SizeChanged de l’objet Window  :

public class SmallWindowTrigger : StateTriggerBase {

    double _maxWindowHeight = double.MaxValue;

 

    public SmallWindowTrigger() {

        Window .Current.SizeChanged += (sender, e) => EvaluateTrigger();

    }

    private void EvaluateTrigger() {

        Rect bounds = Window.Current.Bounds;

        SetActive(bounds.Height < _maxWindowHeight);

    }

    public double MaxWindowHeight {

        set {

            _maxWindowHeight = value;

            EvaluateTrigger();

        }

    }

}

Puis :

<VisualState x:Name="Small">

    <VisualState.StateTriggers>

        <local:SmallWindowTrigger MaxWindowHeight="400" />

    </VisualState.StateTriggers>

    <VisualState.Setters>

        <Setter Target ="TheListView.Visibility" Value="Collapsed"/>

    </VisualState.Setters>

</VisualState>

 

Lorsque plusieurs états peuvent être actifs, le runtime XAML utilise des certaines règles de priorité pour définir quel sera l’état activé. Les déclencheurs personnalisés ont en particulier la préférence par rapport aux déclencheurs standards.

 

RelativePanel

Le RelativePanel permet de positionner assez facilement des contrôles XAML les uns par rapport aux autres. Il peut s’utiliser seul mais c’est avec les Triggers et Setters des VisualStates qu’il atteint sa pleine mesure. Pour l’utiliser, il faut nommer les différents éléments que le RelativePanel contient et puis les positionner les uns par rapport aux autres à l’aide des propriétés attachées de la classe RelativePanel.

L’organisation horizontale illustrée ci-dessous s’implémente de la manière suivante :

<RelativePanel HorizontalAlignment="Center" VerticalAlignment="Center">

    <TextBlock x :Name="AddressTextBlock" Text="Address:" FontWeight="Bold"/>

    <TextBox x :Name="StreetTextBox" Header="Street:"  Text ="425 15th Ave E"

                RelativePanel.Below ="AddressTextBlock" Margin="0,8,0,0" Width="200"/>

    <TextBox x :Name="ZipCodeTextBox" Header="ZipCode" Text="98112"

                RelativePanel.Below ="StreetTextBox" Margin="0,8,0,0" Width="100"/>

    <TextBox x :Name="CityTextBox" Header="City" Text="Seattle"

                RelativePanel.RightOf ="ZipCodeTextBox" RelativePanel.AlignBottomWith="ZipCodeTextBox"

                Margin="8,0,0,0" Width="200"/>

</RelativePanel>

 

Les propriétés attachées du RelativePanel peuvent être rangées en trois groupes:

·       Above , Below , RightOf , LeftOf : dans l’exemple ci-dessus ZipCodeTextBox est below StreetTextBox , ce qui signifie que le haut de ZipCodeTextBox est égal au bas de StreetTextBox

·       Align{Left,Top,Bottom,Right,HorizontalCenter,VerticalCenter}With : utilisés pour positionner les éléments dans la direction perpendiculaire : la gauche de ZipCodeTextBox est alignée sur la gauche de StreetTextBox

·       Align{Left,Top,Bottom,Right,HorizontalCenter,VerticalCenter}WithPanel  : permettent de positionner les éléments par rapport au RelativePanel lui-même.

Deux points importants sont à noter :

·       L’arrangement des éléments se fait en séquence

·       Un élément incorporé dans un RelativePanel sans propriété attachée correspondante est aligné en 0 x 0 c’est-à-dire comme ayant les propriétés AlignTopWithPanel et AlignLeftWithPanel actives.

Exécution des applications en mode fenêtré

Positionnement de la fenêtre

Les applications universelles peuvent maintenant essayer de contrôler la manière dont elles sont affichées à l’aide de la classe Windows.UI.ViewManagement.ApplicationView (remarque : celle-ci n’appartient pas au framework XAML et est donc accessible à l’ensemble des applications universelles). Une application peut maintenant :

·       préciser son mode d’affichage au démarrage, typiquement dans la surcharge de la méthode Application.OnLaunched  :

ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;

ApplicationView.PreferredLaunchViewSize = new Size(600, 600);

Window.Current.Activate();

 

Remarque : il est important de positionner ces propriétés avant d’appeler Window.Activate() .

 

·       essayer de modifier sa taille :

ApplicationView.GetForCurrentView().TryResizeView(new Size(1200, 600));

 

·       essayer de passer en mode plein écran :

var current = ApplicationView.GetForCurrentView();

if (current.IsFullScreen)

{

current.ExitFullScreenMode();

}

else

{

ApplicationView.GetForCurrentView().TryEnterFullScreenMode();

}

Tracé dans la barre de titre

Il est possible de faire en sorte que le contenu XAML d’une fenêtre s’étende dans la partie réservée à la barre de titre.

 

Cela se fait à l’aide de la classe CoreApplicationViewTitleBar :


CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar;

coreTitleBar.ExtendViewIntoTitleBar = true;

Il est généralement souhaitable de déclarer un élément XAML comme faisant office de barre de titre : cela permet à l’utilisateur de déplacer la fenêtre en cliquant dans cette zone :

<Page…

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush }">

        <Grid.RowDefinitions>

            <RowDefinition Height ="Auto" />

            <RowDefinition Height ="*" />

        </Grid.RowDefinitions>

 

        <Border x :Name="CustomTitleBar">

            <TextBlock Style ="{StaticResource HeaderTextBlockStyle}" Text ="XAML in Windows 10"/>

        </Border>

        <!-- Contenu "réel" de la page -->

       

 

La CoreTitleBar expose de plus deux événements qu’il peut être nécessaire de traiter selon les scenarios: IsVisibleChanged permet de gérer le masquage de la barre de titre par exemple lors du passage en mode tablette; LayoutMetricsChanged permet de suivre l’évolution de la taille de la barre de titre, au cas où la barre de titre XAML soit destinée à avoir une hauteur standard.

 

Drag and Drop

Le drag and drop (glisser-déplacer en français) est présent depuis très longtemps dans Windows : d’abord réservé à l’explorateur de fichiers qui reste son utilisation principale, il a connu ses jours de gloire avec OLE2 et les documents composites et était totalement supporté avec les technologies Winform et WPF. A l’arrivée de Windows 8, les nouvelles applications s’exécutaient essentiellement en plein écran : le Drag and Drop n’était plus adapté et les « charmes » furent mis en avant pour le partage de données entre applications (même l’irremplaçable copier / coller s’est trouvé un peu en retrait de par l’absence des barres de menus). Windows 10 change à nouveau la donne en rétablissant le fonctionnement en mode fenêtré de toutes applications, modernes et classiques : le Drag and Drop retrouve donc toute sa justification et réapparaît dans les différentes APIs pour les applications modernes.

Un drag and drop se traduit par les opérations suivantes :

·       Démarrage du drag and drop

L’utilisateur clique sur un objet graphique et commence à le déplacer : l’application possédant objet, appelée la source, prépare un DataPackage contenant les données partageables.
Elle peut aussi modifier l’aspect visuel de l’élément déplacé, indiquer quelles opérations sont acceptées (copie, déplacement, lien) voire tout simplement annuler le drag and drop.

Remarque : le drag and drop tactile est entièrement supporté. Il faut néanmoins maintenant le doigt appuyé pendant quelque temps avant de commencer à le déplacer afin que le système puisse différentier le Drag and Drop des autres interactions comme le défilement.

·       Survol

Lors du survol d’une zone de dépose possible, l’application correspondante (cible) inspecte les données et indique quelle action résulterait de cette dépose : aucune (format inconnu), copie, déplacement ou lien. Elle peut aussi modifier l’aspect visuel de l’élément déplacé ou lui associer un texte. Un exemple de cette modification graphique est donné par le Menu Démarrer : lorsqu’un élément de la liste « Tous les programmes » arrive sur la grille, il prend l’apparence d’une tuile.

·       Dépose

Lorsque l’utilisateur lâche l’objet, l’application cible accède au DataPackage et traite les données associées. Comme l’utilisateur peut lâcher l’objet à n’importe quel moment, il est toujours possible que la cible ne comprenne pas les données. Il est aussi possible de lâcher l’objet en dehors de toute cible ou d’interrompre le drag and drop en appuyant sur Escape.

·       Finalisation

Une fois que l’éventuelle cible a traité les données déposées, l’application source est prévenue de la fin de l’opération et peut à son tour effectuer les opérations nécessaires, en particulier dans le cas d’un déplacement.

Dans tous les cas, il faut noter que l’infrastructure de drag and drop est au service de l’utilisateur et des applications et ne sert que de vecteur de communication : elle n’interdit pas à l’utilisateur de lâcher l’objet manipulé sur une cible ne sachant pas traiter les données (l’événement Drop ne sera néanmoins pas déclenché dans ce cas), tout comme elle n’est pas en mesure d’imposer la suppression des données dans l’application source si une application cible demande uniquement un déplacement.

Deux classes XAML sont au cœur du Drag and Drop :

·       La ListView qui le supportait déjà sous Windows 8.1 et de manière plus limitée (réorganisation uniquement) sous Windows Phone 8.1. Cette classe supporte maintenant le drag and drop inter-applications et se voit donc ajouter l’événement DragItemsCompleted (qui n’était pas nécessaire quand la cible appartenait nécessairement à la même fenêtre que la source).

Même s’il s’appuie sur la même infrastructure, le Drag and Drop de la ListView se veut une solution clés en mains, sans grande possibilités de personnalisation.

·       Tout UIElement peut être source ou cible de Drag and Drop. C’est en se basant sur les événements de cette classe que le développeur peut vraiment personnaliser l’expérience utilisateur.

Démarrage du Drag and Drop

Le démarrage d’un Drag and Drop peut être automatique si la propriété CanDrag vaut True , ou explicite via l’appel à StartDragAsync . Dans les deux cas, le démarrage de l’opération se traduit par l’événement DragStarting  pendant lequel la source va remplir le DataPackage qui représente les données échangées mais peut aussi personnaliser le visuel déplacé par l’utilisateur :

private async void TheBorder_DragStarting(UIElement sender, DragStartingEventArgs args)

{

    args.Data.SetText(…);

    args.Data.RequestedOperation = DataPackageOperation.Copy;

    args.DragUI.SetContentFromDataPackage();

}

Le visuel peut prendre plusieurs formes:

·        par défaut, un instantané de l’UIElement source du D&D est pris,

·        DragUI.SetContentFromDataPackage() demande au système de choisir une representation en fonction des données transférées

·        DragUI.SetContentFromBitmapImage() affiche la BitmapImage passée en paramètre

·        DragUI.SetContentFromSoftwareBitmap() affiche le SoftwareBitmap passé.

Le SoftwareBitmap est une nouvelle classe permettant d’échanger des objets images entre les différentes parties de l’API des applications universelles. Dans le cas de XAML, il est facilement créé à partir d’une RenderTargetBitmap qui permet de générer un bitmap à partir d’un élément XAML appartenant à l’arbre visuel. Dans le cadre de l’événement DragStarting , il faut utiliser un Deferral pour signifier que le traitement de l’événement sera asynchrone (puis en signaler la fin) :

var deferral = args.GetDeferral();

HiddenTextBlock.Text = text;

RenderTargetBitmap rtb = new RenderTargetBitmap();

await rtb.RenderAsync(HiddenBorder);

IBuffer buffer = await rtb.GetPixelsAsync();

SoftwareBitmap sb = SoftwareBitmap.CreateCopyFromBuffer(buffer,

                         BitmapPixelFormat .Bgra8, rtb.PixelWidth, rtb.PixelHeight,

                         BitmapAlphaMode.Premultiplied);

args.DragUI.SetContentFromSoftwareBitmap(sb);

deferral.Complete();

Survol

Lorsque l’objet déplacé passe par-dessus une cible de drag and drop, les événements existant déjà sous Windows 8.1 sont déclenchés. La cible doit préciser quelle opération elle accepte (sinon, l’événement Drop ne sera pas déclenché) et elle peut à son tour personnaliser le visuel de l’opération via la propriété DragUIOverride de l’événement :

·       Le contenu peut être alimenté à l’aide d’une BitmapImage ou d’un SoftwareBitmap ( args.DragUIOverride.SetContentFromSoftwareBitmap() ). Il peut être aussi masqué :

e.DragUIOverride.IsContentVisible = false;

 

·       Le titre peut être modifié ou masqué :

e.DragUIOverride.Caption = "Drop here to insert image";

e.DragUIOverride.IsCaptionVisible = true;

 

·       Enfin, l’icône peut être masquée mais sa personnalisation se fait uniquement au travers de l’opération acceptée (i.e. il n’est pas possible d’afficher des icônes personnalisées) :

e.AcceptedOperation = DataPackageOperation.Copy;

e.DragUIOverride.IsGlyphVisible = true;

 

Dépose

Le drop se traduit par le déclenchement de l’événement Drop . La cible peut alors récupérer les données du DataPackage et retourner l’opération effectuée :

private async void Border_Drop(object sender, DragEventArgs e)

{

   if (e.DataView.Contains(StandardDataFormats.StorageItems))

   {

        e.AcceptedOperation = DataPackageOperation.Copy;

        foreach (var storageItem in await e.DataView.GetStorageItemsAsync())

        {

         

        }

Finalisation

La source peut connaître le résultat de l’opération à l’aide de l’événement DropCompleted ou via le retour de StartDragAsync dans le cas d’un Drag and Drop déclenché par StartDragAsync .

Amélioration des performances

Liaisons de données compilées

Omniprésent dans toutes les applications XAML, la liaison de données (Data Binding) s’est toujours appuyée sur une découverte et une invocation dynamiques des propriétés des objets liés. Dans le cas de .NET, ceci est naturellement implémenté via la réflexion ; dans le cas de C++/CX, l’attribut BindableAttribute ajouté aux classes à lier permet la génération de code permettant ces invocations dynamiques. Cela dit, la dynamicité des appels se paie en termes de performances, aussi Windows 10 apporte la notion de liaisons compilées : l’idée est de préparer les appels liés à la liaison de données lors de la compilation, ce qui améliore grandement les performances (et permet au passage de découvrir la plupart des erreurs à ce moment-là au lieu de voir une page blanche lors de l’exécution).

Visual Studio génère donc le code nécessaire aux liaisons lors de la compilation : cela nécessite de connaître le type des objets liés aux composants graphiques. Cette information n’étant pas disponible via le DataContext sur lequel s’appuie le Data Binding classique, une nouvelle extension XAML – {x:Bind} – apparaît.

Dans une large mesure, {x:Bind} s’utilise de manière similaire à {Binding} avec toutefois quelques nuances. Par exemple :

<Page

  < TextBox Text="{Binding CustomFilter}"/>

<Page

   < TextBox Text="{x:Bind CustomFilter}"/>

La propriété CustomFilter est lue dans l’objet constituant le DataContext de la TextBox , éventuellement hérité d’un ancêtre de celle-ci

C’est la propriété CustomFilter de la page qui est lue.

Par défaut, le mode est OneWay

Par défaut, le mode est OneTime

 

<Page

  < CheckBox x:Name="UseFilterCheckBox"…/>

  < TextBox IsEnabled="{Binding IsChecked, ElementName=UseFilterCheckBox }"/>

<Page

  < CheckBox x:Name="UseFilterCheckBox"…/>

  < TextBox IsEnabled="{x:Bind UseFilterCheckBox .CustomFilter.IsChecked.Value}"/>

ElementName permet de designer un autre élément de la page comme source de la liaison

Comme x:Name=UseFilterCheckBox déclenche la création d’un membre de ce nom et de type CheckBox au niveau de la page, on peut l’utiliser directement dans le chemin d’accès à la propriété.

Le framework effectue lors de l’exécution la conversion entre le type de IsChecked (IReference<Boolean>) et celui de IsEnabled (Boolean)

Comme la liaison est fortement typée et résolue à la compilation, on doit lire la propriété Value pour obtenir le booléen attendu par IsEnabled

 

Comme il est dit plus haut, le mode par défaut est OneTime (l’idée est d’avoir par défaut le scénario le plus rapide) mais il est bien évidemment possible de le modifier en OneWay ou TwoWays . Une alternative est conserver ce mode et de déclencher la mise à jour des liaisons une fois le modèle mis à jour via Bindinds.Update() . Cette approche peut-être très efficace lors qu’un objet contenant en particulier des sous-objets est mis à jour de manière globale.

Même si on utilise un autre mode, on peut aussi suspendre temporairement les liaisons via Bindings.StopTracking() et les reprendre une fois le modèle mis à jour :

private void Refresh()

{

    Bindings.StopTracking();

    … // Mise à jour du modèle

    Bindings.Update();

}

 

La liaison de données est aussi très utilisée au sein des DataTemplates , typiquement dans le cas des ListViews . Dans ce cas, le type portant les propriétés liées pour chaque élément n’est ni la page, ni la ListView, ni les ListViewItems et il faut donc l’indiquer explicitement au compilateur dans le XAML à l’aide de l’attribut x:DataType  :

<DataTemplate x:Key="GameTemplate" x:DataType="local:Game">

   < Grid>

      < TextBlock Text="{x:Bind Result}"

</DataTemplate>

 

 

Une nouveauté intéressante liée à x:Bind est la possibilité de lier des événements à des méthodes de la classe liée, par exemple :

<DataTemplate x:Key="GameTemplate" x:DataType="local:Game">

      < Button Content="Save" Click ="{x:Bind Save}"/>

</DataTemplate>

 

Il y a quelques contraintes sur la signature de la méthode liée : elle peut ne pas avoir de paramètres ou accepter les paramètres de l’événement correspondant (penser à la génération de l’appel : les types des bases sont donc acceptés). C’est une généralisation de l’approche Command mais permet de lier plus d’événements sans passer par des classes intermédiaires de type EventToCommand.

 

 

Simplification de l’arbre visuel

Les contrôles XAML sont souvent implémentés à l’aide de hiérarchies assez sophistiquées d’objets. Un exemple simple mais caractéristique est la bordure en pointillés entourant un contrôle ayant récupéré le focus du clavier :

Auparavant, cette bordure nécessitait un rectangle pour les points noirs, un deuxième pour les points blancs ainsi qu’une grille pour être en mesure d’empiler les deux rectangles et le contenu réel du bouton :

<ControlTemplate TargetType="Button">

    <Grid…>

        <Border>

            <ContentPresenter/>

        </Border>

        <Rectangle x :Name="FocusVisualWhite"/>

        <Rectangle x :Name="FocusVisualBlack"/>

    </Grid>

</ControlTemplate>

 

Windows 10 ajoute la propriété UseSystemFocusVisuals à la classe Control  : si cette propriété vaut True , le rectangle en pointillés standard est tracé par le framework XAML et l’arbre visuel se trouve fortement simplifié :

<ControlTemplate TargetType="Button">

    <Grid>

        <ContentPresenter/>

    </Grid>

</ControlTemplate>

 

Pour les scénarios plus avancés, il est possible de désigner un descendant du contrôle comme étant le porteur du rectangle en pointillés en affectant à cet élément la propriété attachée Control.IsTemplateFocusTarget="True" . Enfin, il est toujours possible de se baser sur le VisualStateManager  : si UseSystemFocusVisuals=False , le système ne trace pas de rectangle du tout.

 

Tout comme ces rectangles traçant le focus, les contrôles standards XAML ou les templates définis par les développeurs contenaient souvent des éléments de type Border ou Rectangle (nécessitant souvent l’ajout d’une grille) pour tracer une bordure autour d’un conteneur comme un StackPanel . Sous Windows 10, les différents panels gagnent les propriétés Border , BorderThickness et Padding qui vont permettre de simplifier l’arbre visuel et d’améliorer légèrement les performances des applications.

ListView

La ListView étant par construction un contrôle affichant un grand nombre d’éléments, la plupart du temps liés à des données, elle fait l’objet d’améliorations à chaque nouvelle version de XAML.

En termes de performances, on peut noter au moins trois évolutions :

·       ChoosingGroupHeaderContainer et ChoosingItemContainer

Ces deux événements permettent à l’application de contrôler le recyclage des éléments et des en-têtes de groupes lors de la virtualisation.

 

·        x:phase

Windows 8.1 avait apporté l’événement ContainerContentChanging permettant de court-circuiter le Data Binding et aussi de réaliser un affichage progressif. L’exemple classique est l’affichage d’une liste de films : si l’utilisateur fait défiler la liste si rapidement que l’affichage des templates complets n’arrive pas à suivre, on peut afficher dans un premier temps uniquement le nom des films, ce qui permet à l’utilisateur de voir où il en est dans le défilement, et compléter l’affichage au fur et à mesure que des cycles CPU sont disponibles.

x:phase permet d’implémenter cette logique d’affichage progressif sur des éléments liés.

 

·       Sélection par bloc

Lorsqu’une liste possède des milliers d’éléments (par exemple une collection de mp3 ou de photos), la gestion de la sélection par élément individuel pose parfois des problèmes de performance. Windows 10 apporte la gestion de la sélection par blocs qui permet de résoudre de problème :

public void SelectRange(ItemIndexRange itemIndexRange)

où ItemIndexRange est défini par un index de début et une longueur, sélectionne tous les éléments correspondants.

public void DeselectRange(ItemIndexRange itemIndexRange);

désélectionne les éléments

public IReadOnlyList<ItemIndexRange> SelectedRanges { get; }

permet de récupérer l’ensemble des éléments selectionnés.

Conclusion

Comme on a pu le voir dans cet article, le framework XAML continue d’évoluer pour servir au mieux les besoins des applications Windows en supportant complètement l’approche Applications Universelles tout en poursuivant l’effort permanent d’optimisation des performances. Son utilisation au sein du système Windows lui-même ne fait que croître : le menu démarrer, le centre de notifications, l’écran de connexion ou le nouveau navigateur Microsoft Edge en sont les exemples les plus emblématiques et devraient conforter les développeurs dans la pérennité de cette plate-forme.

Alain ZANCHETTA
Développeur dans l’équipe XAML de Windows