02/08/2013

[WindowsPhone] UserControl, TiltEffect, LiveTiles (Pin To Start)

Bonjour à toutes et à tous,
aujourd'hui on démarre avec les UserControl. On va voir comment les mettre en place et les personnaliser. Ensuite nous verrons des petites améliorations dans le cadre d'utilisation standard afin de mieux intégrer vos applications dans l'écosystème Windows Phone.

UserControl

Donc un UserControl est un contrôle personnalisable qui va posséder sa propre logique et que l'on va pouvoir réutiliser à chaque fois que cela sera nécessaire. (Dans un souci de factorisation de notre code).
Dans l'exemple prit tout au long de l'article nous allons réaliser un User Control qui affiche une Image et un Titre pour un article.
Donc nous allons commencer avec nos Dependency Properties.

Dependency Property

Les Dependency Properties sont un type de propriétés sur lesquelles reposent les piliers des technologies basées sur le XAML (j'entends ici bien évidemment les technologies WPF, Silverlight & Windows Phone). La puissance du XAML nous permet d'utiliser le styles, les animations et le Data Binding. C'est ensemble nécessite de calculer dynamiquement les valeurs de ces propriétés d'où l'importance des Dependency Properties.
Ici elles vont nous intéresser pour le Data Binding.

Donc nous démarrons par la création de notre User Control et des deux propriétés :

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 System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO.IsolatedStorage;
using System.Windows.Shapes;

namespace MyApp.View.UserControls
{
    public partial class MyPostUC : UserControl
    {
        public static DependencyProperty ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(string), typeof(MyPostUC), new PropertyMetadata(null));
        public string ImagePath
        {
            get
            {
                return (string)GetValue(ImagePathProperty);
            }
            set
            {
                SetValue(ImagePathProperty, value);
            }

        }

        public static DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyPostUC), new PropertyMetadata(null));
        public string Title 
        {
            get
            {
                return (string)GetValue(TitleProperty);
                
            }
            set
            {
                SetValue(TitleProperty, value);
            }
        }

        public MyPostUC()
        {
            InitializeComponent();
        }
    }
}

Data Context

Une petite astuce pour notre Binding sur ces propriétés. Lorsque nous allons utiliser notre User Control le Data Context sera au niveau du contrôle et non au niveau du LayoutRoot, donc :

        public MyPostUC()
        {
            InitializeComponent();
            this.LayoutRoot.DataContext = this;
        }

TiltEffect

Tout d'abord, qu'est ce que le TiltEffect ?
Le TiltEffect est la légère distorsion qu'effectue certains contrôles lorsque ces derniers sont pressés avec le doigt.
Pour mettre cet effet en place nous allons télécharger le Windows Phone Toolkit avec NuGet (Pour rappel version 2.4 minimum pour du dev WP8).
Vous cliquez sur "Outils", "Gestionnaire de package de bibliothèque", "Console du Gestionnaire de package".
Install-Package WPtoolkit
Ensuite nous n'avons plus qu'à instancier côté XAML le Toolkit et à activer le TiltEffect.


LiveTile secondaire (Pin To Start)

Donc maintenant que nous avons notre User Control, qui va certainement alimenter le Data Template d'un LongListSelector, nous allons faire en sorte de créer un menu contextuel afin d'afficher un raccourci sur la Home
Tout d'abord, nous allons côté XAML créer ce menu contextuel (Ca tombe bien on a déjà installé le WPToolkit) :


    
        
            
        
    

