Comment récupérer la taille du clavier sur un téléphone 8.1 ?

Dans l’article précédent, je me suis concentré sur l’application bar, mais ce n’est pas la seule différence que l’on peut constater lorsque l’on exécute une application sur un téléphone 8.0 et sur un téléphone 8.1.

La taille du clavier change elle aussi, pour s’adapter aux phablets. Dans 6tag et 6sec par exemple, j’ai besoin de la taille de ce dernier pour afficher une fausse barre de suggestion au dessus du clavier, afin de proposer à l’utilisateur une liste de hashtags ou d’amis.

Il est techniquement impossible de récupérer la taille du clavier, je vais donc partager avec vous les chiffres que j’ai constaté.

La taille d’un clavier sous Windows Phone 8.0 et sur tous les téléphones 8.1 exception faite des phablets est de 339 pixels sans la barre de suggestion et 408 pixels avec celle-ci.

Sur les phablets 8.1, cette taille change et devient : 262px sans suggestions et 313px avec.

Peut-on automatiser cela ?

Bien sûr ! Et pour cela, nous allons réutiliser la classe PhoneSizeUtils que j’avais créé pour l’application bar :

Eh espérant que cela vous aidera !


namespace Huyn.Utils
{
  public class PhoneSizeUtils
    {
      static PhoneSizeUtils()
      {
          var appbar = new ApplicationBar(){Opacity = 0};
          ApplicationBarHeightStatic = appbar.DefaultSize;
          ApplicationBarMiniHeightStatic = appbar.MiniSize;
          KeyboardWithSuggestionsHeightStatic = 408;
          KeyboardHeightStatic = 339;

          var version81 = new Version(8, 1);
          if (version81 =< System.Environment.OSVersion.Version)
          {

              object sizeobject;
              if (DeviceExtendedProperties.TryGetValue("PhysicalScreenResolution", out sizeobject))
              {
                  var screenResolution = (Size)sizeobject;

                  object dpiobjet;
                  if (DeviceExtendedProperties.TryGetValue("RawDpiY", out dpiobjet))
                  {
                      var dpi = (double) dpiobjet;
                      if (dpi > 0)
                      {
                          var screenDiagonal = Math.Sqrt(Math.Pow(screenResolution.Width / dpi, 2) +
           Math.Pow(screenResolution.Height / dpi, 2));

                          if (screenDiagonal >= 5)
                          {
                              ApplicationBarOpacityHeightStatic = 56;
                              ApplicationBarOpacityMiniHeightStatic = appbar.MiniSize;
                              KeyboardWithSuggestionsHeightStatic = 313;
                              KeyboardHeightStatic = 262;
                              return;
                          }
                      }
                  }
              }
          }
    
              ApplicationBarOpacityHeightStatic = appbar.DefaultSize;
              ApplicationBarOpacityMiniHeightStatic = appbar.MiniSize;

          
      }

      public static double ApplicationBarHeightStatic;


      public static double ApplicationBarMiniHeightStatic;
      public static double ApplicationBarOpacityHeightStatic;
      public static double ApplicationBarOpacityMiniHeightStatic;
      public static double KeyboardWithSuggestionsHeightStatic;
      public static double KeyboardHeightStatic;


      public double ApplicationBarHeight
      {
          get { return ApplicationBarHeightStatic; }
      }

      public double ApplicationBarMiniHeight
      {
          get { return ApplicationBarMiniHeightStatic; }
      }

      public double ApplicationBarOpacityHeight
      {
          get { return ApplicationBarOpacityHeightStatic; }
      }

      public double ApplicationBarOpacityMiniHeight
      {
          get { return ApplicationBarOpacityMiniHeightStatic; }
      }

      public double KeyboardHeight
      {
          get { return KeyboardHeightStatic; }
      }

      public double KeyboardWithSuggestionsHeight
      {
          get { return KeyboardWithSuggestionsHeightStatic; }
      }

    }
}

Comment récupérer la taille de l’applicationBar sur un téléphone 8.1 ?

Jusqu’il y a peu de temps, il était très facile de récupérer la taille de l’applicationbar, il suffisait d’écrire : MonApplicationBar.DefaultSize ou MonApplicationBar.MinSize selon le mode d’affichage de notre applicationBar.

Mais depuis Windows Phone 8.1, ces deux propriétés sont buggués, enfin presque, les valeurs qu’elles retournent ne sont plus fiables, pour une raison assez simple : avec Windows Phone 8.1 et notamment pour être cohérent avec les applications WinRT, microsoft a fait le choix d’adapter l’application bar à la taille physique de l’écran de l’utilisateur, ainsi, une application bar sur un téléphone type phablet sera proportionnellement plus petite que sur un lumia 920, histoire de ne pas avoir une application bar qui fait 2 centimètres de haut. Dans les faits, une application bar fera 72px sur un téléphone classique et 56px sur une phablette.

