Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

TweetSearch – A Cross Platform Metro UI WinRT and Silverlight Application

5.00/5 (3 votes)
16 Sep 2011CPOL5 min read 28.6K  
TweetSearch – A cross platform Metro UI WinRT and Silverlight Application

With the Windows 8 preview release earlier this week, developers are now faced with a whole new and exciting Microsoft stack. The Windows 8 architecture has something of a split-personality, incorporating a completely new runtime, WinRT together with the older Win32 and .NET Framework. However, these sit on different sides of a physical divide in both the architecture and the user interface. For more details of this, see my earlier article.

The new WinRT APIs support both C# and XAML, having an interface that is very similar to its .NET counterparts. However, there are a number of differences, most importantly much of the UI now resides in a new namespace. It is clear that transitioning from Silverlight, WP7 or WPF development to using WinRT will be quite easy.

I am sure there exist classes of application that sit best in the Win8 Metro UI, for which you will use WinRT for development. There will also be classes of application that sit best in the Win8 classic desktop, for which you will still be using WPF and Silverlight to develop.

However … there will certainly exist a class of application that sits well in both the Metro and Desktop environment. In the Win8 preview, Internet Explorer does just this.

This introduces a problem, how can we share code between our Silverlight Desktop and WinRT Metro applications?

I have previously presented talks and written articles on cross-platform application development. So I thought I would grab myself a copy of the Win8 preview release and see how easy it is to code-share between a Silverlight and WinRT Metro application.

This blog post describes a simple TwitterSearch application, the Silverlight version is shown below. Simply type in a search string to see the results retrieved from twitter:

A screenshot of the TwitterSearch application built for WinRT Metro is shown below:

Image 1

When code-sharing between WPF, Silverlight and WP7 applications, separation of concerns and solid design patterns are essential in order to navigate the framework differences. For that reason, I use the Model-View-ViewModel pattern, this makes it easy to specialize the view for each platform, where they typically differ the most. I will use this same approach for WinRT.

The code is shared between the Silverlight and Win8 Metro application by having two separate solutions which both share files via the Visual Studio file linking feature.

The TwitterSearchViewModel which backs this application simply exposes a search string property and a list of tweets:

C#
public partial class TwitterSearchViewModel : INotifyPropertyChanged
{
  private TweetViewModelCollection _tweets = new TweetViewModelCollection();
 
  private IMarshalInvoke _marshalInvoke;
 
  private string _searchText = "WinRT";
 
  public TwitterSearchViewModel(IMarshalInvoke marshalInvoke)
  {
    _marshalInvoke = marshalInvoke;
  }
 
  /// <summary>
  /// Gets / sets the search term
  /// </summary>
  public string SearchText
  {
    get {  return _searchText;  }
    set
    {
      _searchText = value;
      OnPropertyChanged("SearchText");
    }
  }
 
  /// <summary>
  /// Gets the search results
  /// </summary>
  public TweetViewModelCollection Tweets
  {
    get { return _tweets; }
    set { _tweets = value; }
  }
 
  /// <summary>
  /// Gets a command that executes the search
  /// </summary>
  public ICommand ExecuteSearchCommand
  {
    get
    {
      return new DelegateCommand(() => ExecuteSearch());
    }
  }
}

The IMarshalInvoke interface allows the view model to perform asynchronous calls via the Dispatcher, without being dependant on this UI class, more on this later.

It is here that I hit the first problem, WinRT has ICommand, but it lives in a different namespace! Time to add a WinRT compilation symbol to my WinRT project and throw in a pre-compiler directive:

C#
#if WINRT
 
using Windows.UI.Xaml.Input;
 
#else
 
using System.Windows.Input;
 
#endif

Now my view model compiles just fine. However, when I hooked it up with the view, my bindings were only working in one direction. After appealing to Twitter, the MSDN forums and the WPF Disciples, Laurent Bugnion kindly informed me that WinRT has two different INotifyPropertyChanged interfaces. One within System.ComponentModel and the other in Windows.UI.Xaml.Data, if you use the one from System.ComponentModel, it is ignored by the WinRT binding framework. Very confusing!

Fixing this is simply a matter of adding more platform specific namespaces:

C#
#if WINRT
 
// used for ICommand
using Windows.UI.Xaml.Input;
 
