| Comments

This is part 4 in a series on getting started with Silverlight.  To view the index to the series click hereYou can download the completed project files for this sample application in C# or Visual Basic.

In the previous step 3 we did a lot of work to get back our data from a public web service and display it in a control.  The DataGrid control we used, however, isn’t really the UI we’re looking for so let’s define what we want.  To do this we’re going to use an ItemsControl and a DataTemplate.  This will introduce us to XAML binding syntax and how to leverage more powerful data binding information.

Starting the UI over – delete the DataGrid

Well, after all that work, let’s delete the DataGrid and just the DataGrid.  We won’t be needing the assembly reference or the xmlns:data values anymore as well, so go ahead and remove them.

Replace the DataGrid with an ItemsControl like this:

   1: <ItemsControl x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1" />

Now here is where Blend is going to be helpful for us again.  Go into Blend and we’re going to modify the ItemTemplate for the ItemsControl.  ItemsControl is just essentially a rendering control that does what we tell it to.  If we do nothing but change the DataGrid to an ItemsControl and run our application this is what we’ll get:

ItemsControl rendering with no template

The ItemsControl has no idea how we want to display the data, so we have to tell it how in a template…back to Blend.  The general concept we’re going to go for is this (repeated of course):

ItemsControl template mockup

where the box is the avatar of the user posting the message.  Using our knowledge of layout from the previous steps we can create the template easily.  In Blend, locate the SearchResults object in the tree and right click to edit the ItemsTemplate (under the Generated Templates section):

Edit Generated Items menu option

we now have an empty template we can put stuff in.  I called mine SearchResultsTemplate in the dialog that came up.  Now we are in layout editing mode and can drag/move/etc items in our layout.  I created a Grid-based layout and here’s my resulting XAML for the template:

   1: <DataTemplate x:Key="SearchResultsTemplate">
   2:     <Grid Margin="4,0,4,8" d:DesignWidth="446" d:DesignHeight="68">
   3:         <Grid.ColumnDefinitions>
   4:             <ColumnDefinition Width="Auto" />
   5:             <ColumnDefinition Width="*" />
   6:         </Grid.ColumnDefinitions>
   7:         <Border VerticalAlignment="Top" Margin="8" Padding="2" Background="White">
   8:             <Image Width="40" Height="40" />
   9:         </Border>
  10:  
  11:         <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="0,4,0,0">
  12:             <TextBlock x:Name="AuthorName" FontWeight="Bold" />
  13:             <Grid Margin="0,6,0,0">
  14:                 <Grid.RowDefinitions>
  15:                     <RowDefinition Height="Auto" />
  16:                     <RowDefinition Height="2" />
  17:                     <RowDefinition Height="Auto" />
  18:                 </Grid.RowDefinitions>
  19:                 <TextBlock x:Name="TweetMessage" TextWrapping="Wrap" />
  20:                 <TextBlock x:Name="PublishDateLabel" Grid.Row="2"  />
  21:             </Grid>
  22:         </StackPanel>
  23:     </Grid>
  24: </DataTemplate>

I’m also putting the ItemsControl itself into a ScrollViewer since the ItemsControl doesn’t natively provide a scrolling view:

   1: <ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" BorderThickness="1">
   2:         <ItemsControl x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1" ItemTemplate="{StaticResource SearchResultsTemplate}" />
   3:     </ScrollViewer>

Now all we have is a template, but we have to tell that template what to do with the data it will be receiving.

The XAML binding syntax

