• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

TP : Création d’un lecteur de flux RSS simple

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Oula, mais ça fait longtemps qu’on a pas un peu testé nos connaissances dans un contexte bien concret. Il est temps d’y remédier avec ce TP où nous allons mettre en pratique les derniers éléments que nous avons étudiés.
Avec à la clé, un petit début d’application sympathique pour lire nos flux RSS :) . Allez, c’est parti, à vous de travailler.

Instructions pour réaliser le TP

Vous l’avez compris, nous allons réaliser une petite application qui va nous permettre de lire nos flux RSS préférés. Voici ce que devra faire l’application :

  • L’application possédera une page de menu qui permettra soit d’aller voir les flux déjà enregistrés dans l’application, soit d’aller en ajouter de nouveaux.

  • La page de saisie de nouveau flux offrira un moyen de saisir et de faire persister des URL de flux RSS.

  • La page de consultation possèdera une ListBox présentant la liste de tous les titres, image et description des flux.

  • Lors du clic sur un élément, elle renverra sur une nouvelle page contenant un contrôle Pivot, branché encore une fois sur la liste de tous les flux et automatiquement positionné sur le flux sélectionné dans la ListBox. Ce qui permettra de naviguer de flux en flux grâce à un glissement de doigt. Ce pivot présentera le titre du blog ainsi qu’une ListBox contenant la liste des titres des billets classés par ordre décroissant de dernière mise à jour.

  • La sélection d’un billet de cette ListBox renverra sur une page contenant un WebBrowser et affichant ce fameux billet

Voilà pour l’énoncé de ce TP. Il va nous permettre de vérifier que nous avons bien compris comment accéder à des données sur internet, comment utiliser la bibliothèque de syndication, comment utiliser la ListBox et le Pivot avec le binding. Vous devrez également utiliser le répertoire local - bien sûr, nous n’allons pas re-saisir nos flux RSS à chaque lancement d’application -, la navigation et le contrôle WebBrowser. Bref, que des bonnes choses ! :)

Vous vous sentez prêt pour relever ce défi ? Alors, n’hésitez pas à vous lancer. Petit bonus ? Utiliser une solution pour marquer différemment les billets qui ont été lus des billets qui ne l’ont pas encore été…
Vous pouvez bien sûr si vous le souhaitez respecter le patron de conception MVVM, mais ceci n’est pas une obligation.

Si cela vous effraie un peu (et ça peut !), essayons de décortiquer un peu les différents éléments.

  • La page de menu ne devrait pas poser de problèmes, il suffit d’utiliser le service de navigation.

  • La page de saisie d’URL de flux RSS ne devrait pas non plus poser de problèmes. Pensez juste à faire persister toute modification dans le répertoire local.

  • Passons à la page qui affiche la liste des flux RSS dans une ListBox. Le point critique consiste à charger les différents flux RSS, mais ça, vous devriez savoir le faire car nous l’avons vu dans la partie précédente. N’oubliez pas que la classe WebClient ne peut télécharger qu’un élément à la fois, il va donc falloir attendre que le téléchargement précédent soit terminé. Ensuite, étant donné qu’on doit afficher le titre, l’image et la description, il est tout à fait opportun de créer une classe dédiée qui ne contiendra que les éléments que nous souhaitons afficher. Le titre est disponible dans la propriété Title.Text d’un SyndicationFeed, l’image via la propriété ImageUrl et la description via la propriété Description.Text. Chaque flux possédera une liste de billets qui contiendront la date de dernière publication (propriété LastUpdatedTime.DateTime), le titre (propriété Title.Text) et l’URL du billet (premier élément de la liste Links, propriété Uri). Ensuite tout est une histoire de binding, ce qui n’est pas forcément le plus facile mais c’est un point très important où vous devez vous entraîner. :D

  • Enfin, la page avec le WebBrowser ne devrait pas être trop compliquée à faire, le chapitre sur le WebBrowser étant tout fraîchement lu.

N’oubliez pas que vous pouvez utiliser le dictionnaire d’état pour communiquer entre les pages. Allez, j’en ai beaucoup dit. Pour le reste, c’est à vous de chercher un peu. :p Bon courage.

Correction

Ahhhh, une première vraie application. Enfin ! Pas si facile finalement ?
On se rend compte qu’il y a plein de petites choses, plein de légers bugs qui apparaissent au fur et à mesure de nos tests… Il faut avancer petit à petit à partir du moment où ça se complique un peu et lorsque l’application commence à grandir.

