Questo post è nato con uno scopo diverso da quello dell'implementazione di un Rss Reader, dato che, è sufficiente cercare su internet per trovarne di belli e pronti, ma per enfatizzare alcune proprietà molto interessanti dei controlli WPF, come la possibilità di eseguire il raggruppamento dei dati nei controlli ListBox o ListView. Piccola parentesi: per chi volesse conoscere in dettaglio le specifiche RSS 2.0 le può trovare qui. Iniziamo sviluppando un set di classi come riassunto dal seguente Class Diagram:
Nel resto dell'esempio saranno prese in considerazione solo istanze di tipo Rss e RssItem. Generalmente, senza considerare gli elementi opzionali, la struttura xml di un documento RSS è il seguente:
1 <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" ...
2 <channel>
3 <title>Il blog di Pietro Libro</title>
4 <link>http://blogs.ugidotnet.org/PietroLibroBlog/Default.aspx</link>
5 <description />
6 <language>it-IT</language>
7 <copyright>Pietro Libro</copyright>
8 <managingEditor></managingEditor>
9 <generator>Subtext Version 1.9.5.176</generator>
10 ...
11 <item>
12 <title>Adeona, il software &quot;salva&quot; notebook</title>
13 <category>OT</category>
14 <category>Varie ed eventuali</category>
15 <link>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/08/26/93820.aspx</link>
16 <description>...</description>
17 ....
18 </item>
Per leggere un tale documento, utilizziamo un'istanza della classe XDocument, presente nel namespace System.Xml.Linq, contenente metodi che permettono la navigazione dei nodi del documento in maniera molto semplice. Se a questo aggiungiamo la potenza di Linq, veramente c'è poco da dire. Prima di tutto creiamo un'istanza di XDocument e leggiamo il documento passando direttamente l'uri del fornitore del feed RSS:
1 XDocument xDocument = new XDocument();
2 xDocument = XDocument.Load(blogUri);
Per recuperare tutti gli elementi item contenuti nel documento, utilizziamo Linq. Per far questo utilizziamo il metodo Descendants passando come parametro la stringa "Item":
1 _items =
2 (from item in xDocument.Descendants("item")
3 select new RssItem
4 {
5 Title = item.Element("title").Value,
6 Description =item.Element ("description").Value,
7 Category = item.Element("category") == null ? "Nessuna Categoria" : item.Element("category").Value ,
8 Link = item.Element("link") == null ? "": item.Element("link").Value
9 }).ToList<RssItem>();
_items è una lista tipizzata di oggetti RssItem.
Per presentare i dati recuperati utilizziamo una Window WPF. Il nostro obiettivo è mostrare i dati raggruppati per categoria, ovvero ottenere qualcosa del tipo:
Per raggiungere il goal, invece di utilizzare la proprietà ItemSource esposta dal controllo ListBox, utilizziamo un'istanza di oggetto di tipo CollectionViewSource la quale altro non è una che un Proxy della classe CollectionView. Quest'ultima permette di eseguire operazioni di raggruppamento, ordinamento, navigazione e ricerca su di una collezione di dati. Definiamo un'istanza di tipo CollectionViewSource nella collezione Window.Resources della nostra Window WPF:
1 <CollectionViewSource x:Key="cvs">
2 <CollectionViewSource.GroupDescriptions>
3 <PropertyGroupDescription PropertyName="Category"/>
4 </CollectionViewSource.GroupDescriptions>
5 </CollectionViewSource>
Così facendo, aggiungiamo alla collezione GroupDescriptions un oggetto di tipo GroupDescription che descrive il criterio in base al quale i dati devono essere raggruppati nella vista, nello specifico in base al valore della proprietà Category.
Tramite codice XAML, andiamo a descrivere come devono essere visualizzati i nostri Gruppi:
1 <Style x:Key="CustomGroupStyle" TargetType="{x:Type GroupItem}">
2 <Setter Property="Margin" Value="0,0,0,5"/>
3 <Setter Property="Template">
4 <Setter.Value>
5 <ControlTemplate TargetType="{x:Type GroupItem}">
6 <Expander IsExpanded="False" BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1">
7 <Expander.Header>
8 <DockPanel>
9 <TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
10 <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount,
11 Converter={StaticResource CustomConverter},
12 ConverterParameter=' - Numero di post ({0}) '}"/>
13 </DockPanel>
14 </Expander.Header>
15 <Expander.Content>
16 <ItemsPresenter />
17 </Expander.Content>
18 </Expander>
19 </ControlTemplate>
20 </Setter.Value>
21 </Setter>
22 </Style>
E gli elementi in essi contenuti:
1 <DataTemplate x:Key="ItemTemplate">
2 <StackPanel>
3 <TextBlock Text="{Binding Path=Title}"></TextBlock>
4 <TextBlock>
5 <Hyperlink NavigateUri="{Binding Path=Link}" Click="Hyperlink_Click">Visualizza</Hyperlink>
6 </TextBlock>
7 </StackPanel>
8 </DataTemplate>
Non resta che definire il controllo ListBox:
1 <ListBox Name="lstBox" ItemTemplate="{StaticResource ItemTemplate}" Margin="12,41,12,12">
2 <ListBox.GroupStyle>
3 <GroupStyle ContainerStyle="{StaticResource CustomGroupStyle}"/>
4 </ListBox.GroupStyle>
5 </ListBox>
Utilizzando un controllo Button per recuperare il contenuto del documento RSS, scriviamo il seguente codice C#:
1 LinqRss.Rss rss = new LinqRss.Rss();
2 if (rss.Load("http://blogs.ugidotnet.org/PietroLibroBlog/Rss.aspx"))
3 {
4 CollectionViewSource collectionViewSource = this.FindResource("cvs") as CollectionViewSource ;
5 collectionViewSource.Source = rss.Items;
6 lstBox.ItemsSource = collectionViewSource.View;
7 }
In sintesi, recuperata l'istanza di CollectionViewSource contenuta nelle risorse della nostra Window WPF, impostiamo la proprietà Source dell'oggetto CollectionViewSource utilizzando la collezione Items esposta dall'istanza Rss. Non resta che impostare la proprietà ItemSource del controllo ListBox con la proprietà View esposta da CollectionViewSource. Osservazione: i GroupItem sono costituiti da controlli Expander. Nell'ItemTemplate è presente anche un HyperLink che permette la navigazione verso il post d'interesse.
Codice C# e XAML