Here’s where our binding syntax is going to come in.  You see, ItemsControl is getting data to it (remember we haven’t changed our code so the SearchResults.ItemsSource is still being set to our PagedCollectionView.  To map our model elements to our template we need to use Binding.  The basic XAML binding syntax is:

{Binding Path=<some-data-path>, Mode=<binding mode>}

There are more advanced features you could get into, but this is the basic and we’ll start here.  For instance, to bind our Image element in our template to our Avatar from TwitterSearchResult model, it will look like this:

   1: <Image Width="40" Height="40" Source="{Binding Path=Avatar, Mode=OneWay}" />

And to bind the Author to the AuthorName element like this:

   1: <TextBlock x:Name="AuthorName" FontWeight="Bold" Text="{Binding Path=Author, Mode=OneWay}" />

In both of these we are using OneWay syntax because we don’t need to have it be TwoWay as we aren’t changing data back.  For the PublishDate, we want to provide some explicit formatting of the data.  We can do this through Value Converters.

Building a Value Converter

Value converters are classes that implement IValueConverter, which provides a Convert and ConvertBack methods.  For our PublishDate we’re going to basically allow explicit formatting of the DateTime object.  We’ll create a DateTimeConverter.cs class in a folder in our project called Converters.  The class looks like this:

   1: using System;
   2: using System.Threading;
   3: using System.Windows.Data;
   4:  
   5: namespace TwitterSearchMonitor.Converters
   6: {
   7:     /*
   8:      * Use this converter for formatting dates in XAML databinding
   9:      * Example:
  10:      *  Text="{Binding Path=PublishDate, Converter={StaticResource DateTimeFormatter}, ConverterParameter=MMM yy}" />
  11:      * 
  12:      * */
  13:     public class DateTimeConverter : IValueConverter
  14:     {
  15:         #region IValueConverter Members
  16:  
  17:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  18:         {
  19:             DateTime? bindingDate = value as DateTime?;
  20:  
  21:             if (culture == null)
  22:             {
  23:                 culture = Thread.CurrentThread.CurrentUICulture;
  24:             }
  25:  
  26:             if (bindingDate != null)
  27:             {
  28:                 string dateTimeFormat = parameter as string;
  29:                 return bindingDate.Value.ToString(dateTimeFormat, culture);
  30:             }
  31:  
  32:             return string.Empty;
  33:         }
  34:  
  35:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  36:         {
  37:             throw new NotImplementedException();
  38:         }
  39:  
  40:         #endregion
  41:     }
  42: }

Now to use this we'll go back to our XAML page that will be using this (Search.xaml) and add an xmlns declaration and a resource.  The xmlns we’ll use looks like this:

   1: xmlns:converters="clr-namespace:TwitterSearchMonitor.Converters"

and then in the Resources section of the XAML (where the other template is defined) we’ll add a resource that points to the converter:

   1: <navigation:Page.Resources>
   2:         <converters:DateTimeConverter x:Key="DateTimeFormatter" />
   3: ...

With these two things in place we can use our converter on our PublishDateLabel element like this:

   1: <TextBlock x:Name="PublishDateLabel" Text="{Binding Path=PublishDate, 
   2:         Converter={StaticResource DateTimeFormatter},
   3:         ConverterParameter=dd-MMM-yyyy hh:mm tt}" Grid.Row="2"  />

This tells XAML that it should run the IValueConverter to get the rendered output.  Our result is the explicit formatting of data that we want.  The result of all this additional syntax for binding now shows the rendering in our desired mockup:

Rendered ItemsControl data template

(yes I know that ‘twitpic’ as a search shows some interesting results…but you can count on it to have fast refreshing data as a search term!)

Great!  That wasn’t that difficult, was it?  This binding syntax will be essential to building applications for you.

Storing some settings and configuration data

One of the things that would be helpful for our application is to store the last tweet ID so that the next time the application is run, we can start where we left off without starting over.  Additionally it would be cool to save the search term history so that we can view it in our History navigation point later.

To accomplish this, we’ll be using Isolated Storage available in Silverlight.  Isolated Storage enables a low-trust user-specific location for storing simple data.  For some more information on Isolated Storage:

To do this I’m going to add a Helper class to our Model folder.  This helper class will assist us in saving/reading data from our isolated storage location.  The basics of IsolatedStorage are that you create a file to which you can read data from or write data to if you want.  In our use we’ll use IsolatedStorageSettings for saving simple name/value pair data (search term/last ID).  Here’s the contents of the Helper.cs class:

   1: using System.IO.IsolatedStorage;
   2:  
   3: namespace TwitterSearchMonitor.Model
   4: {
   5:     public class Helper
   6:     {
   7:         internal static string GetLatestTweetId(string searchTerm)
   8:         {
   9:             if (IsolatedStorageSettings.ApplicationSettings.Contains(searchTerm))
  10:             {
  11:                 return IsolatedStorageSettings.ApplicationSettings[searchTerm].ToString();
  12:             }
  13:             else
  14:             {
  15:                 return "0";
  16:             }
  17:         }
  18:  
  19:         internal static void SaveLatestTweetId(string searchTerm, string latestId)
  20:         {
  21:             if (IsolatedStorageSettings.ApplicationSettings.Contains(searchTerm))
  22:             {
  23:                 IsolatedStorageSettings.ApplicationSettings[searchTerm] = latestId;
  24:             }
  25:             else
  26:             {
  27:                 IsolatedStorageSettings.ApplicationSettings.Add(searchTerm, latestId);
  28:             }
  29:         }
  30:     }
  31: }

Now in our Search.xaml.cs we’ll add the following in SearchForTweetsEx after the activity indicator is set:

   1: _lastId = Helper.GetLatestTweetId(SearchTerm.Text); // get the latest ID from settings
   2:  
   3: Helper.SaveLatestTweetId(SearchTerm.Text, _lastId); // saving for history even if a result isn't found

and then in the OnReadCompleted after we close the XmlReader we’ll add this:

   1: Helper.SaveLatestTweetId(SearchTerm.Text, _lastId); //saving last tweet id

And that now saves the search terms used as well as the last ID found if a result was found.

Summary

In this step we’ve set up a data template for a control, used some simple data binding using the XAML declarative syntax, added a value converter to format our view of information and save settings information to a local storage mechanism.

We’ve got basically our application working, so let’s start adding some interesting polish to the UI.

Let’s move on to part 5 where we add some new controls to enhance the experience using the data we just stored in this step.


This work is licensed under a Creative Commons Attribution By license.

Please enjoy some of these other recent posts...

Comments