Colin Eberhardt's Technology Adventures

Developing a Lookless Silverlight Gauge Control (part 2)

October 20th, 2010

In a previous blog post I described the process of creating a lookless gauge control. I introduced the concept of an attached view model which separates view specific concepts from the control. In this post I demonstrate how this allows for great flexibility when re-templating the control.

In my previous post I described the development of a radial gauge control where I removed any view-specific logic and properties from the control by introducing an attached view model into the control’s template. My reasons for doing this were to create a completely lookless control in the order that this would permit greater flexibility when re-templating or themeing. The control I created was a gauge control, which in my previous blog post I rendered as a radial gauge. The ‘radial concepts’ of angle, and other derived properties are contained within the attached view model, with the Gauge control itself only containing concepts that are common across all gauges (radial or otherwise). The control is shown below:

In this post I will create two different templates for this control. The first is a small variation on the radial gauge which is rendered as a semi-circle. The second is very different, a bullet graph, where the gauge is rendered as a linear indicator. With each, an attached view model is introduced into the controls template in order to compute properties that aid in the rendering of the control.

A Semi-circular Gauge

The original attached view model I created was hard-coded to create a radial control where the needle has a sweep angle of 300 degrees. To support a semi-circular gauge we need to be able to configure this parameter. I achieved this by simply making SweepAngle a public property of the attached view model. This property is used wherever the view model needs to convert a gauge value to a radial location (i.e. when computing the tick mark rotation angles):

public class RadialGaugeControlViewModel : AttachedViewModelBase
{
  public RadialGaugeControlViewModel()
  {
    SweepAngle = 300;
  }
 
  /// <summary>
  /// Gets / sets the sweep angle of the radial gauge
  /// </summary>
  public double SweepAngle
  { get; set; }
 
  private double ValueToAngle(double value)
  {
    double minAngle = -SweepAngle / 2;
    double maxAngle = SweepAngle / 2;
    double angularRange = maxAngle - minAngle;
 
    return (value - Gauge.Minimum) / (Gauge.Maximum - Gauge.Minimum) *
        angularRange + minAngle;
  }
 
  ...
}

This allows it to be set in the control template of our re-templated gauge control where the view model is instantiated and attached:

<Style TargetType="local:GaugeControl" x:Key="themedGauge">
  <Setter Property="FontSize" Value="8"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:GaugeControl">
        <Grid x:Name="LayoutRoot" >
          <Grid>
            <!-- attach the view model -->
            <local:RadialGaugeControlViewModel.Attach>
              <local:RadialGaugeControlViewModel
                     SweepAngle="180"/>
            </local:RadialGaugeControlViewModel.Attach>
 
            <!-- gauge template goes here! -->
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

This means that the various properties of the attached view model that the view binds to in order to render the various control parts, such as the needle, are now computed with a 180 degree sweep.

For the semi-circular gauge, I used a similar technique to the original radial gauge for creating the required layout, where a grid with various rows / columns with star widths / heights create a proportional layout within which elements such as the needle are placed:

<Grid>
<!-- attach the view model -->
<local:RadialGaugeControlViewModel.Attach>
  <local:RadialGaugeControlViewModel SweepAngle="180" Clip="false"/>
</local:RadialGaugeControlViewModel.Attach>
 
<!-- the gauge outline -->
<Path Stroke="Black" StrokeThickness="3" StrokeLineJoin="Round"
      Stretch="Uniform">
  <Path.Data>
    <PathGeometry>
      <PathGeometry.Figures>
        <PathFigure IsClosed="True" 
                    StartPoint="0.5,0">
          <PathFigure.Segments>
            <ArcSegment Size="0.5,0.5" RotationAngle="45" IsLargeArc="False"
                      SweepDirection="Clockwise"
                      Point="1,0.5"/>
            <LineSegment Point="1,0.55"/>
            <LineSegment Point="0,0.55"/>
            <LineSegment Point="0,0.5"/>
            <ArcSegment Size="0.5,0.5" RotationAngle="45" IsLargeArc="False"
                            SweepDirection="Clockwise"
                            Point="0.5,0"/>
          </PathFigure.Segments>
        </PathFigure >
      </PathGeometry.Figures>
    </PathGeometry>
  </Path.Data>