Maintenant que nous avons le menu contextuel nous allons nous abonner à l'événement du click et réaliser notre tuile secondaire.
Donc pour expliquer le principe de la tuile secondaire ce type de tuile sont des tuiles qui affiche une ou plusieurs images. Nous allons dans un premier temps vérifier que notre tuile secondaire n'existe pas déjà. Ensuite nous créons une image à partir de l'image et du texte de notre contrôle que nous sauvegarderons dans l'Isolated Storage dans le Shared Content. Nous assignerons l'URI de la Tuile lors de sa création comme ceci :

        private void PinToHome_Click(object sender, RoutedEventArgs e)
        {
            var myItem = (sender as MenuItem).DataContext as PostItem;

            ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(myItem.BloggerId));
            if (TileToFind == null)
            {
                var fontFamily = new FontFamily("Segoe WP SemiLight");
                var fontColor = new SolidColorBrush(Colors.White);
                var backgroundRectangle = new Rectangle() { Height = 173, Width = 173, Fill = (Brush)Application.Current.Resources["PhoneAccentBrush"] };

                string ttbTextligne1, ttbTextligne2;

                if (myItem.Title.Count() > 17)
                {
                    ttbTextligne1 = myItem.Title.Substring(0, myItem.Title.Substring(0, 17).Trim().LastIndexOf(" "));
                    ttbTextligne2 = myItem.Title.Substring(myItem.Title.Substring(0, 17).Trim().LastIndexOf(" ") + 1);
                    if (ttbTextligne2.Count() > 10)
                    {
                        ttbTextligne2 = ttbTextligne2.Substring(0, ttbTextligne2.Substring(0 , 10).LastIndexOf(" ")).Trim() + "...";
                    }
                }
                else
                {
                    ttbTextligne1 = myItem.Title;
                    ttbTextligne2 = "";
                }

                var textTextBlock = new TextBlock()
                {
                    Text = ttbTextligne1 + "\r\n" + ttbTextligne2,
                    FontSize = 20,
                    Width = 160,
                    TextWrapping = System.Windows.TextWrapping.Wrap,
                    FontWeight = FontWeights.Bold,
                    Foreground = fontColor,
                    FontFamily = fontFamily
                };

                var WBitmap = new WriteableBitmap(173, 173);
                WBitmap.Render(backgroundRectangle, new TranslateTransform());
                WBitmap.Render(textTextBlock, new TranslateTransform()
                {
                    X = 8,
                    Y = 105
                });

                WBitmap.Render(myImage, new TranslateTransform()
                {
                    X = 11,
                    Y = 11
                });

                WBitmap.Invalidate();

                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    var stream = store.CreateFile("/Shared/ShellContent/" + myItem.Title.Replace("\"", "") + "tile.jpg");
                    WBitmap.SaveJpeg(stream, 173, 173, 0, 100);
                    stream.Close();
                }

                StandardTileData primaryTileData = new StandardTileData();
                primaryTileData.BackgroundImage = new Uri("isostore:/Shared/ShellContent/" + myItem.Title.Replace("\"", "") + "tile.jpg", UriKind.Absolute);
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        ShellTile.Create(new Uri("/View/BlogPostView.xaml?bid=" + myItem.BloggerId, UriKind.Relative), primaryTileData);
                    });
            }
        }

Je rajoute une dernière petite chose. Dans l'état actuel des choses, votre tuile secondaire sera créée ou pas en fonction de son existence mais quelque soit le cas le menu contextuel vous proposera toujours sa création. Pour y remédier, on s'abonne à l'événement du Loaded du MenuItem et l'on rajoute simplement :

        private void PinToHome_Loaded(object sender, RoutedEventArgs e)
        {
            var myItem = (sender as MenuItem).DataContext as PostItem;
            ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(myItem.BloggerId));
            if (TileToFind == null)
            {
                (sender as MenuItem).IsEnabled = true;
            }
            else
            {
                (sender as MenuItem).IsEnabled = false;
            }
        }

Conclusion

On a vu quelques notions sympas à creuser comme les Dependency Properties. Cela devrait, du moins je l'espère, vous permettre de rendre vos applications plus intuitives à vos utilisateurs Windows Phone.
C'est tout pour aujourd'hui.
On se retrouve lundi pour un nouveau tuto ou vendredi prochain pour un nouvel article.

29/07/2013

[WindowsPhone] Lecteur de flux : Couche View

Bonjour à toutes et à tous,
On se retrouve pour la troisième partie de la création de notre lecteur de flux RSS avec la création de la couche View.

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

Mise en route