Il n’y a pas une unique solution pour ce TP. Toute solution fonctionnelle est bonne. Je vais vous présenter la mienne. J’ai porté une attention quasi nulle à la présentation histoire que cela soit assez simple. J’espère que de votre côté, vous en avez profité pour faire quelque chose de joli. ;)

J’ai donc commencé par créer une nouvelle application, nommée TpFluxRss. Étant donné que nous avons besoin de manipuler des flux RSS, il faut référencer l’assembly System.ServiceModel.Syndication.dll.
Voici la page de menu :

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

    <!--TitlePanel contient le nom de l'application et le titre de la page-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="TP Lecteur de flux RSS" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Menu" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <!--ContentPanel - placez tout contenu supplémentaire ici-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel>
            <Button Content="Voir les flux RSS" Tap="VoirFluxTap" />
            <Button Content="Ajouter un flux RSS" Tap="AjouterFluxTap" />
        </StackPanel>
           
    </Grid>
</Grid>

Avec le code-behind suivant :

public partial class MainPage : PhoneApplicationPage
{
    // Constructeur
    public MainPage()
    {
        InitializeComponent();

        if (IsolatedStorageSettings.ApplicationSettings.Contains("ListeUrl"))
        {
            PhoneApplicationService.Current.State["ListeUrl"] = IsolatedStorageSettings.ApplicationSettings["ListeUrl"];
        }
    }

    private void VoirFluxTap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        NavigationService.Navigate(new Uri("/VoirFlux.xaml", UriKind.Relative));
    }

    private void AjouterFluxTap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        NavigationService.Navigate(new Uri("/AjouterFlux.xaml", UriKind.Relative));
    }
}

On observe dans le constructeur qu’on commence par charger un objet que l’on met directement dans le dictionnaire d’état. Le nom de la clé nous fait pressentir qu’il s’agit de la liste des URL de flux RSS que notre utilisateur a saisi dans notre application. Ce qui nous permet de les faire persister d’un lancement à l’autre. Pour le reste, il s’agit d’afficher deux boutons qui nous renvoient sur d’autres pages, les pages VoirFlux.xaml et AjouterFlux.xaml (voir la figure suivante).

La page de menu du lecteur de flux RSS
La page de menu du lecteur de flux RSS

Commençons par la page AjouterFlux.xaml qui, sans surprises, affiche une page permettant de saisir des URL de flux RSS. Le XAML de la page est encore très simple :

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

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="TP Lecteur de flux RSS" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Ajouter un flux" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel>
            <TextBlock Text="Saisir une url" />
            <TextBox x:Name="Url" InputScope="Url" />
            <Button Content="Ajouter" Tap="AjouterTap" />
        </StackPanel>
    </Grid>
</Grid>

Il permet d’afficher une zone de texte, où le clavier sera adapté pour la saisie d’URL (InputScope="Url") et possédant un bouton pour faire l’ajout de l’URL, comme vous pouvez le voir sur la figure suivante.

La page d'ajout des flux du lecteur de flux RSS
La page d'ajout des flux du lecteur de flux RSS

C’est dans le code-behind que tout se passe :

public partial class AjouterFlux : PhoneApplicationPage
{
    private List<Uri> listeUrl;

    public AjouterFlux()
    {
        InitializeComponent();
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        Url.Text = "http://";
        if (PhoneApplicationService.Current.State.ContainsKey("ListeUrl"))
            listeUrl = (List<Uri>)PhoneApplicationService.Current.State["ListeUrl"];
        else
            listeUrl = new List<Uri>();
        base.OnNavigatedTo(e);
    }

    private void AjouterTap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        Uri uri;
        if (!Uri.TryCreate(Url.Text, UriKind.Absolute, out uri))
            MessageBox.Show("Le format de l'url est incorrect");
        else
        {
            listeUrl.Add(uri);
            IsolatedStorageSettings.ApplicationSettings["ListeUrl"] = listeUrl;
            PhoneApplicationService.Current.State["ListeUrl"] = listeUrl;
            MessageBox.Show("L'url a été correctement ajoutée");
        }
    }
}

