Windows 8.1 : Classes XNA

by Nicolas Calvi 2. octobre 2013 12:02

Il m'est souvent arrivé de porter du code .Net (on va dire Classique, genre WPF, etc.) sur du Windows Store App. Une des problématiques que je rencontrais souvent était les bouts de code qui utilisaient des classes du Framework XNA.

De manière général, ces classes étaient souvent les mêmes : Vector3, Matrix, MathHelper, etc. Mais quand on arrive dans le monde des Windows Store App il est difficile de retrouver ses APIs. 

Afin de vous éviter de devoir décompiler ses classes, je vous propose aujourd'hui de télécharger le code source de celle-ci, compatible avec les Windows Strore App. C'est gratuit, c'est que du bonheur :)

Pour le téléchargement c'est ici : Microsoft.Xna.Framework.rar (33,84 kb)

 

Astuce : Inclinomètre (Windows 8.1)

by Nicolas Calvi 4. septembre 2013 07:30

Dans cette astuce nous allons découvrir l'inclinomètre, qui permet de connaitre comme son nom l'indique, le degré d'inclinaison d'un périphérique sur ses trois axes. 

Contrairement à d'autre senseur, l'inclinomètre n'a pas besoin d'une déclaration spéciale dans le manifeste de l'application.

La classe qui gère ce senseur est la classe statique "Inclinometer", qui regroupe ce qu'il faut pour gérer votre senseur. Contrairement au "Geolocator" ce senseur est toujours actif et travail en tâche de fond. Pour récupérer l'instance de l'inclinomètre, il faut passer par la fonction "GetDefault()" qui vous retourne l'instance courante du senseur. Si vous ne possédez pas de senseur, cette fonction vous retournera NULL, vérifiez donc bien l'instance avant de faire quoi que ce soit. Une fois l'instance retournée, voici les propriétés à connaitre :

  • MinimumReportInterval : Cela retourne l'intervalle minimum supporté par l'inclinomètre pour la mise à jour des positions. C'est une propriété en lecture, elle est a titre informative, elle est exprimée en milliseconde.
  • ReportInterval : C'est ici que l'on spécifie l'intervalle désiré (en milliseconde) entre deux notifications de changement d'inclinaison. Quand une tablette bouge, les notifications sont très rapide, cela permet de temporiser ces notifications si vous exécutez un traitement lourd entre chaque. Par contre, saisir un intervalle inférieur au "MinimumReportInterval" sera ignoré par l'API et se positionnera sur son minimum.

Voici ce que cela donne pour l'initialisation :

// Récupération de l'instance
inclinometer = Inclinometer.GetDefault();

// On test si l'inclinomètre existe
if ( inclinometer == null)
{
  throw(new Exception("Inclinometer not aviable."));
}
else
{
  // Spécification de l'intervalle
  double interval = 50;

  inclinometer.ReportInterval = interval < inclinometer.MinimumReportInterval ? inclinometer.MinimumReportInterval : interval;
}

Pour récupérer les changements d'inclinaisons il y a deux solutions, soit on attend que la classe nous l'indique (ce sera donc avec un abonnement à un événement) ou manuellement quand vous le souhaitez (donc avec une fonction).

Méthode automatique (Event) :

C'est la façon la plus simple d'être informé d'un changement d'inclinaison, en s'abonnant à l'événement "ReadingChanged" vous êtes notifié du changement :

// On se branche sur l'événement
inclinometer.ReadingChanged += OnInclinometerReadingChanged;

// Définition de la fonction pour l'événement
private void OnInclinometerReadingChanged(Inclinometer sender, InclinometerReadingChangedEventArgs args)
{
  // Récupération de la lecture du senseur
  InclinometerReading read = args.Reading;

  // Données les plus courantes
  float pitch = read.PitchDegrees;
  float roll = read.RollDegrees;
  float yaw = read.YawDegrees; 

  // Faire ici le traitement que vous voulez
}

Attention toutefois, cet événement ne s'exécute pas dans le ThreadUI, si vous devez affecter ces valeurs dans l'interface, n'oubliez pas de le faire dans le ThreadUI :

// On se branche sur l'événement
inclinometer.ReadingChanged += OnInclinometerReadingChanged;

// Définition de la fonction pour l'événement
private void OnInclinometerReadingChanged(Inclinometer sender, InclinometerReadingChangedEventArgs args)
{
  // On fait le traitement dans le ThreadUI
  await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    // Faire ici le traitement que vous voulez
  }
}

Voilà pour la méthode automatique.

Méthode manuelle (fonction) :