Bon, ça fait deux semaines qu'on est sur ce lecteur, ce serai pas mal qu'on puisse tester si ce qu'on a fait fonctionne. Donc on va faire le strict minimum pour vérifier visuellement qu'on récupère les données et les afficher. Donc c'est parti !
On démarre avec le MainPage.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.Resources;
using SampleRSSReader.ViewModel;
using SampleRSSReader.Model.POCO;

namespace SampleRSSReader
{
    public partial class MainPage : PhoneApplicationPage
    {

        ViewModelLocator vml;

        public MainPage()
        {
            InitializeComponent();
            vml = new ViewModelLocator();
            this.DataContext = vml;

        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            vml.OnNavigatedTo();
        }
    }
}

Donc on instancie notre ViewModelLocator et on définie le DataContext.
Enfin on va surcharger la méthode OnNavigatedTo et déclencher le chargement de nos ViewModel.
Maintenant, on va s'occuper d'un affichage minimaliste des ces données :

<phone:PhoneApplicationPage
    x:Class="SampleRSSReader.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
    <!--LayoutRoot is the root grid where all page content is placed-->

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        <Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector ItemsSource="{Binding PostsViewModel.MyBlogPosts}">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Title}"/>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Donc on rajoute un LongListSelector, on effectue notre Binding sur l'ObservableCollection de notre ViewModel. On édite l'ItemTemplate et on lui affecte un template avec un TextBlock qui se sera bindé sur la propriété Title.

On compile et on envoie ça sur notre émulateur.

Avec des Images c'est mieux !

Maintenant que tout fonctionne, on va "embellir" notre application en affichant des images.
Donc en fait les images sont contenues dans le contenu de l'article, il va donc falloir les isoler pour alimenter nos Image. Pour cela on va utiliser un Converter que j'ai déjà publié sur mon blog et que vous pouvez retrouver ici.

Donc on va ensuite déclarer notre Converter dans le fichier App.xaml :

<Application
    x:Class="SampleRSSReader.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

    xmlns:conv="clr-namespace:SampleRSSReader.View.Converter">

    <!--Application Resources-->
    <Application.Resources>
        <local:LocalizedStrings xmlns:local="clr-namespace:SampleRSSReader" x:Key="LocalizedStrings"/>

        <conv:RSSImageConverter x:Key="ImagePicker" />

    </Application.Resources>

    <Application.ApplicationLifetimeObjects>
        <!--Required object that handles lifetime events for the application-->
        <shell:PhoneApplicationService
            Launching="Application_Launching" Closing="Application_Closing"
            Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    </Application.ApplicationLifetimeObjects>

</Application>

Et enfin, on modifie notre DataTemplate sur MainPage.xaml :

<phone:PhoneApplicationPage
    x:Class="SampleRSSReader.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="PASCALPEREZNET.BLOGSPOT.COM" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="Blog Posts" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector ItemsSource="{Binding PostsViewModel.MyBlogPosts}">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal" Margin="12">
                            <Image Source="{Binding Path=Content, Converter={StaticResource ImagePicker}}" Height="30" Width="30" Margin="0,0,12,0"/>
                            <TextBlock Text="{Binding Title}"/>
                        </StackPanel>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

On compile et on envoie au compilateur, je veux simplement attirer votre attention sur la valeur du Margin que j'ai utilisé dans mon StackPanel. C'est un 12, et ce n'est pas un 12 par hasard c'est que c'est le chiffre d'or à utiliser en Windows Phone pour la bonne et simple raison que c'est l'intervalle entre deux éléments pour arriver à sélectionner sur du tactile.

Accéder aux articles

Maintenant que nous avons notre liste, il serait intéressant de naviguer jusqu'à l'article. On va donc s'abonner au changement de sélection du LongListSelector pour récupérer l'url de l'article et utiliser le navigateur natif du téléphone :

MainPage.xaml
<phone:LongListSelector ItemsSource="{Binding PostsViewModel.MyBlogPosts}" SelectionChanged="LongListSelector_SelectionChanged">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal" Margin="12">
                            <Image Source="{Binding Path=Content, Converter={StaticResource ImagePicker}}" Height="30" Width="30" Margin="0,0,10,0"/>
                            <TextBlock Text="{Binding Title}"/>
                        </StackPanel>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>