On commence par obtenir la liste des URL potentiellement déjà chargée, ensuite nous l’ajoutons à la liste si elle n’existe pas déjà, puis nous mettons à jour cette liste dans le répertoire local ainsi que dans le dictionnaire d’état.

Maintenant, il s’agit de réaliser la page VoirFlux.xaml, qui contient la liste des titres des différents flux. Pour l’exemple, je m’en suis ajouté quelques uns, comme vous pouvez le voir sur la figure suivante.

La liste de tous les flux du lecteur de flux RSS
La liste de tous les flux du lecteur de flux RSS

Ici le XAML intéressant se situe dans la deuxième grille :

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

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="TP Lecteur de flux RSS" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="Voir les flux" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock x:Name="Chargement" HorizontalAlignment="Center" Foreground="Red" FontSize="20" FontWeight="Bold"  />
        <ListBox x:Name="ListBoxFlux" ItemsSource="{Binding ListeFlux}" Grid.Row="1" SelectionChanged="ListBox_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto" />
                            <RowDefinition Height="auto" />
                            <RowDefinition Height="auto" />
                        </Grid.RowDefinitions>
                        <Image Source="{Binding UrlImage}" Height="50" Width="50" />
                        <TextBlock Grid.Column="1" Text="{Binding Titre}" TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle3Style}" />
                        <TextBlock Grid.ColumnSpan="2" Grid.Row="1" Text="{Binding Description}" TextWrapping="Wrap" Margin="0 0 0 20" FontStyle="Italic" />
                        <Line X1="0" X2="480" Y1="0" Y2="0" StrokeThickness="5" Stroke="Blue" Grid.Row="2" Grid.ColumnSpan="2" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Grid>

On y remarque un TextBlock qui servira d’indicateur de chargement des flux, puis une ListBox qui est liée à la propriété ListeFlux. Le modèle des éléments de la ListBox affiche l’image du flux, via la liaison à la propriété UrlImage, le titre via la liaison à la propriété Titre ainsi que la description… Notons un unique effet d’esthétique permettant de séparer deux flux, via une magnifique ligne bleue. :p
Passons au code behind :

public partial class VoirFlux : PhoneApplicationPage, INotifyPropertyChanged
{
    private WebClient client;

    private ObservableCollection<Flux> listeFlux;
    public ObservableCollection<Flux> ListeFlux
    {
        get { return listeFlux; }
        set { NotifyPropertyChanged(ref listeFlux, value); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string nomPropriete)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    public VoirFlux()
    {
        InitializeComponent();
        DataContext = this;
    }

    protected override async void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        List<Uri> listeUrl;
        if (PhoneApplicationService.Current.State.ContainsKey("ListeUrl"))
            listeUrl = (List<Uri>)PhoneApplicationService.Current.State["ListeUrl"];
        else
            listeUrl = new List<Uri>();

        if (listeUrl.Count == 0)
        {
            MessageBox.Show("Vous devez d'abord ajouter des flux");
            if (NavigationService.CanGoBack)
                NavigationService.GoBack();
        }
        else
        {
            Chargement.Text = "Chargement en cours ...";
            Chargement.Visibility = Visibility.Visible;
            ListeFlux = new ObservableCollection<Flux>();
            client = new WebClient();

            queue = new Queue<Uri>();
            foreach (Uri uri in listeUrl)
            {
                try
                {
                    string rss = await client.DownloadStringTaskAsync(uri);
                    AjouteFlux(rss);
                }
                catch (Exception)
                {
                    MessageBox.Show("Impossible de lire le flux à l'adresse : " + uri + "\nVérifiez votre connexion internet");
                }
            }
            Chargement.Text = string.Empty;
            Chargement.Visibility = Visibility.Collapsed;
        }
        base.OnNavigatedTo(e);
    }

    private void AjouteFlux(string flux)
    {
        StringReader stringReader = new StringReader(flux);
        XmlReader xmlReader = XmlReader.Create(stringReader);
        SyndicationFeed feed = SyndicationFeed.Load(xmlReader);
        listeFlux.Add(ConstruitFlux(feed));
        PhoneApplicationService.Current.State["ListeFlux"] = ListeFlux.ToList();
    }