Dans ce cas précis, c'est nous qui allons demander à un moment que nous déterminons, les dernières valeurs captées par le senseur. Pour se faire on fait appel à la fonction "GetCurrentReading()" :

// Récupération des données
InclinometerReading read = inclinometer.GetCurrentReading();

// Données les plus courantes
float pitch = read.PitchDegrees;
float roll = read.RollDegrees;
float yaw = read.YawDegrees;

C'est une autre façon qui peut avoir beaucoup d'avantage (c'est un peu en fonction de l'utilisation que vous aurez du senseur). 

Voilà donc ce qu'il faut savoir pour bien commencer avec le senseur d'inclinaison.

Astuce : Géolocalisation (Windows 8.1)

by Nicolas Calvi 28. août 2013 07:30

Voici comment initialiser et gérer la géolocalisation sous Windows 8.1, c'est assez simple et vous verrez que ça va assez vite à mettre en place.

La géolocalisation sous Windows 8.1 commence par la déclaration dans le manifeste de l'application de la capacité "Location" en cochant la case adéquat :

Une fois cette modification faite, tout va se passer avec la classe "Geolocator", cette classe magique regroupe toutes les actions pour faire de la géolocalisation. Celle-ci une fois instanciée, possède certaine propriétés à connaitre :

  • DesiredAccuracy : Peux être affecté par deux valeurs (Default ou High), elle détermine avec quelle assiduité le module de géolocalisation va travailler pour vous géolocaliser. Attention, si vous êtes en High cela consommera plus de batterie si vous être en situation de mobilité.
  • DesiredAccuracyInMeters : Détermine le niveau de précision que vous voulez avoir en mètre pour la détection. Sachant que si cette valeur est inférieur ou égale à 100, il faut passer en "DesiredAccuracy = High" pour que cela fonctionne. Par expérience on voudrait mettre 1 mètre, mais sachez que vous ne pourrez pas de façon réaliste allez en dessous de 10 mètres. Les capteurs actuels étant incapable de donner autant de précision, on va dire que la valeur la plus basse est 10 mètres. Au final, si votre périphérique utilise une vraie puce GPS elle aura une précision de 10 mètres, si il utilise les réseaux Wifi la précision sera de 100 à 500 mètres en fonction si la zone en contient beaucoup, enfin si il utilise l'IP ce sera de l'ordre du kilomètre.
  • MovementThreshold : En gros cette propriété permet de définir le déplacement en mètre depuis le dernier point de détection, nécessaire pour vous notifier d'un déplacement. Par défaut c'est 0 mètre, ce qui signifie que chaque changement de position est notifié, c'est la valeur que l'on va utiliser dans la majeur partie des cas. Après, vous pouvez avoir des besoins qui ne nécessite pas un traitement a chaque mètre parcourus, c'est à vous de voir.
  • ReportInterval :  Cette propriété est exprimée en milliseconde et définit l'interval minimum de notification du changement de position. En gros, vous êtes en voiture, ça change tout le temps, cela permet de définir l'intervalle de temps entre deux levé de l'événement de changement de position, ce qui permet un peu de calmer l'API si vous avez des traitements long et ainsi permettre d'avoir un intervalle de traitement raisonnable, ici je vous conseille de la laisser à sa valeur par défaut 0 sauf si vous avez des besoins particulier.

Donc si on devait initialiser cette classe en mode ultra précis cela donnerais ceci :

// Création de l'instance
Geolocator geolocator = new Geolocator();

// Définition des paramètres
geolocator.DesiredAccuracy = PositionAccuracy.High;
geolocator.DesiredAccuracyInMeters = 10;
geolocator.MovementThreshold = 0;
geolocator.ReportInterval = 16;

Nous avons donc créé notre instance, maintenant il faut savoir si le module est activé, pour cela on se branche sur l'événement "StatusChanged", il nous permet de savoir à tout moment si on est bien en détection de notre position, pour cela on écrit :

// On se branche a l'événement
geolocator.StatusChanged += OnGeolocatorStatusChanged;

// Définition de la fonction pour l'événement
private void OnGeolocatorStatusChanged(Geolocator sender, StatusChangedEventArgs args)
{
  switch(args.Status)
  {
    case(PositionStatus.Disabled):
      // Le senseur n'est pas actif
      break;

    case (PositionStatus.Initializing):
      // Le senseur s'initialise
      break;

    case (PositionStatus.NoData):
      // Le senseur ne peut pas encore envoyer de données
      // Cela se produit si vous apellez GetGeopositionAsync()
      // avant qu'il est des données de disponibles
      break;

    case (PositionStatus.NotAvailable):
      // Le senseur n'est pas disponible sur votre hardware
      break;

    case (PositionStatus.NotInitialized):
      // Le senseur n'est pas encore initialisé
      // Cela se produit si vous apellez GetGeopositionAsync()
      // avant que le senseur ne soit initialisé
      break;

    case (PositionStatus.Ready):
      // Senseur prêt, les données sont disponibles
      break;
  }
}