MainPage.xaml.cs
        private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var myItem = (PostItem)(sender as LongListSelector).SelectedItem;
            WebBrowserTask myWebBrowser = new WebBrowserTask();
            myWebBrowser.Uri = new Uri(myItem.Link);
            myWebBrowser.Show();
        }

On compile et on envoie sur l'émulateur. On teste ça marche mais... Si vous vous choisi un élément qui se trouve à la fin de la liste, vous vous êtes rendu compte que vous êtes automatique rebasculé au le début de la liste.

La navigation

Le phénomène constaté plus haut est du au fait que lorsque nous avons effectué notre retour avec la touche back de notre téléphone pour revenir en arrière, la méthode OnNavigatedTo a été exécutée ce qui est logique puisque nous naviguions vers elle. Pour éviter d'avoir à nouveau ce phénomène :

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.NavigationMode != NavigationMode.Back)
            {
                base.OnNavigatedTo(e);
                vml.OnNavigatedTo();
            }
        }

Conclusion

Voilà ce sera le mot de la fin.
Comme d'habitude, vous pouvez retrouver les sources ici
La vidéo arrive dans les prochains jours !
A bientôt !

26/07/2013

[XAML] INotifyPropertyChanged

Bonjour à toutes et à tous,
aujourd'hui je vais vous présenter l'interface INotifyPropertyChanged. Donc pour celles et ceux qui ne la connaîtrait pas c'est par ici.

Ca sert à quoi ?

Très utilisée dans les technologies XAML (Windows Phone, Silverlight, WPF), cette interface va nous permettre de signaler qu'une propriété d'un objet a été modifiée par le biais d'un événement et cela va permettre de notifier à votre View(MVVM) ce changement et permettre le rafraîchissement d'informations dans le cadre de Binding.
Du coup c'est plutôt utile. ^^

Comment on fait ?

Premièrement en l'implémentant l'interface.

public class User : System.ComponentModel.INotifyPropertyChanged
{

}
Puis vous faites un clic droit sur INotifyPropertyChanged et "Implémenter l'interface". Vous allez récupérer l'évént handler automatiquement dans votre classe :

public event PropertyChangedEventHandler PropertyChanged;

Et c'est tout ce que vous allez récupérer ! Libre à vous de composer la méthode qui va lever l'événement !

Les accesseurs

Pour petit rappel, pour coder nos accesseurs avant C# 3 :

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      _name = value;
   }
}

Pour coder nos accesseurs après C# 3 :

public string Name { get; set; }

Nos accesseurs avec INotifyPropertyChanged :

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      if(_name != value)
      {
         _name = value;
         // Ici on notifie
      }
   }
}

Pourquoi vérifier si la valeur est changé, simplement dans un souci de performances. "Trop de notifications, tue la notification". De plus et toujours pour les mêmes raisons, ne coder vos accesseurs de cette manière que si les propriétés ont besoins de signaler qu'elles ont été modifiées.

Méthodes de notification

Bon pour les méthodes je vous en propose aujourd'hui 2.
La première est celle proposée dans la MSDN :

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

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

La seconde est celle que j'ai trouvé dans le livre écrit par Jonathan ANTOINE et Thomas LEBRUN intitulé "MVVM - De la découverte à la maîtrise" que vous pouvez retrouver ici


