I. Introduction aux Universal Windows Platform apps▲
Avec l’arrivée de Windows 10 dans l’écosystème de Microsoft, un nouveau type de projet est apparu permettant aux développeurs C# de concevoir des applications Windows 10 fonctionnant à la fois sur tablette, PC, smartphone, Xbox, Raspberry, HoloLens : les Universal Windows Platform apps. La grande innovation ici, c’est qu’avec un projet, donc un binaire final, l’application peut fonctionner sur chacune des plateformes citées ci-dessus. Le développeur n’aura donc besoin que de développer un code unique commun à toutes les plateformes.
Le nouveau SDK incorpore également un bon nombre de nouveautés permettant de gérer justement les différences entre les plateformes au niveau de la vue tout en gardant un seul code métier. De nouveaux contrôles et structures ont été rajoutés afin de diversifier un peu plus la palette d’outils XAML et d’améliorer les performances.
I-A. Un nouveau système d'extension▲
Afin de fonctionner sur les différentes plateformes, les projets UWP ont besoin d’ajouter les plateformes cibles dans les références du projet. Appelés des «?Extensions?», ces packages se trouvent dans le Reference Manager de votre projet. Faites un clic droit sur votre projet, puis Add Reference. Sous l’onglet Universal Windows se trouve une section Extensions, c’est ici que vous trouverez les différentes extensions à rajouter.
Pour le moment, les extensions disponibles concernent le Mobile, Desktop, IoT et Team. D’autres extensions arriveront comme pour la Xbox ou HoloLens. Une fois les extensions rajoutées à votre projet, votre projet fonctionnera toujours sur toutes les plateformes, et une seule compilation sera nécessaire. Cependant, imaginons que nous voulions utiliser une API disponible sur Mobile mais pas sur un Raspberry Pi 2 (avec l’extension IoT et Mobile sur le même projet), par exemple le bouton Volume ou le bouton Back. Le développeur aura besoin de vérifier à l’exécution que l’API est bien présente sur la plateforme sur laquelle fonctionne l’application. Pour ce faire, nous allons utiliser l’API Information.
I-B. L'API Information▲
Cette API est apparue dans le SDK Windows 10 afin de vérifier à l’exécution qu’une classe ou encore une méthode existe bien sur la plateforme où fonctionne l’application. En effet, votre application est censée fonctionner à la fois sur mobile et sur un Raspberry. Cependant, il est clair qu’un Raspberry ne possède pas de touche de volume, ou de bouton pour ouvrir la caméra comme sur un smartphone. Il est donc important de bien vérifier que ces API sont présentes lorsque vous souhaitez les utiliser.
La classe statique ApiInformation se situe dans l’espace de nom Windows.Foundation.Metadata et contient plusieurs méthodes statiques telles que IsApiContractPresent, IsEventPresent ou encore IsMethodPresent. Ces méthodes vont vous aider à déterminer si l’API est présente ou non, et ainsi rendre plus robuste votre code.
Par exemple, pour vérifier que l’API concernant le Bluetooth est présente, on utilise le code suivant :
if
(
ApiInformation.
IsApiContractPresent
(
"Windows.Devices.DevicesLowLevelContract"
,
1
))
{
// Le Bluetooth est présent
}
Chacune des méthodes statiques prend en paramètre une chaîne de caractères correspondant à la classe ou à la méthode que l’on veut trouver. Ici, si la méthode IsApiContractPresent retourne vrai, cela veut dire que le Bluetooth est présent sur la plateforme courante où s’exécute l’application. Afin de trouver les API dont vous devez tester la présence, il n’y a pas de secret : il faut les trouver dans la documentation officielle de Microsoft. Le lien suivant regroupe la majorité des API utilisées dans les applications UWP : https://developer.microsoft.com/fr-fr/windows/develop.
I-C. Les nouveautés du XAML▲
I-C-1. De nouveaux contrôles▲
Le SDK Windows 10 arrive avec son lot de nouveautés XAML (l’éditeur étant directement intégré à Visual Studio), et notamment avec de nouveaux contrôles XAML très pratiques comme le CalendarView. Ce dernier permet d’ajouter un contrôle calendrier entièrement personnalisable (couleur de l’en-tête, couleur de fond, affichage des jours…) et vient ainsi compléter un peu plus les composants utilisables dans les applications Windows 10. Son utilisation est très simple :
Exemple XAML d’un CalendarView Sélectionnez
|
Le calendrier ci-dessus a été personnalisé de la façon suivante :
- les jours sont en italiques?;
- la couleur des jours est rouge?;
- le premier jour de la semaine est le samedi.
La liste complète des propriétés personnalisables du calendrier est ici : https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.calendarview.aspx. Le CalendarView met à disposition un bon nombre d’événements afin de récupérer les interactions utilisateur avec le calendrier, notamment l’événement SelectedDatesChanged qui permet de notifier votre code que la date sélectionnée a changé.
Ensuite, on retrouve le CalendarDatePicker qui peut servir afin de récupérer une date en particulier. Ce contrôle est de type «?liste déroulante?» et a été optimisé afin de récupérer une et une seule date.
Exemple XAML d’un CalendarDatePicker Sélectionnez
|
L’exemple ci-dessus montre un CalendarDatePicker avec les caractéristiques suivantes :
- affichage du mois sur le premier jour du mois ;
- affichage du jour courant en surbrillance ;
- placeholder spécifique.
Ici aussi, l’événement DateChanged permet de savoir si la date a changé et de récupérer la nouvelle date indiquée par l’utilisateur.
Le contrôle Maps a également été enrichi de nouvelles fonctionnalités, comme l’imagerie 3D aérienne ou encore la vue au niveau des rues. L’autre information importante concernant le contrôle Maps est qu’il a enfin été unifié entre la version Mobile et Tablet/Desktop. Enfin, on ne retrouve plus qu’un espace de noms unique désormais permettant de faire fonctionner le contrôle sur toutes les plateformes. Sur Windows 8, une version existait pour Mobile et une autre pour le Desktop. Les nouveaux espaces de noms sont :
- Windows.UI.Xaml.Controls.Maps — espace de noms concernant le contrôle Maps?;
- Windows.Services.Maps — API pour des fonctionnalités avancées de géolocalisation et d’itinéraire. Afin d’utiliser ces services, il est important de posséder au préalable une clé depuis le Bing Maps Developer Center (https://www.bingmapsportal.com/).
Un nouveau contrôle fait son apparition, c’est l’InkCanvas. Il permet de facilement implémenter des fonctionnalités de dessin dans les UWP apps. De base, le contrôle ne supporte que le stylet, mais peut supporter la souris et/ou le touch via la propriété InkPresenter.InputDeviceTypes. Le contrôle met également beaucoup d’événements à disposition du développeur afin de gérer le cycle de vie du dessin de l’utilisateur via la propriété InkPresenter.StrokeInput.
Enfin, le dernier contrôle important inclus dans le nouveau SDK Windows 10 est le SplitView. Ce dernier permet de mettre facilement en œuvre ce qu’on appelle un «?hamburger menu?».
Le contrôle s’utilise de manière très simple. Il y a tout d’abord une partie Pane, qui est la partie de gauche (ou de droite, c’est configurable), et qui permet de définir un menu avec divers boutons. Il est également possible de mettre ce que vous voulez, comme un calendrier, une carte ou bien d’autres choses. Ensuite, il y a une partie Content, qui permet de définir le contenu de la page centrale. Le code ressemble alors à ceci :
<SplitView>
<SplitView.Pane>
<StackPanel>
<Button
Content
=
"Menu 1"
/>
<Button
Content
=
"Menu 2"
/>
<Button
Content
=
"Menu 3"
/>
</StackPanel>
</SplitView.Pane>
<SplitView.Content>
<StackPanel>
<TextBlock
Text
=
"Mon contenu"
/>
</StackPanel>
</SplitView.Content>
</SplitView>
Le développeur est ensuite libre d’effectuer la navigation entre les différentes pages en fonction du menu sélectionné. Un article de Jerry Nixon explique très bien le procédé : http://blog.jerrynixon.com/2015/04/implementing-hamburger-button-with.html.
I-C-2. Un binding compilé▲
Depuis la sortie de la librairie WPF permettant de concevoir des applications modernes et riches, le binding est une technique très utilisée en XAML permettant de lier subtilement la vue avec des données. Grâce à cette technique, le pattern MVVM (Model-View-ViewModel) est facilement mis en œuvre pour garantir ainsi un meilleur découpage entre la vue et le code métier de l’application.
Cependant, cette technique a ses désavantages, comme les performances. Le binding est coûteux en temps d’exécution, et donc il est important de l’utiliser avec parcimonie. L’autre inconvénient que nous pouvons retenir est l’absence de vérification à la compilation. En effet, lors de la compilation, il est impossible de savoir si la propriété liée existe réellement ou non?; c’est uniquement à l’exécution que l’application va lever une exception si la propriété n’existe pas.
Le SDK Windows 10 apporte une nouveauté à ce niveau-là : c’est le binding compilé. À présent, le binding est résolu à la compilation et permet ainsi de détecter les erreurs plus tôt. L’écriture a également changé, et utilise maintenant la notation x:Bind pour mettre en œuvre le binding compilé.
< Button Content ="{ x : Bind MyProperty }"/>
Attention : la propriété liée (ici MyProperty) doit absolument se trouver dans le code-behind de la vue. Si vous utilisez le pattern MVVM, votre ViewModel devra obligatoirement exister en tant que propriété dans le code-behind pour retrouver le même fonctionnement qu’avant.
Les avantages de ce binding sont multiples :
- meilleures performances (50 % plus rapide)?;
- résolution des types à la compilation?;
- navigation à travers les propriétés du code-behind. Par exemple, le code suivant Text="{x:Bind Employee.FirstName}" ira chercher la propriété FirstName de la propriété Employee du code-behind.
Lorsqu’un DataTemplate est utilisé (pour lier une liste à une ListView par exemple), il est important d’indiquer le type avec x:DataType afin que le binding compilé puisse résoudre les types à la compilation. Le code suivant illustre cette nouvelle technique.
<DataTemplate
x
:
Key
=
"SimpleItemTemplate"
x
:
DataType
=
"data:SampleDataGroup"
>
<StackPanel
Orientation
=
"Vertical"
Height
=
"50"
>
<TextBlock
Text
=
"{x:Bind Title}"
/>
<TextBlock
Text
=
"{x:Bind Description}"
/>
</StackPanel>
</DataTemplate>
Ici, on spécifie que le DataTemplate utilise des données de type data:SampleDataGroup, où se trouvent les propriétés Title et Description. Lors de la compilation, si ces propriétés ne sont pas présentes, une erreur surviendra.
I-C-3. Le chargement différé▲
Le chargement différé, ou defer loading, est une nouvelle technique proposée par le SDK Windows 10 afin d’améliorer les performances lors du rendu des vues. Il permet de spécifier quel composant va se charger en premier, et permet également de ne pas charger les éléments de l’UI s’ils ne sont pas initialisés au chargement de la page. Il suffit d’utiliser l’attribut x:DeferLoadStrategy="Lazy" pour appliquer le chargement différé sur l’élément et sur tous ses enfants. Le démarrage se fera ainsi plus rapidement, mais l’empreinte mémoire sera un peu plus importante (600 octets par élément chargé en différé). Les contraintes principales sont les suivantes :
- doit définir un x:Name afin d’être trouvé plus tard dans le code-behind ;
- seul un UIElement peut être chargé de façon différée, et non les ressources ;
- les éléments racines tels que Page ou UserControls ne peuvent pas être différés.
Un élément est chargé de manière différée lorsqu’on utilise des méthodes comme FindName ou encore GetTemplateChild qui vont chercher l’élément dans la vue et le créer. L’utilisation d’un Storyboard ou d’un VisualState permet de créer l’élément automatiquement lorsque l’application en a besoin. Lorsque l’élément est créé, l’événement Loaded se déclenche et tous les bindings sous-jacents sont évalués afin de lier les données à la vue récemment chargée.
Au cours de ce chargement, il est possible d’indiquer quels éléments sont chargés en premier, et lesquels sont susceptibles d’être chargés à la fin du chargement complet. Pour ce faire, on utilise x:Phase="OrderNumber" où OrderNumber représente un nombre supérieur à 0. Si un élément ne possède pas l’attribut, il sera automatiquement affecté à la phase 0. Cette technique est particulièrement utilisée pour les ListView ou les GridView. Elle permet d’ajouter une priorité sur les éléments d’un DataTemplate utilisant x:Bind et ainsi afficher en premier les éléments les plus importants de la liste. Cela est très utile lorsque la liste affiche beaucoup d’éléments et qu’elle ne peut pas tout charger en même temps.
<ListView
x
:
DeferLoadStrategy
=
"Lazy"
>
<ListView.ItemTemplate>
<DataTemplate
x
:
DataType
=
"model:FileItem"
>
<StackPanel>
<Image
Source
=
"{x:Bind ImageData}"
x
:
Phase
=
"3"
/>
<TextBlock
Text
=
"{x:Bind DisplayName}"
/>
<TextBlock
Text
=
"{x:Bind prettyDate}"
x
:
Phase
=
"1"
/>
<TextBlock
Text
=
"{x:Bind prettyFileSize}"
x
:
Phase
=
"2"
/>
<TextBlock
Text
=
"{x:Bind prettyImageSize}"
x
:
Phase
=
"2"
/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
L’exemple ci-dessus montre comment on peut manipuler le binding compilé avec le chargement différé des composants afin de maximiser les performances de chargement de la liste et donc la robustesse de l’application.
I-D. La gestion des écrans▲
L’arrivée du SDK Windows 10 permet maintenant aux développeurs de développer la même application à la fois sur tablette, ordinateur et smartphone. De nos jours, il est relativement facile de partager du code métier entre différentes plateformes, mais est-il également facile de concevoir une même vue conçue pour fonctionner sur différentes tailles d’écrans?? Microsoft a dû intégrer plusieurs nouveaux mécanismes pour faciliter et améliorer le développement de nos applications de demain.
I-D-1. L'Effective Pixel▲
Lorsque l’on travaille avec une Universal Windows Platform app et que l’on conçoit une interface graphique, on ne travaille pas avec des pixels physiques, mais avec des pixels effectifs, permettant à la plateforme d’adapter automatiquement à l’échelle de l’écran les valeurs en pixel indiquées dans le fichier XAML. Cela est valable pour les marges, les polices et toutes autres propriétés acceptant des valeurs en pixel.
La raison d’un tel système au sein du SDK est simple. Un utilisateur utilisant l’application sur son smartphone ou sur sa télévision ne sera tout d’abord pas à la même distance de l’écran. Ensuite, les résolutions n’étant pas les mêmes, si par exemple une police conserve le même nombre de pixels pour la police entre le smartphone et la télévision, le texte sera illisible sur le grand écran. Il est donc important d’avoir un algorithme qui recalcule la taille réelle des composants de la vue en fonction de la résolution.
Pour le développeur, cela ne change strictement rien durant le développement : l’algorithme de calcul des pixels physiques travaille en totale transparence sur l’application, et donc il n’a pas besoin de se soucier de la lisibilité de son contenu sur les différentes plateformes de son application. Pour résumer le principe de l’effective pixel, il est important de revenir sur ce que l’utilisateur perçoit. L’effective pixel permet à l’œil de l’utilisateur de croire que les éléments constituant les pages de l’application apparaissent tout le temps avec la même taille.
I-D-2. Le RelativePanel▲
Il y a des cas où le système de grille en XAML ne suffit plus pour construire sa vue. En effet, avec les composants à notre disposition aujourd’hui (Grid, Stackpanel, Canvas…) il est assez difficile de construire une vue complexe sans que notre fichier XAML soit très long et illisible.
Le SDK Windows 10 intègre un nouveau composant permettant de placer chaque élément l’un en fonction de l’autre, et ainsi de simplifier grandement la structure de notre vue : c’est le RelativePanel. Ce composant est de la famille des Panels et permet de disposer des éléments entre eux grâce à des attached properties (propriétés rattachées à un contrôle en particulier, mais applicable à n’importe quel élément). Les principales propriétés sont les suivantes :
- Above, Below, RightOf et LeftOf permettent de positionner l’élément courant par rapport à un autre?;
- AlignBottomWith, AlignTopWith, AligneVerticalCenterWith et bien d’autres encore permettent de gérer l’alignement de l’élément courant avec un autre élément. Par exemple, la propriété AligneBottomWith va aligner le bas de l’élément courant avec le bas de la cible?;
- AlignBottomWithPanel, AlignTopWithPanel ou encore AligneVerticalCenterWithPanel permettent d’aligner l’élément courant avec le panel englobant de l’élément.
Prenons un exemple et analysons-le. Le code est le suivant :
<RelativePanel
BorderBrush
=
"Gray"
BorderThickness
=
"10"
>
<Rectangle
x
:
Name
=
"RedRect"
Fill
=
"Red"
MinHeight
=
"100"
MinWidth
=
"100"
/>
<Rectangle
x
:
Name
=
"BlueRect"
Fill
=
"Blue"
MinHeight
=
"100"
MinWidth
=
"100"
RelativePanel.
RightOf
=
"RedRect"
/>
<Rectangle
x
:
Name
=
"GreenRect"
Fill
=
"Green"
MinHeight
=
"100"
RelativePanel.
Below
=
"RedRect"
RelativePanel.
AlignLeftWith
=
"RedRect"
RelativePanel.
AlignRightWith
=
"BlueRect"
/>
<Rectangle
Fill
=
"Yellow"
MinHeight
=
"100"
RelativePanel.
Below
=
"GreenRect"
RelativePanel.
AlignLeftWith
=
"BlueRect"
RelativePanel.
AlignRightWithPanel
=
"True"
/>
</RelativePanel>
Le résultat est le suivant :
Les caractéristiques du code sont les suivantes :
- le rectangle Rouge ne possède pas de caractéristique particulière, il est donc naturellement placé en haut à gauche?;
- le rectangle Bleu possède la propriété RelativePanel.RightOf avec comme cible de rectangle Rouge : il est donc placé à sa droite?;
- le rectangle Vert est lui placé en dessous du rectangle Rouge grâce à la propriété RelativePanel.Below. Ensuite, il possède les propriétés AlignLeftWith et AlignRightWith permettant de placer ses côtés au même niveau que les côtés des rectangles Rouge et Bleu. De ce fait, la largeur est automatiquement calculée par le RelativePanel?;
- il en va de même pour le rectangle Jaune où on place son côté droit au même niveau que le côté droit du panel cette fois-ci. Dans ce cas, on utilise simplement un booléen (vrai ou faux) pour indiquer l’activation de l’alignement ou non.
Avec ceci, le développeur possède un premier outil afin de placer ses éléments dans sa vue, mais cela n’est pas suffisant. Il faut combiner ce mécanisme avec un autre pour adapter la vue en fonction de la taille de l’écran.
I-D-3. Le VisualStateManager et les AdaptiveTrigers▲
Le VisualStateManager est un composant déjà connu avec la version de Windows 8 et 8.1 : il permet d’activer certains «?états?» (dans le code-behind) dans le composant permettant de modifier l’UI. Les modifications peuvent s’appliquer sur la visibilité d’un composant, sa taille, son placement, etc. Avec ce système, on pouvait déjà gérer les différentes tailles d’écran, mais cela se faisait côté C# via le code-behind. De ce fait, un designer ne pouvait pas intégralement concevoir une page XAML sans faire du code C#, ce qui peut être une contrainte s’il n’a pas les connaissances nécessaires.
Le SDK Windows 10 comble ce manque en intégrant les AdaptiveTriggers directement dans le VisualStateManager. Ces nouveaux composants permettent de définir des états dans le VisualStateManager et qui vont être automatiquement lancés lorsque la condition de l’AdaptiveTrigger est remplie. Par exemple, il existe un AdaptiveTrigger permettant de gérer la taille de l’écran. L’idée serait alors de modifier l’UI lorsque l’écran dépasse un certain nombre de pixels, et ainsi adapter l’interface pour toutes les tailles d’écran. Prenons l’exemple ci-dessous :
<Grid
x
:
Name
=
"LayoutRoot"
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState
x
:
Name
=
"WideState"
>
<VisualState.StateTriggers>
<AdaptiveTrigger
MinWindowWidth
=
"600"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter
Target
=
"LayoutRoot.Background"
Value
=
"Green"
/>
</VisualState.Setters>
</VisualState>
<VisualState
x
:
Name
=
"NarrowState"
>
<VisualState.StateTriggers>
<AdaptiveTrigger
MinWindowWidth
=
"0"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter
Target
=
"LayoutRoot.Background"
Value
=
"Red"
/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
Les caractéristiques du code ci-dessus sont les suivantes :
- le VisualStateManager définit deux états de l’UI différents, un appelé WideState et l’autre NarrowState?;
- pour chacun des états, on définit un AdaptiveTrigger permettant d’indiquer quand l’état doit être activé. Pour le NarrowState, l’état est activé lorsque l’écran fait au moins 0 pixel (sur smartphone il sera activé en premier) alors que pour WideState, il sera activé lorsque l’écran fera au moins 600 pixels?;
- En dessous de la section VisualState.StateTriggers, on définit ce que l’état doit faire lorsqu’il est activé. Dans chacun des états, on définit simplement une couleur de fond différente pour la grille avec le nom LayoutRoot (la grille englobante).
Avec ce système, il est ainsi facile de réorganiser sa vue avec un RelativeLayout. Le code suivant montre un exemple d’utilisation des AdaptiveTrigger avec un RelativeLayout :
<VisualState
x
:
Name
=
"WideState"
>
<VisualState.StateTriggers>
<AdaptiveTrigger
MinWindowWidth
=
"600"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter
Target
=
"FirstNameText.(RelativePanel.RightOf)"
Value
=
"FirstNameLabel"
/>
</VisualState.Setters>
</VisualState>
<VisualState
x
:
Name
=
"NarrowState"
>
<VisualState.StateTriggers>
<AdaptiveTrigger
MinWindowWidth
=
"0"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter
Target
=
"FirstNameText.(RelativePanel.Below)"
Value
=
"FirstNameLabel"
/>
</VisualState.Setters>
</VisualState>
Le code ci-dessus modifie simplement la disposition du formulaire en fonction de la taille de l’écran. Lorsque l’écran dépasse 600 pixels, on place le champ de saisie à la droite du label, car il y a assez de place pour le placer ainsi. Dans le cas contraire, on place le label en dessous : sur smartphone le formulaire devient ainsi plus lisible.
Il est également possible de définir ses propres AdaptiveTriggers, par exemple lorsque le développeur veut une vue particulière si l’application a accès à Internet. Il suffit de créer une classe héritant de StateTriggerBase et d’utiliser la méthode SetActive (fournie par la classe mère) afin d’activer ou non l’état. L’exemple suivant montre comment gérer la connexion à Internet :
public
class
NetworkConnectionTrigger :
StateTriggerBase
{
public
NetworkConnectionTrigger
(
)
{
NetworkInformation.
NetworkStatusChanged +=
NetworkInformationOnNetworkStatusChanged;
}
public
bool
RequiresInternet {
get
;
set
;
}
private
async
void
NetworkInformationOnNetworkStatusChanged
(
object
sender)
{
await
Dispatcher.
RunAsync
(
CoreDispatcherPriority.
Normal,
(
) =>
{
if
(
NetworkInformation.
GetInternetConnectionProfile
(
) !=
null
)
SetActive
(
this
.
RequiresInternet);
else
SetActive
(!
this
.
RequiresInternet);
}
);
}
}
Le code ci-dessus active (ou non) l’état en fonction de la disponibilité de la connexion Internet. La classe Dispatcher est simplement utilisée ici pour revenir sur le thread principal de l’application (les événements sont toujours exécutés dans un thread séparé) et activer l’état.
En combinant ces deux systèmes, le développeur est maintenant capable de réagir à n’importe quelle taille d’écran et d’étendre ce système pour réagir à n’importe quoi finalement (connectivité Internet, Bluetooth, stockage, plateforme courante de l’application…). Il est possible de définir une infinité d’états et ainsi adapter les éléments de multiples façons possibles. Cela est d’autant plus vrai qu’avec Windows 10 l’application peut fonctionner sur des écrans allant de 5 pouces à plusieurs dizaines de pouces pour les télévisions, fonctionner en online comme en offline, posséder une carte SIM (ou non), fonctionner sur mobile ou sur desktop… Les AdaptiveTrigger permettent ainsi de gérer une infinité de cas possibles et travailler au mieux les vues de l’application.
I-E. L'intégration avec Cortana▲
Windows 10 intègre nativement le nouvel assistant de Microsoft et expose un certain nombre d’API utilisables dans les Universal Windows Platform app. Il est possible par exemple de demander à Cortana d’ouvrir son application à une page en particulier et d’initialiser certaines données de manière automatique, ou encore d’exécuter des calculs et de renvoyer une réponse précise.
Pour ce faire, Cortana se base sur un fichier XML qui décrit les commandes reconnaissables et utilisables par Cortana pour interagir avec l’application : c’est le fichier VCD (Voice Command Definition).
<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands
xmlns
=
"http://schemas.microsoft.com/voicecommands/1.2"
>
<CommandSet
xml
:
lang
=
"fr-fr"
Name
=
"MyTripCommandSet_fr-fr"
>
<AppName>
Adventure Works </AppName>
<Example>
Montrer mon voyage à Londres </Example>
<Command
Name
=
"showTripToDestination"
>
<Example>
Montrer mon voyage à Londres</Example>
<ListenFor
RequireAppName
=
"BeforeOrAfterPhrase"
>
montrer [mon] voyage à {destination}
</ListenFor>
<Feedback>
Voici les détails du votre voyage à {destination}</Feedback>
<VoiceCommandService
Target
=
"MyService"
/>
</Command>
</CommandSet>
</VoiceCommands>
</xml>
Le VCD ci-dessus comporte les caractéristiques suivantes :
- on définit un CommandSet nommé «?showTripToDestination?»?;
- la balise Exemple permet simplement de mettre une phrase de démonstration qui sera affichée à l’utilisateur lorsqu’il activera Cortana. Cette balise est obligatoire en tant que balise enfant de Command et CommandSet?;
- on définit ensuite la balise ListenFor qui va demander à Cortana de surveiller la phrase indiquée. Si elle est reconnue par Cortana, alors la commande est activée. La balise possède l’attribut RequireAppName pour dire si la commande a besoin du nom de l’application avant de spécifier la commande. Ici, on spécifie ‘BeforeOrAfterPhrase’ pour indiquer que le nom de l’application doit être spécifié avant ou après la phrase indiquée dans la balise?;
- la notation [] indique que le mot est optionnel?;
- la notation {} permet de récupérer le mot spécifié par l’utilisateur dans le code plus tard?;
- la balise Feedback indique une chaîne de caractères qui sera affichée à l’utilisateur si la commande est activée?;
- Enfin, la commande VoiceCommandService indique quelle classe de service sera utilisée lors de l’activation de cette commande. Il est également possible de naviguer vers l’application en utilisant la balise Navigate à la place en spécifiant la page. La gestion de l’appel se fait alors dans le OnActivated de la page?;
Après avoir défini son VCD, il faut l’enregistrer dans l’application. Cet enregistrement se déroule dans OnLaunched de la classe App grâce aux deux lignes ci-dessous :
StorageFile vcdStorageFile =
await
Package.
Current.
InstalledLocation.
GetFileAsync
(
@"MyVCD.xml"
);
await
VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync
(
vcdStorageFile);
On va tout d’abord chercher le fichier dans le dossier d’installation de l’application, puis on enregistre le fichier.
Dans l’exemple de VCD que nous avons montré plus haut, nous utilisons une classe de service lorsque la commande est activée. Cette classe est simplement un BackgroundTask permettant d’exécuter du code de manière asynchrone. Ce code pourrait permettre de faire des calculs, appeler une API… Comme tout BackgroundTask, il est important d’enregistrer également la classe dans le manifeste de l’application.
Avec ceci, Cortana est alors capable d’exécuter du code contenu dans votre application, et ainsi renvoyer une réponse qui peut prendre la forme d’un texte, d’une liste d’éléments, d’une image… Le code ci-dessous présente comment renvoyer une réponse simple à l’utilisateur :
class
MyService :
IBackgroundTask
{
public
async
void
Run
(
IBackgroundTaskInstance taskInstance)
{
Var userMessage =
new
VoiceCommandUserMessage
(
);
userMessage.
DisplayMessage =
"This is my response"
;
userMessage.
SpokenMessage =
"My response"
;
var
response =
VoiceCommandResponse.
CreateResponse
(
userMessage);
var
voiceServiceConnection =
VoiceCommandServiceConnection.
FromAppServiceTriggerDetails
(
taskInstance.
TriggerDetails as
AppServiceTriggerDetails);
await
voiceServiceConnection.
ReportSuccessAsync
(
response);
}
}
Les actions effectuées sont les suivantes :
- on instancie un objet de type VoiceCommandUserMessage, permettant de définir une réponse simple qui sera compréhensible par Cortana?;
- on définit ensuite les propriétés DisplayMessage, qui sera le message affiché par l’utilisateur, et SpokenMessage qui sera le message récité par Cortana?;
- on utilise ensuite la classe VoiceCommandResponse permettant de récupérer un objet pour interagir avec Cortana et indiquer la progression de la réponse (en cours, réussite ou échec)?;
- on récupère ensuite un objet VoiceServiceConnection pour envoyer la réponse à Cortana via la méthode ReportSuccessAsync.
L’API Cortana permet de faire bien plus de choses encore. La documentation complète se trouve ici : https://msdn.microsoft.com/fr-fr/windows/uwp/input-and-devices/interact-with-a-background-app-in-cortana.
I-F. Conclusion▲
Pour conclure, il est clair que les nouveaux types de projets UWP app et le nouveau SDK ouvre encore un peu plus le champ des possibles pour les développeurs. Multiplateformes et responsive, ces nouvelles applications vont permettre d’accélérer les temps de développement et réduire les maintenances dues à du code dupliqué. L’interactivité avec Cortana apporte également son petit plus à l’application et permet d’accéder à des infos très rapidement sans forcément ouvrir l’application. Les développeurs Windows peuvent maintenant faire parler toute leur imagination autour d’un seul projet fonctionnant à la fois sur PC, tablette et smartphone.
II. Remerciements▲
J’aimerais remercier f-leb pour sa correction pertinente et Siguillaume, chrtophe, gaby277, Laethy, dorinf et Malick SECK pour leurs relectures techniques et leurs suivis tout au long de la rédaction de l’article.