16/08/2013

[Blog] Retour sur cette période estivale

Bonjour à toutes et à tous,
je pars en vacances donc petit bilan...

Les Articles

Bon cet été, nous n'aurons pas eu de nouvelle édition de JARAC (J'en ai rien à coder). Evénement organisée par Microsoft que j'attendais cette année pour découvrir de petites nouveautés et surtout développer sur Windows Phone 8.
Du coup, j'en ai profité pour partager avec vous des petites choses, qui je l'espère, vous aurons apporté. Pour ma part j'ai pris pas mal de plaisir à faire tous ces articles.

Les Tutos Vidéos

Les Tutos vidéos étaient pour moi un grande première et même si je n'ai eu que peu de retours dessus c'était très sympa à faire. Les vidéos ne seront plus systématiques, mais devraient revenir notamment sur des problèmes qui le nécessitent.
Tant que j'aborde le sujet, certaines vidéos ne sont pas encore publiées aujourd'hui mais elles devraient l'être prochainement. En vacances je devrais avoir le temps de les faire.

Les Vacances

Evidemment, comme tout développeur passionné, je pars avec plein de projets dans les cartons (Application Windows 8, Certification Microsoft,...) et surtout de la lecture. Donc si je tombe sur des livres qui valent le détour, je ne manquerais pas d'en parler.

La rentrée, reposé avec plein d'idées

Alors reposé je l'espère, le plein d'idées c'est sûr puisqu'il y en a déjà qui germent dans ma tête avec de nouvelles catégories d'articles.

Conclusion

Rien à rajouter de plus à part "Bonnes Vacances !" à ceux qui, comme moi, partent en fin de saison et puis pour les autres je sais c'est rageant mais "Bossez bien !".

[WindowsPhone] Twitter avec TweetSharp

Bonjour à toutes et à tous,
On se retrouve aujourd'hui pour un article sur la mise en place de TweetSharp sous WindowsPhone pour récupérer le flux d'un twittos.

Depuis que Twitter a changé son API, il n'est plus possible d'effectuer une requête GET de manière anonyme pour récupérer un flux de tweets.
Nous allons donc voir comment mettre en place l'OAuth.

Compte Développeur Twitter

La première chose à effectuer est de déclarer son application sur son compte développeur.
A noter que si vous pensez ne pas avoir de compte développeur, sachez qu'il est associé à votre identifiant Twitter.

On se connecte donc à l'adresse suivante https://dev.twitter.com/, et on s'authentifie avec "sign in" et ses identifiants Twitter.

Une fois connecté, on accède à ces applications :

On clique ensuite sur "Create an application", on renseigne le formulaire et on finalise en cliquant sur "Create your Twitter Application".

Ensuite vous récupérer vos "Consumer key" et "Consumer secret" :

Et vous cliquez sur "Create my access token" pour les récupérer :

Gestionnaire de paquets

Donc maintenant que nous avons toutes les informations nécessaires on va pouvoir s'attaquer au code ! On ouvre Visual Studio et le manager de packages NuGet. Vérifiez qu'il soit bien à jour (version 2.4 minimum !!). Démarrez le gestionnaire en passant par: "Outils" > "Gestionnaire de package de bibliothèques" > "Console du Gestionnaire de package", et lancez la commande :

Install-package TweetSharp.

Requete TweetSharp