protected void RaisePropertyChanged<T>(Expression<Func<T>> exp)
{
   var memberExpression = exp.Body as MemberExpression;

   if (memberExpression != null)
   {
      string propertyName = memberExpression.Member.Name;
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
         handler(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      if(_name != value)
      {
         _name = value;
         RaisePropertyChanged<string>(() => Name);
      }
   }
}

C'est par exemple cette deuxième implémentation que j'ai utilisé pour mon application "Gâteaux de Laetitia".

Conclusion

Pour plus d'information sur l'interface, c'est par ici
Les deux méthodes présentées ici, ont le mérite de pouvoir continuer à fonctionner même si vous renommez vos variables. Voilà c'est tout pour aujourd'hui.
A bientôt

22/07/2013

[WindowsPhone] Lecteur de flux : Couche ViewModel

Bonjour à toutes et à tous
Nous allons reprendre notre lecteur de flux rss où nous l'avions laissé et allons nous occuper aujourd'hui de la couche ViewModel.

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

Modèle du ViewModel

Donc on va créer notre dossier ViewModel.
Cette fois-ci nous allons créer un modèle qui sera repris par l'ensemble de nos ViewModel. Ce modèle gère la propriété IsBusy de notre ViewModel, l'interface INotifyPropertyChanged et l'événement pour la fin du traitement :

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

using System.Runtime.CompilerServices;

namespace MyApp.ViewModel
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public event EventHandler OnTreatmentCompleted;

        protected void TreatmentCompleted()
        {
            onTreatmentCompleted(this, null);
        }
    }
}

ViewModel des Posts

Ensuite nous créons notre ViewModel qui va gérer une collection d'items, la propriété IsBusy pour indiquer si notre ViewModel est toujours en cours de traitement. Notre méthode d'entrée est bien évidemment la méthode OnNavigatedTo et on s'abonne à l'événement de notre BloggerService. Lorsque le traitement est terminé, nous récupérons les data et on signale que le traitement est terminé :

using MyApp.Model.ModeleClient;
using MyApp.Model.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace MyApp.ViewModel
{
    public class PostsViewModel : ViewModelBase
    {

        private ObservableCollection _myBlogPosts = new ObservableCollection();
        public ObservableCollection MyBlogPosts
        {
            get
            {
                return _myBlogPosts;
            }
            set
            {
                if (_myBlogPosts != value)
                {
                    _myBlogPosts = value;
                    NotifyPropertyChanged();
                }
            }

        }

        BloggerService BloggerSvc;

        public void OnNavigatedTo()
        {
            IsBusy = true;
            BloggerSvc = new BloggerService();
            BloggerSvc.LoadAsyncComplete += BloggerSvc_LoadAsyncComplete;
            BloggerSvc.LoadData();
        }

        void BloggerSvc_LoadAsyncComplete(object sender, EventArgs e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                MyBlogPosts = BloggerSvc.PostData;
                IsBusy = false;
                TreatmentCompleted();
            });
        }
    }
}

BONUS : ViewModelLocator

Dans le cadre de notre application, il n'est pas forcément nécessaire de créer un ViewModelLocator car nous n'avons qu'un seul ViewModel. Mais cela deviendra utile dès que nous en aurons plusieurs. Notre ViewModelLocator va nous permettre d'accéder à l'ensemble de nos ViewModel côté XAML sans changer de DataContext

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using MyApp.Resources;
using MyApp.Model;
using System.Net;
using System.Windows;
using System.IO;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using System.IO.IsolatedStorage;
using LGDL.WP8.Model.ModeleClient;
using Microsoft.Phone.Net.NetworkInformation;

namespace MyApp.ViewModel
{
    public class ViewModelLocator : ViewModelBase
    {
        public PostsViewModel PostsViewModel { get; set; }

        public ViewModelLocator()
        {
            PostsViewModel = new PostsViewModel();

            PostsViewModel.OnTreatmentCompleted += ViewModel_OnTreatmentCompleted;
        }

        void ViewModel_OnTreatmentCompleted(object sender, EventArgs e)
        {
            IsBusy = false;
        }

        public void OnNavigatedTo()
        {
            IsBusy = true;
            PostsViewModel.OnNavigatedTo();
        }
    }
}

Conclusion

Bon voilà, notre couche, vous pouvez retrouver les sources ici
La vidéo :


A vendredi pour un prochain article et à lundi prochain pour la suite.

19/07/2013

[WindowsPhone] WebClient et async await pattern