Il ne tient qu'à vous de traiter ces différents états pour que votre application réagisse proprement aux aléas de ce genre de senseur. Si vous n'avez pas de signal ou que le senseur est désactivé, c'est à vous de le faire savoir dans l'interface, c'est un aspect important du Design de votre application, ne pas laisser l'utilisateur dans l'incertitude, un pictogramme "pas de signal" permet souvent d'éviter que l'utilisateur trouve votre application inutilisable.

Maintenant il n'y a plus qu'à récupérer les changements de position. Pour cela deux solutions, soit on attend que la classe nous l'indique (ce sera donc avec un abonnement à un événement) ou manuellement quand vous le souhaitez (donc avec une fonction).

Méthode automatique (Event) :

C'est la façon la plus simple d'être informé d'un changement de position, en s'abonnant à l'événement "PositionChanged" vous êtes notifié du changement de position :

// On s'abonne à l'événement
geolocator.PositionChanged += OnGeolocatorPositionChanged;

// Définition de la fonction pour l'événement
private async void OnGeolocatorPositionChanged(Geolocator sender, PositionChangedEventArgs args)
{
  // Faire ici le traitement que vous voulez

  // Voici les informations les plus courantes  
  Geoposition pos = args.Position;

  double localAltitude = pos.Coordinate.Point.Position.Altitude;
  double localLatitude = pos.Coordinate.Point.Position.Latitude;
  double localLongitude = pos.Coordinate.Point.Position.Longitude;
}

Attention toutefois, cet événement ne s'exécute pas dans le ThreadUI, si vous devez affecter ces valeurs dans l'interface, n'oubliez pas de le faire dans le ThreadUI :

// On s'abonne à l'événement
geolocator.PositionChanged += OnGeolocatorPositionChanged;

// Définition de la fonction pour l'événement
private async void OnGeolocatorPositionChanged(Geolocator sender, PositionChangedEventArgs args)
{
  // On fait le traitement dans le ThreadUI
  await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {
    // Faire ici le traitement que vous voulez
  }
}

Voilà pour la méthode automatique.

Méthode manuelle (fonction) :

Dans ce cas précis, c'est nous qui allons demander à un moment que nous déterminons, les dernières coordonnées captées par le senseur. Pour se faire on fait appel à la fonction "GetGeopositionAsync()" :

// Appel à la fonction
Geoposition pos = await geolocator.GetGeopositionAsync();

// Récupération des informations
double localAltitude = pos.Coordinate.Point.Position.Altitude;
double localLatitude = pos.Coordinate.Point.Position.Latitude;
double localLongitude = pos.Coordinate.Point.Position.Longitude;

C'est une autre façon qui peut avoir beaucoup d'avantage (c'est un peu en fonction de l'utilisation que vous aurez du senseur). 

Voilà donc ce qu'il faut savoir pour bien commencer avec le senseur de géolocalisation.

Astuce : Tuile a 5 faces (Windows 8.1)

by Nicolas Calvi 22. août 2013 21:50

Voici une petite astuce qui peut être intéressante pour les développeurs Windows 8.1 (et par extension sur Windows 8 ça marche aussi). Comment faire pour créer une tuile ayant 5 Templates différents qui défilent et ce dans les trois tailles (carré, rectangle et large).

En fait ce n'est pas si sorcier que ça. Tout d'abord il déterminer les Templates que vous voulez afficher, pour cela vous pouvez les consulter tous les Templates sur MSDN, ce qui vous donnera une bonne vision de ce qu'il est possible de faire avec les tuiles sous Windows 8.1.

Vous avez trouvé de quoi vos tuiles vont être constituées ? Il n'y à plus qu'a la mettre en place.

Pour définir votre tuile dynamique, il faut générer un XML qui décrit ce que vous voulez afficher et les données qui les constituent. Pour cela deux moyen de créer ce XML. La première façon est de récupérer le template d'une tuile avec la fonction statique :

XmlDocument template = Windows.UI.NotificationsTileUpdateManager.GetTemplateContent(TileTemplateType type)

Vous pouvez donc récupérer les templates un par un et les modifier avec les API XML.

