Dopo qualche giorno riprendiamo la serie di post introduttivi allo sviluppo Metro per Windows 8 con Visual Basic. Finalmente iniziamo a scrivere codice ed a questo proposito faccio le seguenti precisazioni:
- l'app che iniziamo a creare oggi (e che sarà completata nel prossimo post) è un news reader, ossia aggrega e presenta feed RSS.
- sebbene questa possa sembrare la più banale delle app, e in effetti può esserlo, è la palestra ideale per capire una serie di concetti come l'uso di XAML, il pattern Async, l'uso dei contract. Queste sono tutte specificità di Windows 8/Metro e vi renderete conto presto del fatto che scegliere di realizzare un'applicazione semplice o banale permette di concentrarsi sugli aspetti nuovi e tipici di Metro.
Il codice completo sarà disponibile per il download dal prossimo post. Parlando per immagini, oggi arriveremo a questo:

Decisamente essenziale, non c'è dubbio. La prossima volta arriveremo però a definire e utilizzare l'app bar:

Qui scopriremo anche che non dovremo impazzire a creare icone Metro, perché Visual Studio le genera per noi. Inoltre implementeremo lo Share contract integrato col sistema operativo:

Quindi news reader si, ma di classe
Prima di andare avanti, vi consiglio di aprire in un'altra istanza del browser il seguente documento MSDN, del quale andremo a prendere alcuni frammenti di codice, riadattati al nostro caso: Create your first Metro style app with C# or Visual Basic
Creazione del progetto
In Visual Studio 11 creiamo un nuovo progetto Metro con VB, scegliendo il template Blank Application. Questo genera un file XAML vuoto e senza dati di esempio. Così evitiamo confusioni e costruiremo il tutto a manina.
I dati
L'app si occuperà di scaricare e presentare informazioni provenienti dal feed ufficiale delle notizie su Visual Basic di MSDN. La prima cosa da fare è quindi creare una classe che chiameremo FeedItem, che rappresenta la singola notizia:
Public Class FeedItem
Public Property Title As String = String.Empty
Public Property Author As String = String.Empty
Public Property Content As String = String.Empty
Public Property PubDate As Date
Public Property Link As Uri
End Class
Ci occorre poi una classe che rappresenti una collezione di notizie. Noi interrogheremo un solo feed, ma tale classe ci consente di avere più feed come peraltro dimostrato nella pagina MSDN linkata in precedenza. La classe si chiama FeedData ed è la seguente:
Imports Windows.Web.Syndication
Public Class FeedData
Public Property Title As String = String.Empty
Public Property Description As String = String.Empty
Public Property PubDate As Date
Public Property Link As Uri
Private _items As New List(Of FeedItem)
Public ReadOnly Property Items As List(Of FeedItem)
Get
Return _items
End Get
End Property
End Class
Prima considerazione: WinRT offre una strada già pronta per accedere ai feed RSS e Atom attraverso il namespace Windows.Web.Syndication. Ora è necessario creare una classe che si occupi di interrogare il feed e di esporre le notizie a codice chiamante. La chiamiamo FeedDataSource e questa è la prima parte:
Imports Windows.Web.Syndication
' FeedDataSource
' Holds a collection of blog feeds (FeedData), and contains methods needed to
' retreive the feeds.
Public Class FeedDataSource
Private _Feeds As New ObservableCollection(Of FeedData)()
Public ReadOnly Property Feeds() As ObservableCollection(Of FeedData)
Get
Return Me._Feeds
End Get
End Property
Public Async Function GetFeedsAsync() As Task
Dim feed1 As Task(Of FeedData) =
GetFeedAsync("http://services.social.microsoft.com/feeds/feed/VB_featured_resources")
Me.Feeds.Add(Await feed1)
End Function
La collection di feed è di banale interpretazione. E' importante capire che usiamo una collection perché potremmo avere più feed, sebbene qui ne interroghiamo uno solo. Diciamo che ci piace guardare avanti :-)
Ciò che dobbiamo invece considerare è la definizione del metodo GetFeedsAsync. Tale metodo legge in modo asincrono il contenuto del feed e lo aggiunge alla collezione. La sua definizione contiene la parola chiave Async e restituisce un oggetto Task, due cose che vanno a braccetto. L'uso di Async è necessario per dire che l'operazione è asincrona, che è normale in Windows 8. Poiché l'operazione è asincrona e non sequenziale, si restituisce un oggetto Task, che già conoscete se avete letto almeno qualcosa della Task Parallel Library. Il suffisso Async è ncessario per convenzione nelle definizioni dei metodi asincroni.
L'uso della parola chiave Async è necessario anche per un altro motivo: il metodo legge il contenuto dei feed attraverso un altro metodo asincrono chiamato GetFeedAsync. Eccone il codice, che completa la definizione della classe:
Private Async Function GetFeedAsync(feedUriString As String) As Task(Of FeedData)
Dim Client As New SyndicationClient
Dim FeedUri As New Uri(feedUriString)
Try
Dim Feed As SyndicationFeed = Await Client.RetrieveFeedAsync(FeedUri)
' This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
' Process the feed and copy the data we want into our FeedData and FeedItem classes.
Dim FeedData As New FeedData
FeedData.Link = FeedUri
FeedData.Title = Feed.Title.Text
If Feed.Subtitle.Text IsNot Nothing Then
FeedData.Description = Feed.Subtitle.Text
End If
' Use the date of the latest post as the last updated date.
FeedData.PubDate = Feed.Items(0).PublishedDate.DateTime
For Each Item As SyndicationItem In Feed.Items
Dim FeedItem As New FeedItem
FeedItem.Title = Item.Title.Text
FeedItem.PubDate = Item.PublishedDate.DateTime
If Item.Authors.Any Then FeedItem.Author = Item.Authors(0).Name
FeedItem.Content = Item.Summary.Text
FeedItem.Link = Item.Links(0).Uri
FeedData.Items.Add(FeedItem)
Next
Return FeedData
Catch Ex As Exception
Return Nothing
End Try
End Function
End Class
La classe SyndicationClient fornisce un'infrastruttura nativa per leggere feed attraverso il suo metodo Client.RetrieveFeedAsync, il cui risultato è di tipo SyndicationFeed. Come si può facilmente intuire, quest'ultima classe rappresenta un feed e le sue notizie. Il metodo in questione è preceduto da Await, che vuol dire: esegui l'operazione in modo asincrono ed attendi il suo completamento per assegnare il risultato alla variabile.
Per il resto, si fa uso della classe SyndicationItem che rappresenta una singola notizia nel feed (ossia nella collection di notizie esposte dal SyndicationFeed). Ciclare tale collection permette poi di creare tante istanze della classe FeedItem quante sono le notizie. L'uso di tale classe è veramente intuitivo e non mi dilungo troppo su questa, descritta comunque nella documentazione MSDN.
Seguendo l'esempio della documentazione stessa, nel file App.xaml.vb (ve lo ricordate? lo stesso di WPF, Silverlight, Windows Phone....) esponiamo la sorgente dati istanziata:
Public Shared DataSource As FeedDataSource
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DataSource = New FeedDataSource
End Sub
Passiamo ora ad implementare altre parti dell'infrastruttura.
Converters
Anche in Metro esiste il concetto dei value converters, da richiamare da XAML. Ed esattamente come nelle altre tecnologie, come WPF/Silverlight/Phone, i converters si creano attraverso l'implementazione dell'interfaccia IValueConverter. Il che significa che non avremo difficoltà!
Con particolare riguardo al nostro caso, ci serve un value converter per formattare le date provenienti dal feed. Prendiamo ancora spunto dalla documentazione per implementare una classe chiamata DateConverter, definita come segue e che potrà avere molteplici utilizzi:
Imports Windows.Globalization.DateTimeFormatting
Public Class DateConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As String) _
As Object Implements IValueConverter.Convert
If value Is Nothing Then
Throw New ArgumentNullException("value", "Value cannot be null.")
End If
If Not GetType(DateTime).Equals(value.GetType()) Then
Throw New ArgumentException("Value must be of type DateTime.", "value")
End If
Dim dt As DateTime = DirectCast(value, DateTime)
If parameter Is Nothing Then
' Date "7/27/2011 9:30:59 AM" returns "7/27/2011"
Return DateTimeFormatter.ShortDate.Format(dt)
ElseIf DirectCast(parameter, String) = "day" Then
' Date "7/27/2011 9:30:59 AM" returns "27"
Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{day.integer(2)}")
Return dateFormatter.Format(dt)
ElseIf DirectCast(parameter, String) = "month" Then
' Date "7/27/2011 9:30:59 AM" returns "JUL"
Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{month.abbreviated(3)}")
Return dateFormatter.Format(dt).ToUpper()
ElseIf DirectCast(parameter, String) = "year" Then
' Date "7/27/2011 9:30:59 AM" returns "2011"
Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{year.full}")
Return dateFormatter.Format(dt)
Else
' Requested format is unknown. Return in the original format.
Return dt.ToString()
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As String) _
As Object Implements Windows.UI.Xaml.Data.IValueConverter.ConvertBack
Dim StrValue As String = DirectCast(value, String)
Dim Result As Date
If DateTime.TryParse(StrValue, Result) Then
Return Result
End If
Return DependencyProperty.UnsetValue
End Function
End Class
La lettura dei commenti aiuta nella comprensione, si noti più che altro come il namespace Windows.Globalization.DateTimeFormatting consenta di formattare date in modo piuttosto agevole.
L'interfaccia, ovvero la leggenda del Santo XAML
E' giunta l'ora di passare alla definizione della UI e forse sarà questo il punto in cui Windows 8 e Metro vi spaventeranno meno e sarà forse questo il momento in cui benedirete il giorno in cui avete deciso di investire su WPF o Silverlight. Passiamo al designer facendo doppio click sul file BlankPage.xaml in Solution Explorer. Noterete subito un oggetto Page, certamente familiare. Ci interessa definire e organizzare all'interno del layout costituito da una Grid radice:
- visualizzazione del testo di benvenuto
- titolo del feed
- un elenco delle notizie
- un'anteprima del contenuto
Ancora una volta ci viene incontro il codice della documentazione che, se avete confidenza con XAML e data-binding, vi risulterà davvero semplice (da mettere all'interno della Grid radice):
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Title -->
<TextBlock x:Name="TitleText" Text="{Binding Title}"
VerticalAlignment="Center" FontSize="48" Margin="56,0,0,0"/>
<!-- Content -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" MinWidth="320" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<!-- Left column -->
<!-- The default value of Grid.Column is 0, so we do not need to set it
to make the ListView show up in the first column. -->
<ListView x:Name="ItemListView" SelectionChanged="ItemListView_SelectionChanged"
ItemsSource="{Binding Items}"
Margin="60,0,0,10">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"
FontSize="24" Margin="5,0,0,0" TextWrapping="Wrap" />
<TextBlock Text="{Binding Author}"
FontSize="16" Margin="15,0,0,0"/>
<TextBlock Text="{Binding Path=PubDate, Converter={StaticResource dateConverter}}"
FontSize="16" Margin="15,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Right column -->
<!-- We use a Grid here instead of a StackPanel so that the WebView sizes correctly. -->
<Grid DataContext="{Binding ElementName=ItemListView, Path=SelectedItem}"
Grid.Column="1" Margin="25,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock x:Name="PostTitleText" Text="{Binding Title}" FontSize="24"/>
<WebView x:Name="ContentView" Grid.Row="1" Margin="0,5,20,20"/>
</Grid>
</Grid>
</Grid>
Non mi posso chiaramente soffermare su come si definisce il layout in XAML o su controlli come TextBlock e StackPanel. Focalizziamo l'attenzione sul fatto che in WinRT si usa una ListView anziché ListBox, ma il concetto dei template rimane invariato. Si noti l'utilizzo del converter definito poc'anzi. Nella seconda Grid, invece, si noti l'oggetto WebView che viene utilizzato per presentare l'anteprima del contenuto.
Esattamente come altrove, bisogna dichiarare le risorse per poterle utilizzare e questo è il caso del nostro converter. Quindi nel file App.xaml aggiungiamo un ResourceDictionary che contenga la definizione della risorsa:
<ResourceDictionary>
<local:DateConverter x:Key="dateConverter" />
</ResourceDictionary>
E' interessante sottolineare che non c'è necessità di definire il namespace local, poiché Visual Studio 11 lo fa per noi nella definizione dell'elemento Application. Si passa poi al code-behind in cui dobbiamo specificare cosa avviene quando la pagina viene raggiunta. Questo si fa con l'override del metodo OnNavigatedTo, passato in forma asincrona:
Protected Overrides Async Sub OnNavigatedTo (e As Navigation.NavigationEventArgs)
Dim _feedDataSource As FeedDataSource = App.DataSource
If _feedDataSource.Feeds.Any = False Then
Await _feedDataSource.GetFeedsAsync
End If
Me.DataContext = (_feedDataSource.Feeds).First
End Sub
Il codice recupera l'istanza della sorgente dati e, nel caso questa non contenga elementi, invoca il suo metodo GetFeedsAsync per il caricamento. Infine assegna al contesto dati della pagina il primo dei feed (che in questo caso è anche l'unico).
Infine, gestiamo l'evento SelectionChanged per la ListView:
Private Sub ItemListView_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
Dim _feedItem As FeedItem = e.AddedItems(0)
If _feedItem IsNot Nothing Then
' Navigate the WebView to the blog post content HTML string.
contentView.NavigateToString(_feedItem.Content)
End If
End Sub
Questo recupera l'elemento selezionato e passa il suo contenuto all'oggetto WebView attraverso il metodo NavigateToString.
Test
Se ora provate ad avviare l'app otterrete il risultato mostrato nella prima figura di questo post. Complimenti, avete appena creato la vostra prima Metro app con Visual Basic 11! 
Ricapitolando, in questo post abbiamo capito come lavorare in modo asincrono, come definire l'interfaccia tramite XAML, come utilizzare alcuni oggetti offerti da WinRT. Nel prossimo post implementeremo caratteristiche specifiche di Windows 8 e daremo un'occhiata al discorso della rotazione del device.
Alessandro