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

Convertir une string RGB en objet Color

Il est parfois utile d’avoir la possibilité de convertir un code couleur en objet Color afin de pouvoir colorier des éléments de l’interface selon des données distantes. C’est ce que je fais par exemple dans la partie explorer de 6sec où les couleurs de chaque mot clé proviennent du serveur avec utilisé pour la génération de la live tile et la couleur de l’application bar par exemple.

Quelques rappels pour les débutants

Si vous souhaitez juste affecter la couleur d’un contrôle (par exemple un foreground ou un background), pas besoin de vous prendre la tête, faites juste un “{Binding MaCouleurEnString}” le moteur xaml et de binding s’occupera de parser tout cela.

Dans la suite de l’article, on va plutôt parler de solutions pour récupérer la couleur afin par exemple :

  • de faire des traitements dessus (calculer la couleur inverse, des nuances de couleurs)
  • affecter le fond d’une iconic tile
  • ou encore changer la couleur d’une appbar

et encore, pour le dernier cas on peut se contenter d’une dependency property et d’un binding

Alors comment faire cela le plus efficacement possible ?

Puisque l’on parle de performance, il est hors de question d’utiliser un XAMLReader, trop lourd pour notre utilisation et limité au thread UI.

Dans une première version, j’utilisais un petit parser qui récupérait la string, la découpait, puis calculait chacune des composantes R, G, B, A.


public Color HexToColor(string hex)
{
//on supprime le #
hex = hex.Replace("#", "");

byte a = 255;
byte r = 255;
byte g = 255;
byte b = 255;

int start = 0;

//gestion des couleurs ARGB
if (hex.Length == 8)
{
a = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
start = 2;
}

//récupération des composantes R,G, B
r = byte.Parse(hex.Substring(start, 2), System.Globalization.NumberStyles.HexNumber);
g = byte.Parse(hex.Substring(start + 2, 2), System.Globalization.NumberStyles.HexNumber);
b = byte.Parse(hex.Substring(start + 4, 2), System.Globalization.NumberStyles.HexNumber);

return Color.FromArgb(a, r, g, b);
}

Ca fonctionne, mais on peut faire beaucoup mieux en considérant la string comme un seul nombre hexadécimal et non 3 ou 4.


public Color HexToColor(string hex)
{
//on supprime le #
hex = hex.Replace("#", "");
var val=Int32.Parse(hex, System.Globalization.NumberStyles.HexNumber);

byte alpha = 255;
//gestion des couleurs ARGB
if (hex.Length == 8)
{
alpha =(byte)( val >> 24);
}
return Color.FromArgb(alpha,(byte)((val >> 16) & 0xff),(byte)( (val >> 8) & 0xff), (byte)(val & 0xff));
}

Et en .Net ?

Color dispose d’une méthode Color.FromArgb très peu connu uniquement en .net (pas en silverlight et donc windows phone) : Color.FromArgb(int)

http://msdn.microsoft.com/fr-fr/library/2zys7833.aspx

Pas besoin de découper R,G, B ou A, il suffit juste de donner la valeur du nombre hexadécimal.

Pour cela, rien de plus simple :


Color.FromArgb(Int32.Parse(hexcolor.Replace("#", ""), System.Globalization.NumberStyles.HexNumber));

Voilà !!!

Bonus

Allez, en bonus, voyons comment gérer les couleurs html comme “red” ou “blue” en .Net


System.Drawing.ColorTranslator.FromHtml(xCol);

Update: Toujours en .Net, on peut utiliser ColorConverter.ConvertFromString(monstring) qui a l’avantage de gérer à la fois les codes rgb mais aussi les couleurs nommées (merci Benjamin !)

 

 

 

Nouvelle propriété de GDR3 : PowerSavingModeEnabled

GDR3 apporte peu de nouveautés au niveau SDK, toutefois, quelques propriétés ont été ajoutés. Comme le SDK n’a pas changé, on ne peut y avoir accès qu’en utilisant la reflection.