Pour ne pas créer de glitch sur les application Silverlight 8.0 et garder le même layout, Microsoft a pris la décision d’ajouter une bande noire de 72px-56px=16px en bas de l’écran afin de compenser la diminution de l’application bar sur les phablettes : la taille de l’application reste donc la même, l’application bar est bien plus petite.

wp_ss_20140821_0002

On voit bien sur cet écran, l’application bar de 56px et la bande noir en dessous de 16px pour compenser la perte de taille de l’application bar.

Mais que retour MonApplicationBar.DefaultSize dans ce cas ? Bah 72px, ce qui est correct, la zone de mon application a bien diminué de 72px avec l’application bar et la bande noire.

Mais pourquoi y’a un problème si tout est correct ?

Parce que Microsoft n’a pas pensé à tous les cas ! Que se passe t’il si mon application bar est semi-transparente ?

Lorsqu’une application bar est semi-transparente, la zone de dessin de notre application passe dessous la barre, ce qui signifie que la seule utilité de connaitre la taille de l’application bar est de pouvoir afficher des contrôles juste au dessus de la barre et pour cela, il nous faut connaitre sa taille.

Un exemple d’utilisation :

wp_ss_20140821_0004

Mon application bar est semi transparente dans ce cas, à 99% (afin de ne pas afficher un rectangle noir durant les transitions de page), donc ma page va jusqu’en dessus de la barre. Pour afficher mon texte et ma barre de progression, il me faut donc connaitre sa taille.

Note importante : si votre application bar est semi-transparente, votre page à la taille de l’écran, le comportement ne change donc pas entre un device WP8.0 et WP8.1, pas besoin de bande noire pour compenser quelque chose.

Toutefois, si on appelle MonApplicationBar.DefaultSize, on a le résultat : 72px, même sur une phablette… or l’application bar fait que 56 pixels sur ces devices. Il est donc clair que si je fais confiance à cette propriété, mon texte et ma barre de progression seront affichés trop haut.

Que puis je faire alors ?

Rien… enfin, le SDK ne vous aidera pas sur le coup, la réponse officielle serait : passer sur Silverlight 8.1, mais ce n’est pas une bonne réponse.

Voici donc le workaround que j’utilise dans mes applications :

Premièrement, je teste si on est sur un téléphone 8.1 :

 