Bonjour à toutes et à tous
aujourd'hui je fais une suite à l'article ici sur la mise place du pattern async await (Nouveauté C# 5) avec un WebClient.

WebClientExtensions

Comme je l'avais annoncé dans l'article précédent, il n'est pas possible en l'état d'utiliser un WebClient avec le pattern async await. Du coup, on va voir aujourd'hui comment contourner cela.

Alors le premier point est de comprendre pourquoi cela ne marche pas. Tout simplement car la méthode DownloadStringAsync ne renvois pas de Task nécessaire au await.

Donc pour contourner cette limitation nous allons, dans la partie liée au services, créer un classe supplémentaire qui va venir surchager notre WebClient et lui faire renvoyer une Task.

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

namespace System.Threading.Tasks
{
    public static class WebClientExtensions
    {

        public static Task<string> DownloadStringTaskAsync(this WebClient webClient, Uri address)
        {
            var taskCSource = new TaskCompletionSource<string>();

            webClient.DownloadStringCompleted += (s, e) =>
            {
                if (e.Error != null)
                {
                    taskCSource.SetException(e.Error);
                }
                else
                {
                    taskCSource.SetResult(e.Result);
                }
            };

            webClient.DownloadStringAsync(address);

            return taskCSource.Task;
        }

        public static Task<string> DownloadStringTaskAsync(this WebClient webClient, string address)
        { 
            return DownloadStringTaskAsync(webClient, new Uri(address));
        }
    }
}

Si vous recréez cette classe, prenez garde au namespace, il est important ! Donc dans la première méthode, on instancie notre TaskCompletionSource on s'abonne à l'événement DownloadStringCompleted et on utilise un delagate pour assignes Exception et Result à notre tâche. Enfin on lance de téléchargement et on retourne la Task.
La seconde méthode est simplement une surcharge au cas où vous souhaiteriez lancer la même méthode avec un string plutôt qu'une Uri.

Conclusion

Voilà c'est tout pour aujourd'hui, comme d'habitude vous pouvez retrouver les sources ici

16/07/2013

[WindowsPhone] Lecteur de flux : Couche Model

Bonjour à toutes et à tous,
aujourd'hui, je vous propose sur une série de différents articles de créer le lecteur d'un flux RSS. Ca va nous permettre de décortiquer la plomberie dans une architecte MVVM simplifiée.

Nous allons donc démarrer avec la partie liée au Model.

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

On va pour le moment repartir de la solution "vide" que je vous ai présenté ici. Pour un petit rappel :

Donc on va commencer par créer un classe qui va représenter les objets (de type POCO) que nous allons manipuler, à savoir des articles de blog. Comme ceci :

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

using System.ComponentModel;

namespace SampleRSSReader.Model.POCO
{
    public class PostItem : INotifyPropertyChanged
    {
        #region Accesseurs

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

        }

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

        }

        private DateTime _publishdate;
        public DateTime PublishDate
        {
            get
            {
                return _publishdate;
            }
            set
            {
                if (_publishdate != value)
                {
                    _publishdate = value;
                    NotifyPropertyChanged();;
                }
            }

        }

        private string _content;
        public string Content
        {
            get
            {
                return _summary;
            }
            set
            {
                if (_summary != value)
                {
                    _summary = value;
                    NotifyPropertyChanged();;
                }
            }
        }

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


        #endregion



        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Pour plus d'informations sur l'interface INotifyPropertyChanged, voyez ici

Maintenant on va créer notre accès à la donnée :

using SampleRSSReader.Model.POCO;
using SampleRSSReader.Resources;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;

namespace SampleRSSReader.Model.Repository
{
    public class BloggerService
    {
        string RequestPosts = "http://pascalpereznet.blogspot.com/feeds/posts/default?alt=rss&redirect=false&max-results=5";

        private readonly ObservableCollection _postdata = new ObservableCollection();
        public ObservableCollection PostData { get { return _postdata; } }

        public void LoadData()
        {
            var myWebClient = new WebClient();
            myWebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(myWebClient_DownloadStringCompleted);
            myWebClient.DownloadStringAsync(new Uri(RequestPosts));
        }

        void myWebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {

            if (e.Error != null)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    MessageBox.Show(AppResources.BloggerServiceError);
                });
            }
            else
            {
                PostData.Clear();
                UpdatePostsList(e.Result);

            }
            OnLoadAsyncComplete();
        }


        private void UpdatePostsList(string feedXML)
        {
            var flow = XDocument.Parse(feedXML);
            var syncitems = from si in flow.Descendants("item")
                            select new PostItem
                            {
                                BloggerId = si.Element("guid").Value.Substring(si.Element("guid").Value.LastIndexOf("-") + 1),
                                Title = si.Element("title").Value,
                                PublishDate = Convert.ToDateTime(si.Element("pubDate").Value, CultureInfo.InvariantCulture),
                                Content = si.Element("description").Value,
                                Link = si.Element("link").Value
                            };

            foreach (var item in syncitems)
            {
                PostData.Add(item);
            }
        }

        public event EventHandler LoadAsyncComplete;

        protected void OnLoadAsyncComplete()
        {
            LoadAsyncComplete(this, null);
        }

    }
}