Une de ces nouvelles propriétés est PowerSavingModeEnabled, propriété permettant de savoir si l’utilisateur à activé le mode “économie d’énergie”. Attention, on parle bien d’activation et non si le téléphone est en train d’utiliser le mode économie qui est représenté par la propriété PowerSavingModeEnabled (http://msdn.microsoft.com/en-US/library/windowsphone/develop/windows.phone.system.power.powermanager.powersavingmode(v=vs.105).aspx).

Accéder à cette méthode

Première chose à faire, tester si GDR3 est bien sur le téléphone :


if(Environment.OSVersion.Version>= new Version(8, 0, 10492))

ensuite il suffit d’utiliser la reflection pour accéder à cette nouvelle propriété :


var props = (bool)typeof (Windows.Phone.System.Power.PowerManager).GetProperty("PowerSavingModeEnabled").GetValue(null,null);

Conclusion

J’avoue, ce n’est pas la propriété la plus utile, mais elle saura satisfaire tous les créateurs d’app “tuile raccourci vers les settings”.

Modulo dans tous ses états

Une de mes autres passions est les mathématiques, outil indispensable à l’informaticien .

Nous allons nous pencher aujourd’hui sur un élément assez controversé dans l’algorithmique et dans les mathématiques : le modulo.

Classiquement, on sait qu’un modulo est le reste de la division de deux nombres. Pour l’instant, on a juste, mais comment doit se comporter notre modulo quand on a des nombres négatifs ?

Imaginons que nous souhaitons faire la rotation d’une image selon un angle donné en argument. La fonction rotation de la librairie Imaging SDK par exemple, prend en paramètre uniquement des nombres compris entre 0 et 360. Solution : utiliser un modulo.

angle % 360

Si nous avons en entrée l’angle de 375°, un petit modulo 360 et nous passons à 15°, logique et normal. Maintenant imaginons que l’on passe l’angle de -15° en paramètre. Nous allons nous attendre à avoir 345°, mais non… le modulo nous retourne -15. Alors bug ou pas ?

Les modulos

En fait non, la subtilité est qu’il n’y a pas un type de modulo mais trois :

  • le modulo entier qui retourne un nombre entre 0 et le diviseur (si celui-ci est négatif, le résultat sera négatif)
  • le modulo tronqué qui retourne un nombre du même signe que le dividende
  • le modulo euclidien qui retourne toujours un nombre positif

Au niveau algorithmique, à titre personnel, je préfère le modulo euclidien mais dans le cas du framework .Net,c’est un modulo tronqué, dommage pour nous, c’est le seul qui ne respecte pas la loi modulaire : (x+n) mod n = x mod n

En effet,

-5 % 11 == -5

(-5+11) % 11 = 6

Le modulo euclidien en .Net

Dans notre exemple précédent, nous souhaitons utiliser un modulo euclidien, qui retourne toujours un résultat positif.

Voici une implémentation :

a<0?((a % n) + n) % n:a%n

Un peu plus lourd, mais plutôt efficace.

Et C++?

Jusqu’à C++/11, le comportement du modulo negatif n’était pas indiqué (et de façon explicite), c’était au compilateur/processeur de choisir, pas top pour la portabilité.

The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined; otherwise (a/b)*b + a%b is equal to a. If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined.

 

Ceci dit, dans la majorité des cas, le modulo suivait les règles héritées par le Fortran, c’est à dire, le modulo tronqué.

Avec C++/11 les choses sont devenus plus clair et le choix a été fait d’utiliser obligatoirement le modulo tronqué.

The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined. For integral operands the / operator yields the algebraic quotient with any fractional part discarded;81 if the quotient a/b is representable in the type of the result, (a/b)*b + a%b is equal to a.

 

Bilan

Attention aux a priori et vérifiez toujours ce que les fonctions .Net retournent