var version81 = new Version(8, 1);
if (version81 <= System.Environment.OSVersion.Version) { 

Si c’est le cas, j’essaie de récupérer le nombre exact de pixels de mon écran :

 object sizeobject; if (DeviceExtendedProperties.TryGetValue("PhysicalScreenResolution", out sizeobject)) { var screenResolution = (Size)sizeobject; 

ensuite, je tente de récupérer la densité de pixel de l’écran (nombre de pixels par pouces)

 object dpiobjet; if (DeviceExtendedProperties.TryGetValue("RawDpiY", out dpiobjet)) { var dpi = (double) dpiobjet; if (dpi > 0) {

grâce à ces deux données, je peux calculer la taille physique en pouce de la largeur et de la hauteur de mon écran :


var screenHeight=screenResolution.Height / dpi;

var screenWidth=screenResolution.Width / dpi;

On applique notre bon vieux pythagore et on en déduit la diagonale de l’écran :


var screenDiagonal = Math.Sqrt(Math.Pow(screenWidth, 2) + Math.Pow(screenHeight, 2));

Il suffit alors de tester si la diagonale de l’écran est supérieur ou égale à 5 pouces, taille que windows phone considère comme phablet :


if (screenDiagonal >= 5)

{

ApplicationBarOpacityHeightStatic = 56;

ApplicationBarOpacityMiniHeightStatic = appbar.MiniSize;

}

 

Voici donc le code complet :

 

namespace Huyn.Utils
{
  public class PhoneSizeUtils
    {
      static PhoneSizeUtils()
      {
          var appbar = new ApplicationBar(){Opacity = 0};
          ApplicationBarHeightStatic = appbar.DefaultSize;
          ApplicationBarMiniHeightStatic = appbar.MiniSize;
      
          var version81 = new Version(8, 1);
          if (version81 =< System.Environment.OSVersion.Version)
          {

              object sizeobject;
              if (DeviceExtendedProperties.TryGetValue("PhysicalScreenResolution", out sizeobject))
              {
                  var screenResolution = (Size)sizeobject;

                  object dpiobjet;
                  if (DeviceExtendedProperties.TryGetValue("RawDpiY", out dpiobjet))
                  {
                      var dpi = (double) dpiobjet;
                      if (dpi > 0)
                      {
                          var screenDiagonal = Math.Sqrt(Math.Pow(screenResolution.Width / dpi, 2) +
           Math.Pow(screenResolution.Height / dpi, 2));

                          if (screenDiagonal >= 5)
                          {
                              ApplicationBarOpacityHeightStatic = 56;
                              ApplicationBarOpacityMiniHeightStatic = appbar.MiniSize;
                              return;
                          }
                      }
                  }
              }
          }
    
              ApplicationBarOpacityHeightStatic = appbar.DefaultSize;
              ApplicationBarOpacityMiniHeightStatic = appbar.MiniSize;

          
      }

      public static double ApplicationBarHeightStatic;


      public static double ApplicationBarMiniHeightStatic;
      public static double ApplicationBarOpacityHeightStatic;
      public static double ApplicationBarOpacityMiniHeightStatic;
  

      public double ApplicationBarHeight
      {
          get { return ApplicationBarHeightStatic; }
      }

      public double ApplicationBarMiniHeight
      {
          get { return ApplicationBarMiniHeightStatic; }
      }

      public double ApplicationBarOpacityHeight
      {
          get { return ApplicationBarOpacityHeightStatic; }
      }

      public double ApplicationBarOpacityMiniHeight
      {
          get { return ApplicationBarOpacityMiniHeightStatic; }
      }

    }
}

Suite de l’article pour récupérer la taille du clavier cette fois : cliquez ici.

WinRT sur Windows phone : ci-git CameraCaptureTask

De plus en plus de Windows Phone sont en train de se mettre à jour vers Windows Phone 8.1, il est donc temps de parler un peu de WinRT, qui va remplacer au fur et à mesure les applications Silverlight que l’on connaissait jusqu’à là.
L’apport de WinRT permet notamment de créer des applications fonctionnant à la fois sur Windows Phone et sur Windows, c’est ce qu’on appelle les universal apps. Néanmoins, tout changement apporte ses avantages mais aussi ses inconvénients.

Parmi les inconvénients, je pense notamment à la disparition du CameraCaptureTask, task bien pratique dans le passé pour ajouter une fonctionnalité prise de photo dans son application sans devoir redévelopper l’ensemble des fonctionnalités d’une application photo comme la gestion du focus, de l’exposition, etc…

Sous Silverlight, il suffisait d’écrire :

var cameraCaptureTask = new CameraCaptureTask();
cameraCaptureTask.Show();

Pas vraiment difficile ! Mais quid de WinRT ?

La tâche n’existe tout simplement pas, inutile de la chercher dans la documentation, cette dernière vous conseillera plutôt de rédevelopper entièrement cette fonctionnalité avec les API MediaCapture qui sont :

  • pas vraiment super bien documenté : même pas de sample sur MSDN
  • buggués : des problèmes dans les photos sous 8.1, un crash à l’initialisation sous 8.1.1

Autant dire qu’on nous aide pas vraiment pour le coup !

Mais le CameraCaptureUI ?

Uniquement dispo sous Windows et non disponible sous Windows Phone… On a tendance à oublier que les Universal Apps n’englobent pas toutes les API windows…

Mais alors que puis je faire ?

Deux choix s’ouvrent à toi : prendre ton mal en patience ou utiliser un workaround !

Je ne suis pas patient !

Voici donc un workaround pour toi. L’astuce est d’utiliser un FileOpenPicker, objet qui sert normalement à sélectionner un fichier à ouvrir dans votre téléphone.

Commençons par créer l’instance


var openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.PicturesLibrary,

};
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");

et lançons le picker


openPicker.PickSingleFileAndContinue();

wp_ss_20140811_0001[1]

L’interface de sélection d’images s’affichera alors, vous remarquerez en bas un bouton caméra, bouton qui vous permettra de… lancer l’application camera !

Conclusion

Il n’est pas possible de retrouver exactement le même comportement qu’avec Silverlight, on ajoute ici une étape intermédiaire, mais faute de mieux, c’est toujours ça de pris !

 

 

Vérifier qu’un fichier existe dans le local storage en universal app

Lorsque l’on passe de Silverlight à WinRT on peut ressentir quelques manques, notamment lorsque l’on essaie de vérifier si un fichier existe ou non dans l’isolated/local storage.

En Silverlight, c’était très simple :


IsolatedStorageFile.FileExists(...)

Mais comment faire cela en WinRT ? Bah ce n’est pas si simple que cela.

WinRT dans sa globalité ne propose pas une telle fonction, il faut contourner le problème en essayant de récupérer les données du fichiers.

Dans sa version Windows 8.1, WinRT propose la fonction TryGetItemAsync, plutôt simple d’utilisation, il nous suffira de tester si la fonction retourne une référence nulle ou pas :


return await ApplicationData.Current.LocalFolderTryGetItemAsync(filepath) !=null;

Oui mais…

On compile le tout et on se rend compte assez rapidement qu’un problème existe avec Windows Phone 8.1. Bien que sorti après, le SDK de l’OS mobile ne propose pas une telle fonction apparu avec Windows 8.1. Il faut donc faire autrement utiliser une astuce datant de Windows 8.

On va utiliser la fonction GetFileAsync, cette fonction retourne les données du fichier si celui-ci existe et lance une exception si ce dernier n’existe pas.


try
{
await ApplicationData.Current.LocalFolder.GetFileAsync(filepath);
return true;
}
catch
{
return false;
}

Et donc pour mon code Universal ?

On pourrait utiliser le second code pour les deux plateformes, toutefois, la solution numéro 1 est plus élégante, on va donc prendre la première solution pour Windows 8.1 et la seconde pour Windows Phone 8.1 et utiliser les directives de précompilation pour séparer les deux implémentations :


public static async Task<bool> FileExists(this StorageFolder folder, String filepath)
{

#if WINDOWS_APP
return await folder.TryGetItemAsync(filepath) !=null;
#endif

#if WINDOWS_PHONE_APP

try
{
await folder.GetFileAsync(filepath);
return true;
}
catch
{
return false;
}
#endif
}

Voilà !

J’ai un problème avec mon string

Je tiens tout d’abord à m’excuser pour ce titre, mais difficile de trouver plus juste : j’ai vraiment eu un problème avec mes strings et plus précisément en voulant porter le code d’une de mes applications Silverlight Phone 8 en Universal/WinRT.

Pour mutualiser mes Path, j’ai pris l’habitude de stocker la Geometry (le contenu de la propriété Path.Data) dans un String et non un objet Geometry car il faut pour cela convertir les données (sous la forme d’une string) en structure XAML, ce qui vous prendra bien quelques heures et vous posera quelques soucis quand vous voudrez l’éditer. A choisir, entre un copier-coller et 1 heure à recréer la structure XAML, je trouve la première solution plus commode.

Certes, revenons à notre soucis : mon problème de string !

Dans un fichier ressource de mon application, je stocke donc les Path sous la forme de string, de la façon suivante :


ResourceDictionary

xmlns:system="clr-namespace:System;assembly=mscorlib"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

<system:String x:Key="NextPath">M205.5,310.5 L214.75,352.5 L272,356 L214.25,361.5 L205.5,403.75 L301.25,356.5 z</system:String>

...

Aucun soucis à noter, je fais cela depuis des années, tout fonctionne.

En voulant porter mon code en WinRT, j’ai bêtement copier coller ce dernier code dans un fichier ressource de mon projet WinRT et j’ai laissé le bien nommé ‘resharper’ solutionner le problème de namespace pour “system”.

Après son intervention, voici donc ce que j’avais :


ResourceDictionary

xmlns:system="using:System"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

<system:String x:Key="NextPath">M205.5,310.5 L214.75,352.5 L272,356 L214.25,361.5 L205.5,403.75 L301.25,356.5 z</system:String>

...

Le type String est bien dans le namespace System, tout devrait fonctionner. Je compile donc…

Et là… catastrophe, mon projet contenant 2 lignes génère déjà 2 erreurs : YOU FAIL

 

Syntax Error Found in XBF generation

Syntax Error Found in XBF generation

Bon… soyons honnête, c’est le code erreur le plus explicite jamais vu… XBF étant le format binaire d’un fichier XAML…

De toute évidence, mon problème vient des strings… Avant de crier et pleurer mon malheur “Silverlight c’était mieux avant”, je fais quelques recherches et je me rend très rapidement compte qu’il n’y a pas qu’un seul type String, y’en a deux exactement en XAML.

Le System.String est un type .Net qui ne doit plus être utilisé dans un fichier XAML en fait, il faut utiliser un nouvel objet XAML, nommé toujours String mais du namespace X (le même namespace que x:Name, x:Key, etc…). On ne parle plus vraiment d’un objet .Net avec toute sa complexité mais d’un simple helper XAML qui sera utilisé pour générer le XBF.

Conclusion, lorsque vous portez un String, si c’est pour du visuel, allez dans le X

Pourquoi certains émulateurs Windows Phone ne se lancent pas

… et comment contourner le problème.

Windows Phone 8.1 SDK vient de sortir en release candidate, mais souffre d’un petit bug un peu embêtant : seul l’émulateur WVGA fonctionne sur les Windows français.

Capture

 

“The specified diagonal screen size is not valid.”

Pourquoi ?

La globalisation d’une app est un sujet sérieux et parfois on se loupe. C’est malheureusement le cas ici. L’émulateur essaie de lire un fichier de configuration avec des nombres au format anglais (virgule pour séparer les milliers, point pour séparer les décimales). Sauf que pour une raison inconnue, l’émulateur tente de lire ce nombre avec les séparateurs configurés dans votre windows. Donc pour nous français : espace pour les milliers, virgule pour les décimales.

Alors comment faire ?

Pour contourner ce problème, il suffit de changer la configuration des séparateurs dans votre windows. Soyons honnête, c’est un contournement du problème et non un correctif.

Pour cela, allez dans le panneau de configuration et sélectionnez “Modifier les formats de date, d’heure ou de nombre”

Capture

 

Cliquez ensuite sur “paramètres supplémentaires”

Capture

 

et enfin changez le séparateur décimal en mettant “.”

Capture

 

et comme dirait Joe Belfiore : “voilà” !

Les effets secondaires

Attention, vous avez changez la façon dont windows interprétera les données numériques, vous pourrez rencontrer des soucis dans vos feuilles excel par exemple ou vos logiciels de comptabilité, mais en attendant mieux, c’est la seule solution disponible. N’oublions pas toutefois qu’il s’agit d’une version RC et non RTM.

CloudSix API: add cloud storage support to your app in a nutshell

What is CloudSix?

CloudSix is a set of full client apps for cloud storage services.

The current version only support Dropbox but other cloud storage services will be added as soon as possible, including: OneDrive, Google Drive, Box.com and Mega.co.

But it’s not only a full client app, it’s also an API that can be used by other apps.

What features are available for third-party apps?

Two features can be easily added to your application: a file picker and a file saver.

File Picker

CloudSix provides a way for other apps to select a file on cloud storages. No need to manage authentification, file browsing, file manager, file cache, retrieve data, etc… just with 2 lines of code you can ask CloudSix to do this task for you. Moreover, you can add some filters like “show me only jpg and png files” or “show me only mp4 file < 2Mb”, once launched, CloudSix will only display the kind of files you want and will relaunch your app with the selected file.

File Saver

You can also ask to CloudSix to save a file for you. Once again, just with 2 lines of code. You only need to provide the stream and a file name (the user will be able to change it if he wants)

Do I need to update my app when other cloud storage services will be added?

No, the SDK will not change, support of other cloud services will be automatically

How can I add CloudSix feature to my app?

First you need to add the cloudSix Connector using nuget, to do it, search “CloudSixConnector” in your package manager or write the following line in the package console:

Install-Package CloudSixConnector

The library is very small, only 10kb, no memory or performance impact to your app.

How can I use the file picker?

First you need to create a custom file extension to communicate with CloudSix.

Open your WMAppManifest.xml file and add the following line to the Extensions node (replace MyAppName by your app name):

<FileTypeAssociation Name="Associated with CloudSix" NavUriFragment="fileToken=%s" TaskID="_default">
<SupportedFileTypes>
<FileType ContentType="application/cloudsix2MyAppName">.cloudsix2MyAppName</FileType>
</SupportedFileTypes>
</FileTypeAssociation>

This file protocol will be used to pass the file data from cloudsix to your app.

Create a CloudSixPicker object and give it the file protocol you previously set.

var launcher = new CloudSixPicker("cloudsix2MyAppName");

To manage and trace your request, you can add a token, re-sent with the file data (‘.’ will be automatically replaced by ‘_’)

launcher.Token = "FromCloudSix";

You can also add a caption, displayed in the cloudsix header (no need to add your app name, it will be automatically displayed):

launcher.Caption = "select a file (image or video <1Mb)";

cloudsixcaption

Then you can select the file types you want (file extension (case insensitive and maximum file size):

launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "jpg" });
launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "png" });
launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "mp4", MaxFileSizeInKb = 1000 });