Une fois vos paquets installés, vous créez votre classe de TwitterItem :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyApp.Model.ModeleClient : INotifyPropertyChanged
{
    public class TwitterItem
    {
        private string _tweetid;
        public string TweetId
        {
            get
            {
                return _tweetid;
            }
            set
            {
                if (_tweetid != value)
                {
                    _tweetid = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private string _username;
        public string UserName 
        {
            get
            {
                return _username;
            }
            set
            {
                if (_username != value)
                {
                    _username = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private string _message;
        public string Message 
        {
            get
            {
                return _message;
            }

            set
            {
                if (_message != value)
                {
                    _message = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public string _imagesource;
        public string ImageSource 
        {
            get
            {
                return _imagesource;
            }
            set
            {
                if (_imagesource != value)
                {
                    _imagesource = value;
                    NotifyPropertyChanged();
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Et enfin sur votre classe qui gère la récupération des données depuis Twitter :

using MyApp.Model.ModeleClient;
using MyApp.Resources;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using System.Xml.Linq;
using TweetSharp;

namespace MyApp.Model.Services
{
    
    public class TwitterService
    {

        private readonly ObservableCollection<TwitterItem> _data = new ObservableCollection<TwitterItem>();
        public ObservableCollection<TwitterItem> Data { get { return _data; } }

        public void LoadData(string Request)
        {

            var service = new TweetSharp.TwitterService("consumerkey", "consumersecret");
            service.AuthenticateWith("access token", "access token secret");

            var options = new ListTweetsOnUserTimelineOptions { ScreenName = "pascalpereznet", Count = 50 };

            Data.Clear();
            service.ListTweetsOnUserTimeline(options, (statuses, response) =>
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        foreach (var status in statuses)
                        {
                            Data.Add(new TwitterItem { TweetId = status.Id.ToString(), Message = status.Text, ImageSource = status.User.ProfileImageUrl, UserName = status.User.ScreenName });

                        }
                    }
                    else
                    {

                    }
                });
        }
    }
}

Conclusion

Voilà, cette requête va vous permettre de pouvoir récupérer une liste de Tweets d'un Twittos.
Pour plus d'informations sur TweetSharp et les requêtes possibles, la documentation est ici.
A bientôt

12/08/2013

[WindowsPhone] Lecteur de flux : LiveTiles, Isolated Storage & BackgroundAgent

Bonjour à toutes et à tous,
nous nous retrouvons pour la cinquième et dernière partie sur le lecteur de flux RSS, ou nous verrons comment mettre en place des LiveTiles et les mettre à jour via un BackgroundAgent. C'est parti !.

Dossier complet

  1. [WindowsPhone] Lecteur de flux : Couche Model
  2. [WindowsPhone] Lecteur de flux : Couche ViewModel
  3. [WindowsPhone] Lecteur de flux : Couche View
  4. [WindowsPhone] Lecteur de flux : Le détail du post
  5. [WindowsPhone] Lecteur de flux : LiveTiles, Isolated Storage & BackgroundAgent

LiveTiles

Il existe trois types de tuiles dynamiques :

  • Flip
  • Iconic
  • Cycle
Pour plus d'infos, ou pour avoir un visuel de ces différents types tuiles c'est par ici.

Dans notre application, nous allons choisir d'utiliser les Flip.
Et nous allons choisir de réinitialiser notre Tile à chaque fois que l'application va se lancer. Du coup dans notre fichier MainPage.xaml.cs, nous codons une méthode ResetLiveTilte:

using Microsoft.Phone.Shell;
// ...
        private void ResetLiveTile()
        {
            FlipTileData primaryTileData = new FlipTileData();
            primaryTileData.Count = 0;
            primaryTileData.Title = AppResources.LiveTilesTitle;
            primaryTileData.BackContent = "";
            ShellTile primaryTile = ShellTile.ActiveTiles.First();
            primaryTile.Update(primaryTileData);
        }

Nous créons des données de FlipTile que nous réinitialisons. On passe le BackContent à "" pour arrêter l'effet de rotation. Ensuite nous récupérons la première ShellTile active et nous la mettons à jour avec les données du FlipTileData.

Isolated Storage

Maintenant nous devons vérifier si de nouveaux articles ont été écrits depuis la dernière dernière fois que l'application a été ouverte. Nous allons donc retourner dans notre PostViewModel et nous allons créer une méthode qui va nous permettre de garder la dernière synchronisation qu'aura effectué l'utilisateur et qui servira de référence à notre tâche périodique.

        void UpdateIsoStorage()
        {
            var myDate = Convert.ToDateTime(DateTime.Now, CultureInfo.InvariantCulture);
            IsolatedStorageFile myIsoStorage = IsolatedStorageFile.GetUserStoreForApplication();

            StreamWriter myStream = new StreamWriter(new IsolatedStorageFileStream("LastUpdate.txt", FileMode.Create, FileAccess.Write, myIsoStorage));
            myStream.Write(myDate);
            myStream.Close();
        }

Nous récupérons l'Isolated Storage dédié à notre application sur le téléphone de l'utilisateur et nous écrivons la valeur dans un fichier LastUpdate.txt. Bien penser à fermer le flux. Et nous allons lever cette méthode lorsque le chargement asynchrone sera terminé :

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                MyBlogPosts = BloggerSvc.PostData;
                IsBusy = false;
                OnTreatmentComplete();
            });
            UpdateIsoStorage();

Maintenant on peut s'occuper de la tâche périodique.

BackgroundAgent

Donc nous insérons un nouveau projet de type Windows Phone Scheduled Task Agent. Dans notre fichier ScheduleAgent.cs nous le transformons comme ceci :

        public static void LaunchForTest(string name, TimeSpan delay)
        {

        }
        protected override void OnInvoke(ScheduledTask task)
        {
            WebClient webClient = new WebClient();
            webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
            webClient.DownloadStringAsync(new Uri("http://pascalpereznet.blogspot.com/feeds/posts/default?alt=rss&redirect=false&max-results=30")); 
        }

        void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                
            }
            else
            {
                int myReturnValue = GetNewPostsQuantity(e.Result);
                if (myReturnValue > 0)
                {
                    string myText;
                    if (myReturnValue > 1)
                    {
                        myText = myReturnValue + " nouvel article";
                    }
                    else
                    {
                        myText = myReturnValue + " nouveaux articles";
                    }
                    
                    FlipTileData primaryTileData = new FlipTileData();
                    primaryTileData.Count = myReturnValue;
                    primaryTileData.BackContent = myText;

                    ShellTile primaryTile = ShellTile.ActiveTiles.First();
                    primaryTile.Update(primaryTileData);
                }
                else
                {
                    FlipTileData primaryTileData = new FlipTileData();
                    primaryTileData.Count = 0;
                    primaryTileData.Title = "pascalpereznet";
                    primaryTileData.BackContent = "";
                    ShellTile primaryTile = ShellTile.ActiveTiles.First();
                    primaryTile.Update(primaryTileData);
                }
            }
            NotifyComplete();
        }

        private static int GetNewPostsQuantity(string feedXML)
        {
            try
            {
                var date = String.Empty;
                var flow = XDocument.Parse(feedXML);
                var syncitems = (from si in flow.Descendants("item")
                                 select si.Element("pubDate").Value).ToList();


                using (IsolatedStorageFile myIsoStorage = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (IsolatedStorageFileStream myStream = myIsoStorage.OpenFile("LastUpdate.txt", FileMode.Open, FileAccess.Read))
                    {
                        using (StreamReader sr = new StreamReader(myStream))
                        {
                            date = sr.ReadLine();
                        }
                    }

                }
                DateTime MyDate = new DateTime();
                if (date != null)
                {
                    MyDate = Convert.ToDateTime(date);
                }
                else
                {
                    MyDate = DateTime.Now;
                }

                int Counter = 0;

                foreach (var item in syncitems)
                {
                    var mycompared = Convert.ToDateTime(item, CultureInfo.InvariantCulture);
                    if (mycompared > MyDate)
                    {
                        Counter++;
                    }
                }

                return Counter;
            }
            catch (Exception)
            {
                return 0;
            }

        }

Nous lançons une recherche asynchrone de l'ensemble des posts. Nous allons ensuite lire la valeur que nous avons stocké dans notre Isolated Storage et compter le nombre d'articles plus récents. En fonction du résultat nous mettons à jour notre liveTile.

Création de notre tâche périodique

Nous allons ensuite rechercher dans le service gérant les tâches planifiées (tâche périodique et tâche intensive) si notre tâche n'existe pas déjà et nous le supprimons. Nous en créons une nouvelle et nous l'ajoutons, C'est lors de l'ajout que nous serons si l'utilisateur a bloqué ou pas les tâches périodiques pour notre application.

        PeriodicTask periodicTask;
        string periodicTaskName = "PeriodicAgent";
        public bool agentsAreEnabled = true;


        private void StartPeriodicAgent()
        {
            agentsAreEnabled = true;
            periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;

            if (periodicTask != null)
            {
                try
                {
                    ScheduledActionService.Remove(periodicTaskName);
                }
                catch (Exception)
                {
                }

            }

            periodicTask = new PeriodicTask(periodicTaskName);
            periodicTask.Description = "Check for Updates";

            try
            {
                ScheduledActionService.Add(periodicTask);
#if DEBUG
                ScheduledActionService.LaunchForTest(periodicTaskName, new TimeSpan(0, 0, 10));
#endif
            }
            catch (InvalidOperationException exception)
            {
                if (exception.Message.Contains("BNS Error: The action is disabled"))
                {
                    agentsAreEnabled = false;
                }
                if (exception.Message.Contains("BNS Error: The maximum number of ScheduledActions of this type have already been added."))
                {

                }

            }
            catch (SchedulerServiceException)
            {

            }
        }

le if #DEBUG va nous permettre de lancer rapidement la tâche lors en phase de test pour ne pas attendre que le tâche s'exécute au bout de 30 min.

Conclusion

Voilà, ainsi s'achève mon dossier Windows Phone de cet été.
Vous pouvez retrouver les sources ici
A vendredi pour un nouvel article.

09/08/2013

[WindowsPhone] Flurry - Audience Tracking

Bonjour à toutes et à tous,
aujourd'hui on va regarder comment mettre en place du Tracking dans nos applications pour décortiquer un petit peu le comportement de nos utilisateurs face à nos applications.

On commence tout d'abord par créer un compte sur Flurry en cliquant sur "sign up".

Une fois le compte créé, il ne nous reste plus qu'à aller dans l'onglet "Application" et cliquer sur "Add a new Application"

Choisissez la plateforme Windows Phone :

On attribue un nom et une catégorie à notre application :

On peut enfin recevoir notre identifiant pour notre application :

Pour finir, on cliquer sur "Download" à côté du SDK car en plus de télécharger l'API Flurry, on va également avoir un récapitulatif pour notre identifiant d'application :

Maintenant que nous avons tout ce qu'il faut, il ne reste plus qu'à ajouter une référence vers la dll de Flurry.
Ensuite modifiez notre App.xaml.cs comme ceci :


using FlurryWP8SDK;

namespace MyApp
{
    public partial class App : Application
    {
     //...
        // Code to execute when the application is launching (eg, from Start)
        // This code will not execute when the application is reactivated
        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
            FlurryWP8SDK.Api.StartSession("JT4JZDZ47KC7H4QCPDGG");
        }

        // Code to execute when the application is activated (brought to foreground)
        // This code will not execute when the application is first launched
        private void Application_Activated(object sender, ActivatedEventArgs e)
        {
            FlurryWP8SDK.Api.StartSession("JT4JZDZ47KC7H4QCPDGG");
        }

        // Code to execute when the application is deactivated (sent to background)
        // This code will not execute when the application is closing
        private void Application_Deactivated(object sender, DeactivatedEventArgs e)
        {
            FlurryWP8SDK.Api.EndSession();
        }

        // Code to execute when the application is closing (eg, user hit Back)
        // This code will not execute when the application is deactivated
        private void Application_Closing(object sender, ClosingEventArgs e)
        {
            FlurryWP8SDK.Api.EndSession();
        }
    //...
    }
}

Conclusion

Voilà c'est tout pour aujourd'hui.
Il vous faudra attendre quelques heures, avant de pouvoir obtenir vos premières statistiques.
A bientôt.

06/08/2013

[WindowsPhone] Hero Corp France

Article pour nouvelle application Windows Phone.

L'application

Pour cette occasion, j'ai créé une page supplémentaire pour mon blog afin de ressembler toutes mes applications.

A l'occasion de l'annonce officielle du démarrage de saison 3, qui a ravi tous les fans de la série, le site Hero Corp France.com a souhaité réaliser une application sur (presque) toutes les plateformes de smartphone. Etant moi même friand de cette série, j'ai eu l'opportunité de réaliser cette application après quelques échanges par mail avec l'Administrateur du site.

Conclusion

N'hésitez pas à aller commenter et noter l'application.
Vous pouvez retrouver l'application ici

05/08/2013

[WindowsPhone] Lecteur de flux : Le détail du post

Bonjour à toutes et à tous,
on se retrouve aujourd'hui pour la quatrième partie de notre lecteur de flux. Et nous allons ajouter le détail de l'article.

Dossier complet

  1. [WindowsPhone] Lecteur de flux : Couche Model
  2. [WindowsPhone] Lecteur de flux : Couche ViewModel
  3. [WindowsPhone] Lecteur de flux : Couche View
  4. [WindowsPhone] Lecteur de flux : Le détail du post
  5. [WindowsPhone] Lecteur de flux : LiveTiles, Isolated Storage & BackgroundAgent

Model

Donc la première des choses que nous allons faire c'est de rajouter la méthode qui va aller chercher le détails de l'article dans notre couche Model :

        public void LoadBlogPost(string BloggerId)
        {
            var myWebClient = new WebClient();
            myWebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(myWebClient_DownloadStringCompleted);
            myWebClient.DownloadStringAsync(new Uri("http://pascalpereznet.blogspot.com/feeds/posts/full/" + BloggerId + "?alt=rss"));
        }

A noter que l'EventHandler lèvera la méthode myWebClient_DownloadStringCompleted que nous avons créé dans la première partie.

ViewModel

Nous créons maitenant le ViewModel associé et reprends les même informations qui le précédent ViewModel à la différence que nous n'avons ici besoin que du premier résultat de l'ObservableCollection que nous renverra notre classe communiquant avec le service.

using SampleRSSReader.Model.POCO;
using SampleRSSReader.Model.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace SampleRSSReader.ViewModel
{
    public class PostViewModel : ViewModelBase
    {
        private PostItem _myBlogPost = new PostItem();
        public PostItem MyBlogPost
        {
            get
            {
                return _myBlogPost;
            }
            set
            {
                if (_myBlogPost != value)
                {
                    _myBlogPost = value;
                    NotifyPropertyChanged();
                }
            }
        }

        BloggerService Bloggersvc;

        public void OnNavigatedTo(string BloggerId)
        {
            IsBusy = true;
            Bloggersvc = new BloggerService();
            Bloggersvc.LoadAsyncComplete += Bloggersvc_LoadAsyncComplete;
            Bloggersvc.LoadBlogPost(BloggerId);
        }

        void Bloggersvc_LoadAsyncComplete(object sender, EventArgs e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                MyBlogPost = Bloggersvc.PostData.FirstOrDefault();
                IsBusy = false;
                TreatmentCompleted();
            });
        }
    }
}

View

Nous allons directement dans notre dossier View pour rajouter une page qui nous allons appeler PostView, et nous retournons sur le MainViewPage.xaml.cs pour modifier le code lors de la sélection de l'article :

        private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var myItem = (PostItem)(sender as LongListSelector).SelectedItem;
            NavigationService.Navigate(new Uri("/View/PostView.xaml?bid=" + myItem.BloggerId, UriKind.Relative));
            //WebBrowserTask myWebBrowser = new WebBrowserTask();
            //myWebBrowser.Uri = new Uri(myItem.Link);
            //myWebBrowser.Show();
        }

Nous naviguons d'une page à une autre en passant en paramètre le BloggerId. Et maintenant on va traiter cela dans le PostView.xaml.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using SampleRSSReader.ViewModel;

namespace SampleRSSReader.View
{
    public partial class PostView : PhoneApplicationPage
    {
        ViewModelLocator _vml;
        public PostView()
        {
            InitializeComponent();
            _vml = new ViewModelLocator();
            DataContext = _vml;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            _vml.PostViewModel.OnNavigatedTo(NavigationContext.QueryString["bid"].ToString());
        }
    }
}

Vous voyez qu'à nouveau nous surchargeons la méthode OnNavigatedTo et nous lançons la récupération des données.

IHM

Maintenant nous allons mettre cela en forme dans l'affichage.


        
            
            
        

        
        
            
            
        

        
        
            
        
    

Comme vous pouvez le constater, les titres sont modifiés et bindés, et nous exécutons cela sur notre émulateur. Et là nous nous apercevons que nous n'avons pas la possibilité de naviguer jusqu'à la fin de l'article. Normal, il nous manque un contrôle : le ScrollViewer.
Nous le rajoutons :


            
                
            
        

Maintenant, lorsque nous exécutons tout cela, nous remarquons deux choses :

  1. Toute les balises HTML sont visibles.
  2. Malgré le ScrollViewer, on ne voit toujours pas la fin de l'article.(si vous avez pris un article assez long)

Alors pour les balises HTML qui sont encore visibles, je vous propose d'ajouter un converter :
[WindowsPhone] TextPicker

Pour la limite de l'article nous arrivons à une limite mise en place sur les contrôles WP (donc ici notre TextBlock) qui consiste à ne pas excéder 2048 en hauteur et largeur. Pour contourner cela, il faudra découper notre texte en plusieurs parties ce qui s'effectuera naturellement lorsque nous chercherons à mettre en forme notre texte. Sujet qui sera traité à part.

Conclusion

Voilà c'est tout pour aujourd'hui. Vous pouvez retrouver comme d'habitude les sources du code ici. Les sources intègrent le converter.
La vidéo viendra un peu plus tard.
A vendredi pour un nouvel article ou à lundi pour un nouveau tuto (le dernier de la série).

[WindowsPhone] TextPicker

Bonjour à toutes et à tous,
Je vous propose aujourd'hui un Converter pour récupérer uniquement le texte de votre flux rss. C'est par ici...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Data;

namespace SampleRSSReader.View.Converter
{
    public class TextPicker : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null) 
            {
                return null;
            }

            string fixedString = "";

            // Supprime les retours à la ligne.
            fixedString = fixedString.Replace("\r", "").Replace("\n", "");

            // Supprime le balises. 
            fixedString = Regex.Replace(value.ToString(), "<[^>]+>", string.Empty);

            // Remplace les caractères encodés.
            fixedString = HttpUtility.HtmlDecode(fixedString);

            // Supprime les formats DateTime avec une heure non définie
            fixedString = fixedString.Replace("00:00:00", "");

            strLength = fixedString.ToString().Length;

            if (strLength == 0)
            {
                return null;
            }

            // Permet de limiter la longueur du texte.
            int maxLength = 4096;
            int strLength = 0;
            

            else if (strLength >= maxLength)
            {
                fixedString = fixedString.Substring(0, maxLength);
                fixedString = fixedString.Substring(0, fixedString.LastIndexOf(" "));
                fixedString += "...";
            }

            return fixedString;
        
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Voilà, on se retrouve en fin de semaine pour un nouvel article.