</Path>
 
<Grid ShowGridLines="true">
  <Grid.RowDefinitions>
    <RowDefinition Height="3.5*"/>
    <RowDefinition Height="4*"/>
    <RowDefinition Height=".6*"/>
    <RowDefinition Height=".6*"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="8.0*"/>
    <ColumnDefinition Width=".7*"/>
    <ColumnDefinition Width=".7*"/>
    <ColumnDefinition Width="8.0*"/>
  </Grid.ColumnDefinitions>
 
  <!-- needle -->
  <Path Stretch="Uniform"
    Grid.Row="1" Grid.Column="1"
    Grid.ColumnSpan="2" Grid.RowSpan="2"
    Fill="Black"
    HorizontalAlignment="Center"
    Stroke="Black" StrokeThickness="0.5"
    Data="M 0,0 l 5,60 l -10, 0"
    RenderTransformOrigin="0.5,1">
    <Path.RenderTransform>
      <RotateTransform Angle="{Binding Path=ValueAngle}"/>
    </Path.RenderTransform>
  </Path>
 
  <!-- needle cover -->
  <Ellipse Fill="White"
            Stroke="Black" StrokeThickness="2"
            Width="13" Height="13"
            HorizontalAlignment="Center" VerticalAlignment="Bottom"
            Grid.Row="2" Grid.Column="1"
            Grid.RowSpan="2" Grid.ColumnSpan="2"/>
</Grid>

You can see the various gridlines below:

The code for creating the major / minor ticks and qualitative ranges are much the same as before. However, for a bit of variation I did not want the labels on this gauge to be rotated. In order to achieve this I used the same technique as before where each label is initially constructed at the same location, then a translate / rotate transform is used to move it to the correct location on the dial. In this control, the text label is rotated a second time in the opposite direction in order to bring it back to its original orientation:

<ItemsControl.ItemTemplate>
  <DataTemplate>
    <Grid Width="50" Height="20">
      <Grid.RenderTransform>
        <TransformGroup>
          <TranslateTransform X="-25" Y="-10"/>
          <TranslateTransform Y="{Binding Path=Parent.GridHeight,
              Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-0.69}"/>
          <RotateTransform Angle="{Binding Path=Angle}"/>
        </TransformGroup>
      </Grid.RenderTransform>
      <TextBlock Text="{Binding Path=Value}" 
            VerticalAlignment="Center" HorizontalAlignment="Center"
            RenderTransformOrigin="0.5, 0.5">
        <!-- rotate the labels by '-Angle' to return to their original orientation -->
        <TextBlock.RenderTransform>
          <RotateTransform Angle="{Binding Path=Angle,
                Converter={StaticResource ScaleFactorConverter}, ConverterParameter=-1}" />
        </TextBlock.RenderTransform>
      </TextBlock>
    </Grid>
  </DataTemplate>
</ItemsControl.ItemTemplate>

The finished semi-circular gauge can be seen here next to my original radial gauge control:

So, the original control has proven to be pretty versatile in that it can be re-templated to look quite different. To allow this, the attached view model was modified to expose a few view-centric properties that could be set when it is created and attached within the template.

However, the example above is still a radial gauge. A much bigger challenge would be to re-template the control to look completely different. That was my next challenge!

A Bullet Graph Template

Radial gauge controls look very pretty; however they eat up a lot of screen real-estate, and are not the best way of visualising a one-dimensional metric. A much clearer visualisation can be achieved by using a linear gauge, a bullet-graph for example. This section describes how a new attached view model can be applied to the gauge control, to support a template which produces a bullet graph.

Using the same process as in the above example, the basic structure of the control is defined using a Grid:

<Style TargetType="local:GaugeControl" x:Key="bulletGraphGauge">
  <Setter Property="FontSize" Value="8"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:GaugeControl">
        <Grid x:Name="LayoutRoot" Background="LightBlue">
          <Grid ShowGridLines="True">
            <!-- attach the view model -->
            <local:RadialGaugeControlViewModel.Attach>
              <local:BulletGraphGaugeViewModel/>
            </local:RadialGaugeControlViewModel.Attach>
 
            <!--the grid layout -->
            <Grid.RowDefinitions>
              <RowDefinition Height="3*"/>
              <RowDefinition Height="*"/>
              <RowDefinition Height="2*"/>
              <RowDefinition Height="*"/>
              <RowDefinition Height="3*"/>
            </Grid.RowDefinitions>                
 
            <!-- featured measure indicator -->
            <Rectangle Fill="Black"
                        Grid.Row="2"
                        HorizontalAlignment="Left"
                        Width="{Binding Path=FeaturedMeasureLength}"/>
          </Grid>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The ‘featured measure’, which is a thermometer-like bar which indicates the current value of the gauge has its Width property bound to the view model which is attached at the top of the template. To simplify the code, I refactored the attached view model concept to extract a base class which is shared by both the radial and bullet-graph subclasses. This base class takes care of attaching to its parents DataContext, adapting the control’s properties, exposing the actual width / height of the control and the other bits and pieces I described in my previous blog post. The bullet graph view model is then much simpler as a result. In the code below you can see how the FeaturedMeasureLength is computed in the view model:

public class BulletGraphGaugeViewModel : AttachedViewModelBase
{
  public BulletGraphGaugeViewModel()
  {
  }
 
  private GaugeControl Gauge
  {
    // the view model’s DataContext is bound to the Gauge
    get { return DataContext != null ? (GaugeControl)DataContext : null; }
  }
 
  public double FeaturedMeasureLength
  {
    get
    {
      if (Gauge == null)
        return 0;
 
      return ValueToWidth(Gauge.Value - Gauge.Minimum);
    }
  }
 
  /// <summary>
  /// Converts the given value (which should be between Gauge.Maximum / Gauge.Minimum)
  /// into suitable width for rendering within the view.
  /// </summary>
  private double ValueToWidth(double value)
  {
    double range = Gauge.Maximum - Gauge.Minimum;
    return (value) / range * ElementWidth;
  }
}

Next we’ll add the ‘qualitative range’ scale:

<!-- Qualitative ranges -->
<ItemsControl ItemsSource="{Binding Path=Ranges}"
              Grid.Row="1" Grid.RowSpan="3">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <StackPanel Orientation="Horizontal"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Rectangle Width="{Binding Path=Width}"
                  Stroke="Black" StrokeThickness="0.2"
                  Fill="{Binding Path=Color, Converter={StaticResource ColorToBrushConverter}}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
<Border BorderBrush="Black" BorderThickness="1.0"
        Grid.Row="1" Grid.RowSpan="3" Margin="-1"/>

Once again, the attached view model is used to adapt the properties exposed by the control in order to provide properties which are easier to bind to in order to create the required UI. Here the Gauge controls QualitativeRange is adapted to expose a Range property, which the ItemsControl in the XAML above binds to, this property is an enumeration of RangeItems which each have the correct width and colour for our UI:

public IEnumerable<RangeItem> Ranges
{
  get
  {
    if (Gauge == null)
      yield break;
 
    for (int i = 0; i < Gauge.QualitativeRange.Count; i++)
    {
      var range = Gauge.QualitativeRange[i];
      if (i == 0)
      {
        // first RangeItem, width is determined from the first range value
        yield return new RangeItem()
        {
          Color = range.Color,
          Width = ValueToWidth(range.Maximum - Gauge.Minimum)
        };
      }
      else
      {
        // subsequent items, width computed as the difference between the
        // current value and its predecessor
        var previousRange = Gauge.QualitativeRange[i - 1];
        yield return new RangeItem()
        {
          Color = range.Color,
          Width = ValueToWidth(range.Maximum - previousRange.Maximum)
        };
      }
    }
  }
}
 
...
 
public class RangeItem
{
  public double Width { get; set; }
  public Color Color { get; set; }
}

Finally, the scales are added:

<!-- upper scale -->
<ItemsControl ItemsSource="{Binding Path=MajorTicks}"
              VerticalAlignment="Center"
              Grid.Row="0"
              ItemsPanel="{StaticResource CanvasTemplate}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Grid Width="50" Height="20">
        <Grid.RenderTransform>
          <TransformGroup>
            <!-- centre the labels -->
            <TranslateTransform X="-25" Y="-10"/>
            <TranslateTransform X="{Binding Path=Position}"/>
          </TransformGroup>
        </Grid.RenderTransform>
        <TextBlock Text="{Binding Path=Label}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"/>
      </Grid>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
 
<!-- major ticks -->
<ItemsControl ItemsSource="{Binding Path=MajorTicks}"
              VerticalAlignment="Bottom"
              Grid.Row="0"
              ItemsPanel="{StaticResource CanvasTemplate}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Line X1="{Binding Path=Position}" Y1="0" X2="{Binding Path=Position}" Y2="-5"
            Stroke="Black"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
 
<!-- minor ticks -->          
<ItemsControl ItemsSource="{Binding Path=MinorTicks}"
              VerticalAlignment="Bottom"
              Grid.Row="0"
              ItemsPanel="{StaticResource CanvasTemplate}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Line X1="{Binding}" Y1="0" X2="{Binding}" Y2="-3"
            Stroke="Black"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Again, exactly the same pattern is employed, with the MajorTicks and MinorTicks properties exposed by the attached view model, each providing an enumeration of simple value objects with a Position and Label properties, which are bound to the three ItemsControls above to create the required UI.

The complete bullet graph template is shown below next to the original radial gauge:

In conclusion, the use of an attached view model provides a mechanism for exposing view-specific properties to the view logic within the XAML template. This enables the construction of a control which really is entirely lookless.

You can download the sourcecode for this project: GaugeControl.zip

Regards, Colin E.

Silverlight as an alternative to PowerPoint

October 13th, 2010

Recently I gave a presentation on cross platform application development with Silverlight, WPF and WP7. I wanted to move away from the standard PowerPoint bullet-point driven presentation style, so decided to create a presentation as a Silverlight application running in full-screen mode. This blog post describes the steps I took to create this.

PowerPoint is a very popular tool for the rapid creation of presentations, however, PowerPoint presentations have the problem that they tend to look-alike, due to them being built upon ‘stock’ templates, clip art and animated transitions. Interestingly data visualisation guru Edward Tufte goes further that just criticising the cosmetics of PowePoint. In his book “The Cognitive Style of PowerPoint“, he claims that the bullet-point, abbreviated-text style of PowerPoint foreshortens evidence and thought!

For my presentation at a recent Scott Logic / Microsoft event I wanted to avoid using bullet-points as much as possible. I decided to make my presentation mostly graphical, putting more emphasis on the verbal delivery of the content. The subject of the talk was how Silverlight and WPF are unifying cross platform development, so I thought, why not use a full-screen Silverlight application to deliver the presentation?

Taking inspiration from the Metro Design Language and the Windows Phone 7 Panorama control, here is the presentation I created:

Mouse-over to see the forward / back / full-screen controls, also left / right mouse button clicks navigate forwards / backwards

Whilst it is graphically quite rich, and includes animated transitions, I feel that by writing the presentation as a Silverlight application, it further re-enforces the contents of the talk. (at least that is my excuse for not following Edward Tufte’s teachings to the letter!).

The presentation also works just fine on Windows Phone 7, with just a few tweaks required for the small differences in user interaction:

With the WP7 version, you navigate by clicking on either the left or right hand side of the phone’s screen.




The following section is a small collection of notes on the implementation of this presentation and the sourcecode is given at the end of this post.

The basic structure of the presentation is quite simple, with the individual slides created as user controls. These control each wrap their content in a Viewbox control so that the entire slide is scaled when the presentation is run in full-screen mode. (Note, WP7 does not have a Viewbox control, however it has a fixed screen resolution, so each slide has a hard-coded width / height).

The slides are contained with a presenter which is responsible for displaying a single slide and animating their transitions:

<local:PanormicSlidePresenter Title="WPF and Silvelight; Unification" SelectedIndex="0">
  <slide:FrontPage/>
  <slide:Timeline/>
  <slide:PlatformDifferences/>
  <slide:SeparateTeams/>
  <slide:UnifiedTeams/>
</local:PanormicSlidePresenter>

The PanormicSlidePresenter is a subclass of ListBox, which provides the ability to select a single slide, with the PanormicSlidePresenter ensuring that only the selected slide is visible. The template for the PanormicSlidePresenter uses a horizontal stack panel as the container for the slides. It also has a TextBlock which renders the title of the slide deck. These elements each have a Storyboard which is triggered in code-behind to animate the slides into view.

<Style TargetType="local:PanormicSlidePresenter">
  <!-- a ListBoxItem style that removes that removes the selected / enabled state elements -->
  <Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle}"/>
  <Setter Property="ItemsPanel">
    <Setter.Value>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal"/>
      </ItemsPanelTemplate>
    </Setter.Value>
  </Setter>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:PanormicSlidePresenter">
        <Grid local:Clip.ToBounds="True">      
          <!-- the title for this slide presenter -->
          <Canvas VerticalAlignment="Top" HorizontalAlignment="Left">
            <Viewbox x:Name="Title">
              <Viewbox.Resources>
                <Storyboard x:Key="animation">
                  <DoubleAnimation  Storyboard.TargetProperty="(Canvas.Left)"
                                    To="360" Duration="0:0:0.5">
                    <DoubleAnimation.EasingFunction>
                      <SineEase EasingMode="EaseOut" />
                    </DoubleAnimation.EasingFunction>
                  </DoubleAnimation>
                </Storyboard>
              </Viewbox.Resources>
              <TextBlock Text="{TemplateBinding Title}" Foreground="#444"/>
            </Viewbox>
          </Canvas>
 
          <!-- the presenter which renders the slides-->
          <Canvas >
            <ItemsPresenter x:Name="ItemsPresenter">
              <ItemsPresenter.Resources>
                <Storyboard x:Key="animation">
                  <DoubleAnimation  Storyboard.TargetProperty="(Canvas.Left)"
                                      To="360" Duration="0:0:0.5">
                    <DoubleAnimation.EasingFunction>
                      <SineEase EasingMode="EaseOut" />
                    </DoubleAnimation.EasingFunction>
                  </DoubleAnimation>
                </Storyboard>
              </ItemsPresenter.Resources>
            </ItemsPresenter>
          </Canvas>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The code for the PanormicSlidePresenter is pretty simple, it basically performs two tasks. Firstly, it ensures that the size of each of the slides it contains matches its own size, this ensures that each slide fills the entire screen. Secondly, when selection changes, it applies an offset to the StackPanel which contains each of the slides, to bring the correct slide into view. It also applies a suitable offset to the title element to give a parallax scrolling effect which is one of the distinctive features of the Windows Phone 7 Panorama control.

/// <summary>
/// Renders a collection of elements, with animated transitions between each.
/// </summary>
public class PanormicSlidePresenter : ListBox
{
  private ItemsPresenter _itemsPresenter;
 
  private FrameworkElement _title;
 
  #region Title Dependency Property
 
  public string Title
  {
    get { return (string)GetValue(TitleProperty); }
    set { SetValue(TitleProperty, value); }
  }
 