Here is the full example:

var launcher = new CloudSixPicker("cloudsix2sixsnap");
launcher.Caption = "select a file (image or video <1Mb)";
launcher.Token = "FromCloudSix";
launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "jpg" });
launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "png" });
launcher.FileExtensions.Add(new CloudSixFileExtension() { Extension = "mp4", MaxFileSizeInKb = 1000 });
launcher.Show();

To get the cloudsix answer, you need to add a UriMapper to your app

//in App.xaml.cs
RootFrame.UriMapper = new UriMapper();
internal class UriMapper : UriMapperBase
{

public  override Uri MapUri(Uri uri)
{

if (uri.StartsWith("/FileTypeAssociation"))
{
// Get the file ID (after "fileToken=").
int fileIDIndex = tempUri.IndexOf("fileToken=") + 10;
string fileID = tempUri.Substring(fileIDIndex);

var fileinfo=CloudSixPicker.GetAnswer(fileID);

//display the right page

return new Uri("VideoPlayerPage.xaml?fileid="+HttpUtility.UrlEncode(fileID),UriKind.Relative);

}
return uri;
}
}

and in VideoPlayerPage.xaml.cs

var file=await fileinfo.CopySharedFileAsync(NavigationContext.QueryString["fileid"]);

 

How can I use the file saver?