    private Flux ConstruitFlux(SyndicationFeed feed)
    {
        Flux flux = new Flux { Titre = feed.Title.Text, UrlImage = feed.ImageUrl, Description = feed.Description == null ? string.Empty : feed.Description.Text, ListeBillets = new List<Billet>() };
        foreach (SyndicationItem item in feed.Items.OrderByDescending(e => e.LastUpdatedTime.DateTime))
        {
            Uri url = item.Links.Select(e => e.Uri).FirstOrDefault();
            if (url != null)
            {
                Billet billet = new Billet { Id = url.AbsolutePath, DatePublication = item.LastUpdatedTime.DateTime, Titre = item.Title.Text, EstDejaLu = EstDejaLu(url.AbsolutePath), Url = url };
                flux.ListeBillets.Add(billet);
            }
        }
        return flux;
    }

    private bool EstDejaLu(string id)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains("ListeDejaLus"))
        {
            List<string> dejaLus = (List<string>)IsolatedStorageSettings.ApplicationSettings["ListeDejaLus"];
            bool any = dejaLus.Any(e => e == id);
            return any;
        }
        return false;
    }

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (ListBoxFlux.SelectedItem != null)
        {
            Flux flux = (Flux)ListBoxFlux.SelectedItem;
            PhoneApplicationService.Current.State["FluxCourant"] = flux;
            NavigationService.Navigate(new Uri("/VoirFluxPivot.xaml", UriKind.Relative));
            ListBoxFlux.SelectedItem = null;
        }
    }
}

C’est le code-behind le plus long de l’application. Nous voyons l’implémentation classique de INotifyPropertyChanged ainsi qu’une propriété ListeFlux qui est une ObservableCollection de Flux. L’objet Flux contiendra toutes les propriétés d’un Flux que nous souhaitons afficher sur la prochaine page :

public class Flux
{
    public string Titre { get; set; }
    public Uri UrlImage { get; set; }
    public string Description { get; set; }
    public List<Billet> ListeBillets { get; set; }
}

Un titre, l’URL de l’image, la description ainsi qu’une liste de billets, sachant que la classe Billet sera :

public class Billet
{
    public string Id { get; set; }
    public DateTime DatePublication { get; set; }
    public string Titre { get; set; }
    public Uri Url { get; set; }
    public bool EstDejaLu { get; set; }
}

Ensuite, il y a le chargement des flux. Voyez comme c’est facile grâce à await, une simple boucle et on n'en parle plus. ;)

La méthode AjouteFlux crée un objet SyndicationFeed comme on l’a déjà vu puis assemble un objet Flux à partir du SyndicationFeed. La liste d’objets Flux est ensuite mise à jour dans le dictionnaire d’état.
Notons que lors de l’assemblage, je dois déterminer si le billet a déjà été lu ou non. Pour cela, je tente de lire dans le répertoire local la liste de tous les billets déjà lus, identifiés par leur id. Si je le trouve, c’est que le billet est déjà lu. Cette liste est alimentée lorsqu’on visionne réellement le billet.

Enfin, l’événement de sélection d’un flux s’occupe de récupérer le flux sélectionné, de le mettre dans le dictionnaire d’état et de naviguer sur la page VoirFluxPivot.xaml.
Finalement, ce n’est pas très compliqué. Il faut juste enchainer tranquillement les actions sans rien oublier.

Passons à présent à la page VoirFluxPivot.xaml où le but est d’afficher dans un pivot la liste des flux, en se positionnant sur celui choisi. Ce qui nous permettra de naviguer de flux en flux en faisant un glissement de doigt. Chaque vue du pivot contiendra une ListBox avec tous les billets du flux en cours. Notons au passage que j’ai choisi de marquer les billets déjà lus en italique (voir la figure suivante).

La liste des billets d'un flux du lecteur de flux RSS
La liste des billets d'un flux du lecteur de flux RSS

Ici c’est le XAML qui est sans doute le plus compliqué. Nous avons besoin du contrôle Pivot. Notre contrôle Pivot est lié à la propriété ListeFlux :

<Grid x:Name="LayoutRoot" Background="Transparent">
    <phone:Pivot x:Name="PivotFlux" ItemsSource="{Binding ListeFlux}" Loaded="PivotFlux_Loaded">
        <phone:Pivot.HeaderTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Titre}" />
            </DataTemplate>
        </controls:Pivot.HeaderTemplate>
        <phone:Pivot.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Titre}" FontWeight="Bold" FontSize="20" />
                    <ListBox ItemsSource="{Binding ListeBillets}" SelectionChanged="ListeBoxBillets_SelectionChanged">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Titre}" Margin="0 0 0 40" FontStyle="{Binding EstDejaLu, Converter={StaticResource FontFamilyConverter}}" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </StackPanel>
            </DataTemplate>
        </controls:Pivot.ItemTemplate>
    </controls:Pivot>