Ici je vais prendre un peu de temps pour expliquer. Donc nous avons notre classe avec l'ObservableCollection qui va contenir nos data.
Ensuite le point d'entrée sera la méthode LoadData(). Pour un premier exercice sur Windows Phone nous utilisons un WebClient (1 - C'est plus simple, 2 - Y en a assez pour l'exercice et surtout si c'est votre première application. Promis la prochaine fois, on fera des HttpWebRequest ^^). On s'abonne à l'événement DownloadStringCompleted qui va nous permettre de récupérer l'information de manière asynchrone. (Donc en vulgarisant, nous allons retrouver dans un flux de travail différent de celui du flux principal de celui qui gère l'interface de l'application. Par conséquent, cela n'impacte pas notre utilisateur durant ce traitement). Et on lance notre requête.
En appelant la methode BeginInvoke de notre Dispatcher nous nous rattachons à nouveau au flux principal. Ce qui est le cas si nous obtenons un retour contenant une erreur.
On parse ensuite notre resultat et on le requête (LinQ) pour identifier nos éléments à ajouter dans notre ObservableCollection.
Afin on lève un événement pour signaler à notre ViewModel que nous avons fini notre traitement.

Et là vous vous dites, "moi j'ai vu des mecs qui utilise le pattern async await et s'est bien plus simple !".
Oui c'est vrai mais deux choses : la première est que le pattern async await est une nouveauté de C# 5 donc la méthode que je vous ai présenté à le mérite de fonctionner sur l'ensemble de la plateforme Windows Phone 7.x. Et deuxièmement, le pattern async await ne fonctionne pas un WebClient. Du moins pas en l'état et je ferai prochainement un article sur la manière de contourner cela !

Conclusion

Du coup c'est fini pour aujourd'hui car nous en avons fini avec la couche Model.
Comme d'habitude vous pouvez retrouver les sources ici
La vidéo :


A bientôt !

12/07/2013

[Converter] BoolToVisibility

Allez c'est sans conteste, le converter dont vous vous servirez le plus. Convertir un Boolean en Visibility

Bon je pense qu'il n'y a pas besoin d'explication supplémentaire sur l'intérêt de ce converter donc :

public class BoolToVisibilityConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // Vérification de la valeur
            if (value == null)
            {
                return null;
            }
            else
            {
                var myBoolean = (bool)value;

                // Vérification du paramètre
                if (parameter != null)
                {
                    // Autant de paramètre qu'on veut
                    switch (parameter.ToString())
                    {
                        case "invert":
                            return myBoolean ? Visibility.Collapsed : Visibility.Visible;
                        default:
                            return myBoolean ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
                else
                {
                    return myBoolean ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }

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

Pour en savoir plus sur les converter c'est par ici

Une notion abordée ici qui n'est pas abordée dans mon précédent article est le ConverterParameter qui sert tout simplement à passer un paramètre à mon converter. Dans ma classe, le paramètre invert permet d'inverser le résultat.


    
        
    


Il est à noter qu'en WPF le framework embarque directement le converter donc, il reste plus qu'à l'instancier dans notre code côté XAML :


      

A Lundi pour une nouvelle vidéo.