To save a file, it’s very simple, just create a CloudSixSaver object with the stream you want to save as well as a name file (can be altered by the user).

var saver = new CloudSixSaver("Zelda.gba", myfilestream);
await saver.Launch();

You can add a callback if you want, just add a protocol extension:

<Protocol Name="yourappname" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />-->
saver.AppCustomExtension = "yourappname";

The protocol extension will be called by CloudSix once the file saved with the following format:

yourappname:cloudsiximportresult?result=saved&originfilename=[the filename set by your app]&filepath=[the path where the file has been saved]

Warning: the user can change the filename, filepath takes into account the user filename whereas originfilename takes into account the filename the third party app set (to trace result).

How much it costs ?

Nothing, the app is available for free and will remain free.

 

Do I need a dropbox developer key?

No. All actions are made by the CloudSix app. The library doesn’t contain dropbox API or dropbox references, it only contains some codes to launch CloudSix with parameters. So to summarize, using the file picker and file saver features only launches CloudSix with some parameters, nothing else. One launched only CloudSix runs and let users to select folder, make actions, etc… So CloudSix is the only responsable of all transactions.

Héritage et désérialisation Json

Pour ma nouvelle application fictive, que l’on va nommer 6Zoo, je récupère depuis le serveur une liste d’animaux sous la forme d’un json.