</Grid>

Nous nous abonnons également à l’événement de chargement de Pivot. L’entête du Pivot est liée au titre du flux, via le modèle d’entête. Quant au corps du Pivot, il possède un modèle où il y a notamment une ListBox, liée à la propriété ListeBillets. Notons que nous nous sommes abonnés à l’événement de changement de sélection de la ListBox. Le corps de la ListBox consiste à lier le titre du billet avec une subtilité où j’utilise un converter pour positionner le style de la police en fonction de si le billet a déjà été lu ou pas. Ce converter devra donc être déclaré dans les ressources :

<phone:PhoneApplicationPage.Resources>
    <converter:FontFamilyConverter x:Key="FontFamilyConverter" />
</phone:PhoneApplicationPage.Resources>

Avec l’espace de nom qui va bien :

xmlns:converter="clr-namespace:TpFluxRss"

La classe de conversion devra donc convertir un booléen en FontStyle :

public class FontFamilyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? FontStyles.Italic : FontStyles.Normal;
    }

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

Ici, nul besoin d’implémenter la méthode de conversion de FontStyle vers booléen, elle ne sera pas utilisée. La conversion consiste à avoir le style italique si le booléen est vrai, normal sinon.
Reste le code-behind :

public partial class VoirFluxPivot : PhoneApplicationPage, INotifyPropertyChanged
{
    private ObservableCollection<Flux> listeFlux;
    public ObservableCollection<Flux> ListeFlux
    {
        get { return listeFlux; }
        set { NotifyPropertyChanged(ref listeFlux, value); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string nomPropriete)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    public VoirFluxPivot()
    {
        InitializeComponent();
        DataContext = this;
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        ListeFlux = new ObservableCollection<Flux>((List<Flux>)PhoneApplicationService.Current.State["ListeFlux"]);

        base.OnNavigatedTo(e);
    }

    private void PivotFlux_Loaded(object sender, RoutedEventArgs e)
    {
        PivotFlux.SelectedItem = (Flux)PhoneApplicationService.Current.State["FluxCourant"];
        PivotFlux.SelectionChanged += PivotFlux_SelectionChanged;
    }

    private void ListeBoxBillets_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ListBox listBox = (ListBox)sender;
        if (listBox.SelectedItem != null)
        {
            Billet billet = (Billet)listBox.SelectedItem;
            PhoneApplicationService.Current.State["BilletCourrant"] = billet;
            NavigationService.Navigate(new Uri("/VoirBillet.xaml", UriKind.Relative));
            listBox.SelectedItem = null;
        }
    }

    private void PivotFlux_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        PhoneApplicationService.Current.State["FluxCourant"] = PivotFlux.SelectedItem;
    }
}

On retrouve notre propriété ListeFlux, ainsi que son implémentation classique. On peut voir que lors de l’arrivée sur la page, je récupère dans le dictionnaire d’état la liste des flux et que je construis une ObservableCollection avec, pour la mettre dans la propriété ListeFlux.
Dans l’événement de chargement du Pivot, j’en profite pour récupérer le flux courant et ainsi positionner le pivot sur le flux précédemment sélectionné dans la ListBox.
Enfin, lors de la sélection d’un billet dans la ListBox, il ne me reste plus qu’à positionner le billet dans le dictionnaire d’état et de naviguer sur la page VoirBillet.xaml.
Sachant que la ListBox est dans un contrôle qui possède plusieurs vues, il n’est pas possible d’obtenir la ListBox en cours par son nom. En effet, en fait il y a autant de ListBox que de flux. On utilisera donc le paramètre sender de l’événement de sélection qui nous donne la ListBox concernée.
Remarquons que nous devons modifier le flux courant lorsque nous changeons d’élément dans le Pivot. On utilise l’événement de changement de sélection auquel on s’abonne à la fin du chargement du Pivot. On fait ceci une fois que l’élément en cours est positionné, afin que l’événement ne soit pas levé.