  public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title",
    typeof(string), typeof(PanormicSlidePresenter), new PropertyMetadata(""));
 
  #endregion
 
  public PanormicSlidePresenter() 
  {
    DefaultStyleKey = typeof(PanormicSlidePresenter);
 
    this.SelectionChanged += PanormicSlidePresenter_SelectionChanged;
    this.SizeChanged += PanormicSlidePresenter_SizeChanged;
  }
 
  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();
 
    _itemsPresenter = this.GetTemplateChild("ItemsPresenter") as ItemsPresenter;
    _title = this.GetTemplateChild("Title") as FrameworkElement;
  }
 
  /// <summary>
  /// Handle size changed events, scaling the slides and setting the required offsets
  /// </summary>
  private void PanormicSlidePresenter_SizeChanged(object sender, SizeChangedEventArgs e)
  {
    // scale each slide
    foreach (FrameworkElement element in Items)
    {
      SetElementSize(element);
    }
 
    // offset to display the correct slide
    Canvas.SetLeft(_itemsPresenter, GetSlideOffset());
 
    // scale and offset the title
    _title.Width = ActualWidth * 1.5;
    Canvas.SetLeft(_title, GetTitleOffset());
  }
 
  /// <summary>
  /// Compute the offset of the title base on the current selection
  /// </summary>
  private double GetTitleOffset()
  {
    return -((ActualWidth / 2) * SelectedIndex / (Items.Count-1));
  }
 
  /// <summary>
  ///  Compute the offset required to bring the selected slide into view
  /// </summary>
  private double GetSlideOffset()
  {
    return -SelectedIndex * ActualWidth;
  }
 
  /// <summary>
  /// Animates the current slide into view
  /// </summary>
  private void ShowCurrentSlide()
  {
    AnimateElementTo(_itemsPresenter, GetSlideOffset());
    AnimateElementTo(_title, GetTitleOffset());
  }
 
  /// <summary>
  /// Animates the given element to the given canvas offset
  /// </summary>
  private void AnimateElementTo(FrameworkElement element, double canvasLeft)
  {
    Storyboard anim = element.Resources["animation"] as Storyboard;
 
    var dblAnim = anim.Children[0] as DoubleAnimation;
    dblAnim.From = Canvas.GetLeft(element);
    dblAnim.To = canvasLeft;
    anim.Stop();
 
    Storyboard.SetTarget(anim, element);
    anim.Begin();
  }
 
  /// <summary>
  /// Handle selection changed to animate the slide into viee
  /// </summary>
  private void PanormicSlidePresenter_SelectionChanged(object sender, SelectionChangedEventArgs e)
  {
    if (_itemsPresenter == null)
      return;
 
    ShowCurrentSlide();
  }
 
  /// <summary>
  /// As slide are added, set their size
  /// </summary>
  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  {
    base.PrepareContainerForItemOverride(element, item);
 
    // scale the elements as they are added
    SetElementSize(item as FrameworkElement);
  }
 
  /// <summary>
  /// Sets the size of the given element to match the size of this container
  /// </summary>
  private void SetElementSize(FrameworkElement element)
  {
    element.Width = this.ActualWidth;
    element.Height = this.ActualHeight;
  }
}

The presentation shown above also has another ‘slide presenter’ which animates transitions between its child control by spinning each into view using a RotateTransform. The code for this presenter, WheelSlidePresenter, is very similar to the panorama presenter described above. If you are interested, the sourecode is available to download below.

As an aside, I really like the way that the Visual Studio designed shows the positions of the off-screen slides. You can even change the SelectedIndex of the presenter elements in XAML and watch the animated transitions:

In conclusion, I think Silverlight provides an interesting alternative to PowerPoint for presentations (although creating Silverlight presentations requires coding skills, so it is not suitable for the masses!).

You can download the full sourcecode for this presentation below:

Sourcecode for Web version: SilverlightPresentationWeb.zip
Sourcecode for Windows Phone 7 version: SilverlightPresentationMobile.zip

Regards,
Colin E.

White Paper: Silverlight, WPF and Windows Phone 7 cross platform development

October 11th, 2010

Last week I gave a presentation at a joint Scott Logic / Microsoft event about how WPF and Silverlight are unifying the development platform for desktop, web and mobile. To accompany the talk I wrote a white paper which delves into this subject in a little more detail.

You can download the whitepaper here: WPF and Silverlight Cross Platform Development White Paper (PDF, 1.8 MBytes)

Or view it online in the PDF viewer below:

I also created a cross platform application that has a 75% shared codebase between the WPF (desktop), Silverlight (web) and Windows Phone 7 (mobile) versions. I plan to share more of this at a later date.

Any thoughts, ideas or comments, please share them below.

Regards,
Colin E.