Mon application représente les animaux sous différentes formes :


class Animal
{
public String Name { get; set; }

public String Type { get; set; }

}

class Bird:Animal
{
public double WingSpan { get; set; }
}

class Fish : Animal
{
public String WaterType { get; set; }
}

Voici un exemple de json que je reçois :

{“result”:[{"type":"fish","name":"sardine","watertype":"salt"},{"type":"bird","name":"pigeon","wingspan":12},{"type":"fish","name":"shark","watertype":"salt"}]}

 

Je crée alors la classe suivante pour désérialiser le contenu :


public class Result
{

[JsonProperty("result")]
public Animal[] Result { get; set; }
}

Or, avec json.net par exemple, je ne vais pas récupérer une liste d’objets Bird et Fish, mais une liste d’Animal. Je perd donc des informations (WingSpan et WaterType) et les pouvoirs de l’héritage.

Plusieurs solutions existent :

  • désérialiser à la main, efficace, mais prend du temps et pas forcément viable dans le temps si les modèles évoluent
  • créer une classe contenant l’ensemble des propriétés des types enfants puis refaire une passe pour créer les bon types, soyons clair, c’est du bricolage.

Désérialiser les types enfants en une seule passe

Pour désérialiser les types enfants afin d’avoir une liste de Bird et de Fish de façon efficace, il existe un moyen assez peu contenu via Json.net.

On va commencer par créer un JsonConverter qui va nous nous permettre de prendre la main sur une partie de la désérialisation, dans notre cas, la désérialisation des objets de type Animal.

Afin d’être générique et pouvoir réutiliser ce principe sur d’autres objets, on va créer deux classes : JsonClassChooserConverter<T> qui va se charger de généraliser le traitement sur les classes enfants et AnimalConverter qui va en hériter et qui va spécialiser le traitement sur les classes enfants d’Animal :


public abstract class JsonClassChooserConverter<T> : JsonConverter
{

protected abstract T ComputeType( JObject jObject);

public override bool CanConvert(Type objectType)
{
return false;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{

return null;

}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}

On va commencer par implémenter CanConverter, pour cela, assez simple, on vérifie que le type est bien compatible:


public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}

On passe maintenant à ReadJson. On va récupérer l’objet JObject qui va nous aider à désérialiser le contenu par la suite, on va ensuite appeler ComputeType afin de récupérer le type de l’objet (Fish ou Bird) enfin on va désérialiser le contenu selon le type récupéré.


public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{

if (reader.TokenType == JsonToken.Null) return null;
JObject jObject = JObject.Load(reader);
T target = ComputeType(jObject);
serializer.Populate(jObject.CreateReader(), target);

return target;
}

Il nous reste plus qu’à créer la classe AnimalConverter héritant de la classe précédente et implémentant la méthode ComputeType.


public class AnimalConverter : JsonClassChooserConverter<Animal>
{
protected override Meta ComputeType(JObject jObject)
{
if (jObject["type"] != null && jObject["type"].Value<string>()=="bird")
{
return new Bird();
}

else
{
return new Fish();
}
}
}

Comment utiliser notre convertisseur ?

Pour utiliser notre convertisseur, on a deux solutions. Soit on le définit au niveau global (au niveau du deserializer), soit on le définit au niveau local (une propriété bien précise).