La seconde façon de faire est de constituer son XML en mode "texte" classique, de le charger dans une instance XmlDocument. Les deux techniques reviennent au même, donc après c'est une question de point de vue et de façon d'implémenter les choses.

Si l'on veut par exemple avoir 5 templates différents de tuile "wide", il faut créer un XML de cette forme (on peut bien sûr alterner les différents Templates, aucune nécessiter d'avoir 5 fois le même) :

<tile>
  <visual>
    <binding template="TileWideImage" branding="None">
      <image id="1" src="ms-appx:///Assets/Tiles/Tile00.png"/>
    </binding>
  </visual>
</tile>

Cette portion de XML permet de générer une face de notre tuile, elle sera de type "TileWideImage" et utilisera une ressource image qui est contenue dans mon applicatif. Vous remarquez l'attribut "branding" sur l'élément "binding", il permet de ne pas afficher le logo de l'application dans le coin inférieur gauche, il faut donc le mettre à "None" et c'est réglé.

Ceci est donc une des faces de notre tuile dynamique, si on veut donc avoir jusqu'a 5 faces, il faut générer 5 XML de ce type. Mais une fois générer, comment on les affecte ? Voici comment on procède :

// Considérons nos 5 XML correspondant
// aux 5 faces de notre tuile dynamique
XmlDocument tile1 = ... ;
XmlDocument tile2 = ... ;
XmlDocument tile3 = ... ;
XmlDocument tile4 = ... ;
XmlDocument tile5 = ... ;

// Création de l'instance qui permet
// d'affecter notre tuile dynamique
TileUpdater tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();

// On active le défilement de nos faces
tileUpdater.EnableNotificationQueue(true);

// On transforme nos XML en TileNotification
// qui représente une face convertie dans
// le système de tuile dynamique Windows 8
TileNotification tn1 = new TileNotification(tile1);
TileNotification tn2 = new TileNotification(tile2);
TileNotification tn3 = new TileNotification(tile3);
TileNotification tn4 = new TileNotification(tile4);
TileNotification tn5 = new TileNotification(tile5);

// Ensuite on supprime toutes les définitions
// de face déjà enregistrées pour cette tuile
tileUpdater.Clear();

// Pour finir on ajoute nos nouvelles définitions
tileUpdater.Update(tn1);
tileUpdater.Update(tn2);
tileUpdater.Update(tn3);
tileUpdater.Update(tn4);
tileUpdater.Update(tn5);

Bien sûr j'ai volontairement décomposé toutes les étapes pour bien comprendre ce qu'il se passe. Une fois que vous avez fait toutes ses opérations, votre tuile dynamique est prête et s'exécute sans soucis. 

Dans le cas décrit ci-dessus, cela ne crée qu'une tuile dynamique en mode "Wide". Pour gérer les autres tailles ("square" et "large"), il vous suffit d'ajouter d'autres Templates, Windows 8 fera le tri au moment de l'affichage.

ItemTemplate Windows 8

by Nicolas Calvi 29. avril 2013 16:00

Un petit post pour vous partager un petit soucis que j'ai rencontrer. Je me suis créer des "ProjectTemplates" et "ItemTemplates" pour mes projets MVVM sous Windows 8. Là où la création du "ProjectTemplate" se passe sans soucis, je me suis aperçu que mes "ItemTemplates" ne s'affichais pas quand je faisais "Nouvel élément".

En fait, Visual Studio 2012 exporte ceci dans son fichier "MyTemplate.vstemplate" :

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
  <TemplateData>
    <DefaultName>ItemView.xaml</DefaultName>
    <Name>Black Windows 8 Empty View</Name>
    <Description>Vue pour être utilisée dans une pattern MVVM Black Blog</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>__TemplateIcon.jpg</Icon>
    <PreviewImage>__PreviewImage.jpg</PreviewImage>
  </TemplateData>
  <TemplateContent>
    <References />
    <ProjectItem SubType="Designer" TargetFileName="$fileinputname$.xaml" ReplaceParameters="true">HomeView.xaml</ProjectItem>
    <ProjectItem SubType="Code" TargetFileName="$fileinputname$.xaml.cs" ReplaceParameters="true">HomeView.xaml.cs</ProjectItem>
  </TemplateContent>
</VSTemplate>

Or avec cette définition, l'ItemTemplate ne s'affiche pas dans ma liste. En cherchant un peu je me suis rendy compte qu'il manquait en fait un élément de définition dans le fichier. Si j'ajoute cette ligne dans le fichier de définition :

<TemplateGroupID>WinRT-Managed</TemplateGroupID>

Mon "ItemTemplate" s'affiche bien, il faut juste le savoir.