// used for INotifyPropertyChanged
using Windows.UI.Xaml.Data;
 
#else
 
using System.ComponentModel;
using System.Windows.Input;
 
#endif

My original code used WebClient, which is not present in WinRT. Fortunately, HttpWebRequest is present in both, so I changed my code to use that, hence the need for the IMarshalInvoke interface to marshal execution back onto the UI thread.

As a result, much of the core logic, which makes use of Linq-to-XML, in the view model is shared between both applications:

C#
/// <summary>
/// Parses the response from our twitter request, creating a list of TweetViewModel instances
/// </summary>
private void ParseXMLResponse(string xml)
{
  var doc = XDocument.Parse(xml);
  var items = doc.Descendants(AtomConst.Entry)
                  .Select(entryElement => new TweetViewModel()
                  {
                    Title = entryElement.Descendants
                    (AtomConst.Title).Single().Value,
                    Id = long.Parse(entryElement.Descendants
                    (AtomConst.ID).Single().Value.Split(':')[2]),
                    ProfileImageUrl =  entryElement.Descendants
                    (AtomConst.Link).Skip(1).First().Attribute("href").Value,
                    Timestamp = DateTime.Parse(entryElement.Descendants
                    (AtomConst.Published).Single().Value),
                    Author = entryElement.Descendants(AtomConst.Name).Single().Value
                  });
 
  _tweets.Clear();
  foreach (var item in items)
  {
    _tweets.Add(item);
  }
}
 
/// <summary>
/// Searches for the given term
/// </summary>
private void ExecuteSearch()
{
  IsSearching = true;
 
  // perform the search
  string uri = _twitterUrl + SearchText;
  HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(uri));
  request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
}
 
/// <summary>
/// A callback that receives the search results
/// </summary>
private void ReadCallback(IAsyncResult asynchronousResult)
{
  HttpWebRequest request = 
  (HttpWebRequest)asynchronousResult.AsyncState;
  HttpWebResponse response = 
  (HttpWebResponse)request.EndGetResponse(asynchronousResult);
  using (StreamReader streamReader1 = 
  new StreamReader(response.GetResponseStream()))
  {
    string resultString = streamReader1.ReadToEnd();
 
    // marshall onto the UI thread and parse
    _marshalInvoke.Invoke(() =>
      {
        Tweets.Clear();
        ParseXMLResponse(resultString);
        IsSearching = false;
      });
  }
}

This makes me happy. :)

On a side note, whilst WinRT does not have WebClient, it actually has something much better via classes such as SyndicationFeed which make use of the new C# async and await pattern for dealing with asynchrony. Of course, using these new APIs reduces the amount of code you share with the Silverlight .NET code.

The DelegateCommand implementation again hits the same problem of the same interface being located in a different namespace, yet despite this, most of the code is shared:

C#
#if WINRT
 
// used for ICommand
using Windows.UI.Xaml.Input;
 
#else
 
using System.Windows.Input;
 
#endif
 
namespace SLUGUK.ViewModel
{
  public class DelegateCommand : ICommand
  {
    private Action _action;
 
    public DelegateCommand(Action action)
    {
      _action = action;
    }
 
    public bool CanExecute(object parameter)
    {
      return true;
    }
 
#if WINRT
    public event Windows.UI.Xaml.EventHandler CanExecuteChanged;
#else
    public event EventHandler CanExecuteChanged;
#endif
 
    public void Execute(object parameter)
    {
      _action();
    }
  }
}

Despite the various XAML UI controls being in a different C# namespace, they use the same URIs for their XML namespace mappings. This makes it possible to share XAML as well as C# code between your applications.

However, in much the same way as it makes little sense to share XAML between WP7 and Silverlight due to differences in form-factor and user-experience, I don’t think it makes sense to share XAML between Silverlight and Metro WinRT. The primary focus of the Win8 Metro applications is the touch-first tablet form factor, hence you would expect it to be quite different from a desktop application.

The following is the XAML for my Metro UI application:

XML
<UserControl x:Class="WinRTMetroTwitter.View.TwitterSearchView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="using:WinRTMetroTwitter.View"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
 
    <UserControl.Resources>
        <local:BoolToVisibilityConverter 
        x:Key="BoolToVisibilityConverter"/>
        <local:ImageConverter x:Key="ImageConverter"/>        
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
        <Grid Margin="50"
              Width="400"
              HorizontalAlignment="Left">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Path=SearchText, Mode=TwoWay}"
                     Width="200"/>
                <Button Command="{Binding Path=ExecuteSearchCommand}"
                    Content="Go!"/>
                <TextBlock VerticalAlignment="Center"
                     Visibility="{Binding Path=IsSearching, 
                     Converter={StaticResource BoolToVisibilityConverter}}"
                     Text="Searching ..."
                     Margin="20,0,0,0"
                     FontSize="15"/>
            </StackPanel>
 
            <!-- renders the search results -->
            <ItemsControl x:Name="itemsControl"
                          Grid.Row="2">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal"
                                    Margin="5">
                           <Image Source="{Binding ProfileImageUrl, 
                           Converter={StaticResource ImageConverter}}"
                                  Width="96" Height="96"
                                  Stretch="UniformToFill"
                                  Margin="2"
                                  VerticalAlignment="Top"/>
 
                            <StackPanel Orientation="Vertical">
                                <TextBlock Text="{Binding Title}"
                                           TextWrapping="Wrap"
                                           FontSize="15"
                                           Width="300"
                                           HorizontalAlignment="Left"/>
                                <StackPanel Orientation="Horizontal">
 
                                    <TextBlock FontWeight="Bold"
                                               Text="{Binding Path=Author}"
                                               FontSize="15"/>
 
                                    <TextBlock Margin="10,0,0,0"
                                              Text="{Binding Path=Timestamp}"
                                               FontSize="15"
                                              Foreground="#333"/>
                                </StackPanel>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
 
            <Rectangle Fill="#FF0C0C0C"
                   Opacity="0.5"
                   Grid.Row="1"
                   Visibility="{Binding Path=IsSearching, 
                   Converter={StaticResource BoolToVisibilityConverter}}"/>
        </Grid>
    </Grid>
</UserControl>

If you are a Silverlight developer, you will notice that everything here is standard Silverlight, it is just wired-up to different classes via the XAML namespace mappings.

I did discover that the IValueConverter signature within WinRT is a bit different. Within this application, the value conversion logic is so simple it is hardly worth code-sharing, however, it would be a very simple exercise if you wished to do so.

C#
namespace WinRTMetroTwitter.View
{
  public class BoolToVisibilityConverter : IValueConverter
  {
    public object Convert(object value, string typeName, object parameter, string language)
    {
      return (bool)value ? Visibility.Visible : Visibility.Collapsed;
    }
 
    public object ConvertBack(object value, string typeName, object parameter, string language)
    {
      throw new NotImplementedException();
    }
  }
}

Finally, I did hit against one further problem. With WinRT, it seems that there is currently a bug which prevents ItemsControl binding to collections via INotifyCollectionChanged from working. This has been raised in the MSDN forums and I am sure it will be fixed. As a simple workaround, I added the following code-behind (gasp!):

C#
public sealed partial class TwitterSearchView : UserControl
{
  private TwitterSearchViewModel feedViewModel;
 
  public TwitterSearchView()
  {
    InitializeComponent();
 
    this.Loaded += ResultsView_Loaded;
  }
 
  private void ResultsView_Loaded(object sender, RoutedEventArgs e)
  { 
    feedViewModel = this.DataContext as TwitterSearchViewModel;
 
    // detect collection changed
    feedViewModel.Tweets.CollectionChanged += FeedItems_CollectionChanged;
  } 
 
  private void FeedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  {
    // manually 'refresh' the UI
    itemsControl.ItemsSource = null;
    itemsControl.ItemsSource = feedViewModel.Tweets;
  }
}

Conclusions

Creating a cross-platform WinRT / Silverlight application is entirely possible, using the same techniques that we currently employ for cross-platform Silverlight / WPF / WP7 applications. There are further complications relating to classes living in different namespaces and there will of course be other detail-level differences that this simple example does not touch upon. However, I think this presents a feasible and promising route for re-using existing Silverlight/ WPF code, and for developing applications that target the Win8 tablet, WP7 phone and Silverlight (or WPF) desktop with a single code-base.

Regards,
Colin E.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)