Pour l’utiliser au niveau global :


var result = JsonConvert.DeserializeObject<Result>(json, new AnimalConverter());

Notre méthode CanConvert sera alors appelé pour chaque propriété afin de vérifier si notre converter peut ou pas désérialiser le contenu.

Pour être plus efficace, on peut indiquer vouloir utiliser ce converter que pour certaines propriétés.

Pour une liste :


[JsonProperty(PropertyName = "animaux",ItemConverterType = typeof(AnimalConverter))]
public List<Animal> Animaux{ get; set; }

et pour un objet simple :


[JsonConverter(typeof(AnimalConverter))]

public Animal MonAnimalPrefere{get;set;}

Conclusion

Voici une méthode très efficace pour dé-sérialiser directement vos objets sans refaire une seconde passe afin de gérer les héritages, en espérant que cela vous aidera !

A noter,

public String Type { get; set; }

ne sert plus à rien maintenant que nos données sont typées, vous pouvez donc le supprimer de votre modèle

ContextMenu et LongListSelector/Listbox : les ennemies

Dans une des mes applications (TVShow), j’utilise un ContextMenu dans une LongListSelector, permettant notamment d’indiquer comme vu, tous les épisodes précédents. Mais j’ai remarqué un petit problème amusant.

Une LongListSelector est virtualisée, cela signifie que les éléments visuels de votre liste vous servir pour plusieurs items de votre source. Exemple, l’élément visuel X va afficher l’épisode 2×23, puis en scrollant, ce même élément va être recyclé pour affiché l’épisode 1×18. Cette technique employée par le longlistselector va vous permettre de gagner grandement en performance.

Mais quel est le soucis avec les ContextMenu ?

Avant d’aller plus loin, regardons à quoi ressemble la méthode appelée lorsque l’on utilise le ContextMenu :


private void MenuPreviousSeen_Click(object sender, RoutedEventArgs e)
{
var frameworkElement = sender as FrameworkElement;//mon menu item
var epi = frameworkElement.DataContext as Models.Episode;
ActionToDo(epi);
}

Le code est correct, rien à redire.

Mais il pose quand même problème, à cause d’un bug du ContextMenu. Si par malheur, l’utilisateur a utilisé le menu pour l’épisode 2×23, puis plus tard il l’utilise encore pour l’épisode 1×18, le DataContext du ContextMenu ne sera pas rafraîchi et il utilisera toujours celui de l’épisode 2×23, le ContextMenu ayant la bonne idée de s’initialiser à la première utilisation et ne jamais se rafraîchir.

Par conséquent, en essayant de faire une action sur l’épisode 1×18, je vais en faite faire une action sur l’épisode 2×23 sans m’en rendre compte, pensant que le DataContext est bon.

A noter, vous aurez exactement le même soucis avec les Listbox virtualisées.

Solution ?

La solution va consister à forcer le recalcul du context, pour cela, il suffit d’écrire :


<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu Background="White" IsZoomEnabled="False" Unloaded="ContextMenu_Unload">
<toolkit:MenuItem Click="MenuSeasonSeen_Click">
<toolkit:MenuItem.Header>
<TextBlock TextWrapping="Wrap" Text="{Binding Localizedresources.MarkAsSeen, Source={StaticResource LocalizedStrings}}" />
</toolkit:MenuItem.Header>
</toolkit:MenuItem>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>

et côté c#

private void ContextMenu_Unload(object sender, RoutedEventArgs e)
{
Microsoft.Phone.Controls.ContextMenu menu = (sender as Microsoft.Phone.Controls.ContextMenu);
menu.ClearValue(FrameworkElement.DataContextProperty);
}

Et voilà, votre DataContext sera bien réinitialisé à chaque utilisation du contextMenu !

émuler l’émulateur 1080p

Depuis l’annonce du lumia 1520, le nouveau windows phone disposant d’un écran 1080p, de nombreux développeurs attentent l’arrivée d’un nouvel émulateur pour le SDK windows phone. Cette attente est d’autant plus compréhensible, surtout suite aux différents articles provenant de microsoft et nokia sur l’adaptation de l’affichage de son application pour les grands écrans comme le lumia 1520. http://blogs.windows.com/windows_phone/b/wpdev/archive/2013/11/22/taking-advantage-of-large-screen-windows-phones.aspx ou encore http://developer.nokia.com/Resources/Library/Lumia/#!optimising-for-large-screen-phones/optimising-layout-for-big-screens.html

Mais vous faites peut-être une erreur

En effet, lorsque l’on parle d’adaptation du design selon la taille de l’écran, on ne parle pas de sa définition (720p, 1080p, etc…) mais de sa taille PHYSIQUE ! Pour faire simple, le lumia 1320 et le HTC 8X par exemple partagent la même définition d’écran (le nombre de pixels) : 720p, toutefois, on voudra afficher plus d’information sur le lumia que sur l’htc.