Il ne reste plus que la page VoirBillet.xaml, qui est plutôt simple. En tous cas, le XAML ne contient que le contrôle WebBrowser :

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

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="TP Lecteur de flux RSS" Style="{StaticResource PhoneTextNormalStyle}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <phone:WebBrowser x:Name="PageBillet" NavigationFailed="PageBillet_NavigationFailed" />
    </Grid>
</Grid>

Nous nous sommes quand même abonnés à l’événement d’erreur de navigation pour afficher un petit message, au cas où…
Dans le code-behind, nous aurons juste besoin de démarrer la navigation et de marquer le billet comme lu :

public partial class VoirBillet : PhoneApplicationPage
{
    private Billet billet;

    public VoirBillet()
    {
        InitializeComponent();
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        billet = (Billet)PhoneApplicationService.Current.State["BilletCourrant"];
        billet.EstDejaLu = true;

        List<string> dejaLus;
        if (IsolatedStorageSettings.ApplicationSettings.Contains("ListeDejaLus"))
            dejaLus = (List<string>)IsolatedStorageSettings.ApplicationSettings["ListeDejaLus"];
        else
            dejaLus = new List<string>();
        if (!dejaLus.Any(elt => elt == billet.Id))
            dejaLus.Add(billet.Id);

        IsolatedStorageSettings.ApplicationSettings["ListeDejaLus"] = dejaLus;
        PageBillet.Navigate(billet.Url);
        base.OnNavigatedTo(e);
    }

    private void PageBillet_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
    {
        MessageBox.Show("Impossible de charger la page, vérifiez votre connexion internet");
    }
}

N’oublions pas de faire persister la liste des billets lus dans le répertoire local…
Et voici le résultat à la figure suivante.

Affichage d'un billet dans le lecteur de flux RSS
Affichage d'un billet dans le lecteur de flux RSS

Et voilà, notre application commence à être fonctionnelle. J’espère que vous aurez réussi à maîtriser notamment les histoires de binding et de sélection d’éléments. C’est un travail très classique dans la création d’application Windows Phone avec le XAML. Ce sont des éléments que vous devez maîtriser. N’hésitez pas à refaire ce TP si vous ne l’avez pas complètement réussi.
Vous pouvez aussi compléter cette application qui est loin d’être parfaite, vous entraîner à faire des jolies interfaces dans le style Modern UI, rajouter des transitions, des animations, etc.

Aller plus loin

Vous avez remarqué que dans la correction, je ne stocke dans le dictionnaire d’état que les objets que j’ai moi-même créés, à savoir les objets Flux et Billet. Vous me direz, pourquoi ne pas mettre directement les objets SyndicationFeed et SyndicationItem ?
Eh bien pour une bonne raison, ceci introduit un bug pas forcément visible du premier coup. Ce bug survient lorsque notre application est désactivée, par exemple si l’on reçoit un coup de fil ou si on affiche le menu. Cela vient du fait que les objets SyndicationFeed et SyndicationItem ne sont pas sérialisables, ce qui fait que lorsqu’on passe en désactivé, la sérialisation échoue et fait planter l’application. En effet, le dictionnaire d’état est un emplacement qui est disponible tant que notre application est désactivée. Il est plus rapide d’accès que le répertoire local, mais ceci implique que les objets qu’on y stocke soient sérialisables.
Pour résoudre ce point, il suffit de ne pas mettre ces objets dans le dictionnaire d’état et par exemple de mettre plutôt directement nos objets Flux et Billet, comme ce que j’ai proposé en correction. Ceci est d’ailleurs plus logique.

L’autre solution est de vider le dictionnaire d’état dans l’événement de désactivation de l’application, avec par exemple :

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    PhoneApplicationService.Current.State["ListeFlux"] = null;
}

Évidemment, il faut re-remplir le dictionnaire d’état dans la méthode d’activation de l’application :

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    // code à faire, utilisation du WebClient pour recharger tous les flux
}

Ou alors renvoyer l’utilisateur sur la page où s’effectue ce chargement, en utilisant le service de navigation.

Vous serez aussi d’accord avec moi, l’ajout de l’URL d’un flux n’est pas des plus commodes. Si vous voulez approfondir l’application, vous pouvez également essayer d’importer des flux à partir d’une autre source… depuis un autre syndicateur de flux, à partir d’un fichier OPML.

Exemple de certificat de réussite
Exemple de certificat de réussite