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.
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.
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.
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.
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();
}
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.
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.
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();
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;
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())
{
…
}
…
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
.
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.
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.
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.
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