Alors quid d’un émulateur ?

Bah il est totalement inutile, le lumia 1520 par exemple, malgré sa définition de 1080p est considéré par le SDK comme un écran 720p (App.Current.Host.Content.ScaleFactor== 1.6 et non 2.25), les seules différences sont 3 valeurs de DeviceExtendedProperties nouvellement disponibles :

  • PhysicalScreenResolution : la définition effective de l’écran (1920*1080 pour le 1520)
  • RawDpiX et RawDpiY : le scalefactor horizontal et vertical en dpi (le nombre de pixels physiques par pource)

Et c’est tout. Les deux dernières propriétés sont très intéressantes, pour la première fois, on ne parle plus de définitions mais de taille physique d’écran, combien ai je de pixels sur X centimètres. Car au final c’est cela qui nous intéresse, il suffit de multiplier RawDpiX/RawDpiY par  PhysicalScreenResolution  et on retrouve la taille physique de mon écran en pouces (facilement convertible en centimètres pour ceux qui préfèrent). Par conséquent, il va être très compliquer de fournir un émulateur pour chaque nouveau téléphone sorti, car on n’est plus limité aux trois définitions (maintenant 4), mais aussi à la taille physique de l’écran du téléphone, un gros n’importe quoi à venir si on continue sur la lancée un émulateur pour chaque besoin.

Mais comment je peux tester ?

C’est là que j’interviens, j’ai développé un (tout) petit helper qui va vous permettre de simuler le lumia 1520 sur l’émulateur 720p (pour rappel : le lumia 1520 est considéré par le système comme un 720p). Je vais juste me contenter de redéfinir DeviceExtendedProperties via une classe nommée DeviceExtendedPropertiesExt, qui lorsque la propriété ActivateFake1080p sera mise à true retournera les nouvelles valeurs du 1520. On commence par redéfinir la méthode GetValue:

   public static object GetValue(string propertyName)
        {
            if (ActivateFake1080p)
            {
                switch (propertyName)
                {
                    case "PhysicalScreenResolution":
                        return new Size(1080, 1920);
                    case "RawDpiX":
                    case "RawDpiY":
                        return 368d;
                }
            }
            return GetValue(propertyName);
        }

puis la méthode TryGetValue

 

 public static bool TryGetValue(string propertyName, out object propertyValue)
        {
            if (ActivateFake1080p)
            {
                try
                {
                    propertyValue = GetValue(propertyName);
                    return true;
                }
                catch
                {
                    propertyValue = null;
                    return false;
                }
            }
            return DeviceExtendedProperties.TryGetValue(propertyName, out propertyValue);
        }

Pour l’utiliser c’est simple, remplacez toutes vos références DeviceExtendedProperties en DeviceExtendedPropertiesExt et activez selon votre souhait la propriété ActivateFake1080p (attention à ne pas l’activer en production)

 
  
using Microsoft.Phone.Info;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace FakeEmulator1080p
{
    public class DeviceExtendedPropertiesExt
    {
        // Summary:
        //     Activate or not the fake 1080 data.
        //
        public static bool ActivateFake1080p { get; set; }


        // Summary:
        //     Retrieves an object representing the specified device property.
        //
        // Parameters:
        //   propertyName:
        //     The name of the device property to be retrieved. Property names are case-sensitive.
        //
        // Returns:
        //     Returns System.Object.
        public static object GetValue(string propertyName)
        {
            if (ActivateFake1080p)
            {
                switch (propertyName)
                {
                    case "PhysicalScreenResolution":
                        return new Size(1080, 1920);
                    case "RawDpiX":
                    case "RawDpiY":
                        return 368d;
                }
            }
            return GetValue(propertyName);
        }
        //
        // Summary:
        //     Retrieves an object representing the specified device property.
        //
        // Parameters:
        //   propertyName:
        //     The name of the device property to be retrieved. Property names are case-sensitive.
        //
        //   propertyValue:
        //     The output parameter object in which the value of the device property is
        //     stored.
        //
        // Returns:
        //     Returns System.Boolean.
        public static bool TryGetValue(string propertyName, out object propertyValue)
        {
            if (ActivateFake1080p)
            {
                try
                {
                    propertyValue = GetValue(propertyName);
                    return true;
                }
                catch
                {
                    propertyValue = null;
                    return false;
                }
            }
            return DeviceExtendedProperties.TryGetValue(propertyName, out propertyValue);
        }


    }
}

 

Retrouvez le code ainsi qu’un exemple ici : http://www.rudyhuyn.com/blog/wp-content/uploads/2013/11/FakeEmulator1080p.zip