Silverlight 4 Rough Notes: Binding

Note – these posts are put together after a short time with Silverlight 4 as a way of providing pointers to some of the new features that Silverlight 4 has to offer. I’m posting these from the PDC as Silverlight 4 is announced for the first time so please bear that in mind when working through these posts.

Silverlight’s data-binding capabilities are key in that they make underpin the designer/developer workflow and they make it possible to build applications far more quickly than we did with other UI technologies.

They’re pretty powerful but even so they’ve remained a subset of what’s available in WPF. In Silverlight 4, there are some new capabilities for Silverlight’s data-binding that take it nearer to what WPF can do.

StringFormat Binding

This is a small thing but I’ve really found it useful in WPF so it’s great to see it show up in Silverlight too. It’s effectively the equivalent of;

String.Format()

in the .NET Framework in that if you’ve got some business object like;

  public class Customer
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public decimal BankBalance { get; set; }
  }

and if I set one of those up as my DataContext;

public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          this.DataContext = new Customer()
          {
            FirstName = "Kermit",
            LastName = "Frog",
            BankBalance = 250000.0m,
            DateOfBirth = DateTime.Now
          };
        };
    }
  }

then I can write a bit of UI that binds to those properties and uses StringFormat;

<UserControl x:Class="SilverlightApplication20.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Viewbox>
            <StackPanel>
                <TextBlock
                    Text="{Binding FirstName}" />
                <TextBlock
                    Text="{Binding LastName}" />
                <TextBlock
                    Text="{Binding Path=DateOfBirth,StringFormat=Date of Birth: \{0:D\}}" />
                <TextBlock
                    Text="{Binding Path=BankBalance,StringFormat=Balance: \{0:C\}}" />
            </StackPanel>
        </Viewbox>
    </Grid>
</UserControl>

to get the formatting of my dates and currencies as;

image

So that’s neat – personally I’d love to see this with MultiBinding where I find it most useful but ( AFAIK ) MultiBinding isn’t in the Silverlight framework right now.

FallbackValue Binding

This is a fairly obvious one – for a number of reasons it might not be possible to get the data from the bound object to the UI. It might be an exception thrown in a getter, it might be a conversion problem, etc. etc.

FallbackValue provides a mechanism for specifying a fallback value for when that occurs. Quick example taking my previous bit of code and changing it to;

  public class Customer
  {
    public string FirstName 
    {
      get
      {
        throw new NotImplementedException("Sorry");
      }
      set
      {
      }
    }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public decimal BankBalance { get; set; }
  }

and my previous bit of XAML to;

<Grid x:Name="LayoutRoot" Background="White">
        <Viewbox>
            <StackPanel>
                <TextBlock
                    Text="{Binding FirstName, FallbackValue=N/A}" />
                <TextBlock
                    Text="{Binding LastName,FallbackValue=N/A}" />
                <TextBlock
                    Text="{Binding Path=DateOfBirth,StringFormat=Date of Birth: \{0:D\}}" />
                <TextBlock
                    Text="{Binding Path=BankBalance,StringFormat=Balance: \{0:C\}}" />
            </StackPanel>
        </Viewbox>
    </Grid>

then gives me the UI below when the FirstName property accessor fails to get a value for data-binding;

image 

and my fallback value is provided – note that the fallback value may well also need to be converted in order to be the right type for the property that it is bound to ( my example doesn’t need that ).

TargetNullValue Binding

Somewhat similar to FallbackValue is the TargetNullValue which allows you to specify how null values from the business layer are represented in the UI and vice versa.

Updating our previous example again, let’s change the code;

public class Customer
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime? DateOfBirth
    {
      get
      {
        return (dateOfBirth);
      }
      set
      {
        dateOfBirth = value;
      }
    }
    public decimal BankBalance { get; set; }

    DateTime? dateOfBirth;
  }

So now we have a nullable DateOfBirth. Note that the explicit set/get are there purely for me to set breakpoints on. With that in place I can change my UI a little to;

  <Grid x:Name="LayoutRoot" Background="White">
        <Viewbox>
            <StackPanel>
                <TextBlock
                    Text="{Binding FirstName,StringFormat=Forename:\{0\}}" />
                <TextBlock
                    Text="{Binding LastName,StringFormat=Surname:\{0\}}" />
                <TextBox
                    Text="{Binding Path=DateOfBirth,TargetNullValue=N/A,StringFormat=DOB:\{0\},Mode=TwoWay}" />
                <TextBlock 
                    Text="{Binding Path=BankBalance,StringFormat=Balance:\{0\}}" />
                <Button
                    Content="Save" />
            </StackPanel>
        </Viewbox>
    </Grid>

and so now the DateOfBirth is bound into a TextBox and it has a TargetNullValue of the string “N/A”. If I provide the UI with a data-bound context such as;

 public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          this.DataContext = new Customer()
          {
            FirstName = "Kermit",
            LastName = "Frog",
            BankBalance = 250000.0m,
            DateOfBirth = null
          };
        };
    }
  }

where the DateOfBirth starts off as null and remains null ( note – I didn’t do property change notification here so that’s pretty poor and doesn’t really “work” but it serves my purpose ) then the UI I will see is;

image

so the TargetNullValue shows up nicely. If I type a decent value into that property then I’ll see that poked into my code;

image

results in;

image

whereas if I type the string “N/A” into that TextBox;

image

then I see the value of NULL passed through to my code;

image

so that’s pretty neat and a nice way of dealing with values that come out of the database for instance and have to take representations of null.

DependencyObject Binding

I often find myself trying to figure out why I can’t databind some property in Silverlight only to remember after a while

“Ahhh, it’s not a FrameworkElement, it’s a DependencyObject and I can’t bind to those!!!”

I sent an email the other day because I’d failed to make some data-binding and the response was “Because it’s not a FrameworkElement !” and I had to go hide under a rock for a while for forgetting such a basic thing.

So, as the SDK says;

The target UI property that displays and possibly allows user changes to the data. The target can be any DependencyProperty of a FrameworkElement.

Here’s an example that bit me a few times with Silverlight 3;

<UserControl
    x:Class="SilverlightApplication20.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid
        x:Name="LayoutRoot"
        Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Viewbox Margin="24">
            <TextBlock
                Text="Hello World" />
            <Viewbox.Projection>
                <PlaneProjection
                    x:Name="myProjection"
                    RotationX="{Binding ElementName=sliderX,Path=Value}"
                    RotationY="{Binding ElementName=sliderY,Path=Value}"
                    RotationZ="{Binding ElementName=sliderZ,Path=Value}">                    
                </PlaneProjection>
            </Viewbox.Projection>
        </Viewbox>
        <StackPanel
            Grid.Row="1"
            Margin="10">
            <Slider x:Name="sliderX" Minimum="-180" Maximum="180" Value="0"/>
            <Slider
                x:Name="sliderY"
                Minimum="-180"
                Maximum="180"
                Value="0" />
            <Slider
                x:Name="sliderZ"
                Minimum="-180"
                Maximum="180"
                Value="0" />
        </StackPanel>
    </Grid>
</UserControl>

which produces a simple UI;

image

here we’re binding the (dependency) properties RotationX, RotationY, RotationZ on a PlaneProjection. The “problem” is that PlaneProjection is not a FrameworkElement but is instead a DependencyObject and so the binding doesn’t work. Except in Silverlight 4, it works just fine 🙂

This is a little bit odd because a DependencyObject doesn’t have any notion of a DataContext and so you have to ponder around where it gets its data from? So, if we take our sliders away and have a simpler UI;

<UserControl
    x:Class="SilverlightApplication20.MainPage"
    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="clr-namespace:SilverlightApplication20"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    <UserControl.Resources>
        <local:MyRotation
            x:Key="negativeRotation"
            Rotation="-45" />
    </UserControl.Resources>

    <Grid
        x:Name="LayoutRoot"
        Background="White"
        DataContext="{StaticResource negativeRotation}">
        <Viewbox Margin="24">
            <TextBlock
                Text="Hello World" />
            <Viewbox.Projection>
                <PlaneProjection
                    x:Name="myProjection"
                    RotationX="{Binding Path=Rotation}"
                    RotationY="{Binding Path=Rotation}">                    
                </PlaneProjection>
            </Viewbox.Projection>
        </Viewbox>       
    </Grid>
</UserControl>

where MyRotation is just a little class;

  public class MyRotation
  {
    public int Rotation { get; set; }
  }

then everything works fine even though there’s no way we could ever get hold of that PlaneProjection instance and set a DataContext on it but, regardless, it still goes ahead and “finds the right DataContext” in the sense that it finds a friend somewhere up the tree of elements (the Grid) that has a DataContext and makes use of that.

That gets a bit “interesting” in that if we have something like this;

<UserControl
    x:Class="SilverlightApplication20.MainPage"
    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="clr-namespace:SilverlightApplication20"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    <UserControl.Resources>
        <local:MyRotation
            x:Key="negativeRotation"
            Rotation="-45" />
    </UserControl.Resources>

    <Grid
        x:Name="LayoutRoot"
        Background="White"
        DataContext="{StaticResource negativeRotation}">
        <Grid.Resources>
            <PlaneProjection
                x:Key="myProjection"
                RotationX="{Binding Path=Rotation}"
                RotationY="{Binding Path=Rotation}" />
        </Grid.Resources>
        <Border>
            <Border.DataContext>
                <local:MyRotation
                    Rotation="45" />
            </Border.DataContext>
            <Viewbox
                Margin="24"
                Projection="{StaticResource myProjection}">
                <TextBlock
                    Text="Hello World" />
            </Viewbox>
        </Border>
    </Grid>
</UserControl>

then it’s not immediately obvious to be whether the Projection will end up with rotation values of –45 ( from the Grid’s DataContext ) or +45 degrees ( from the Border’s DataContext )

It actually ends up with –45 degrees because the PlaneProjection “belongs” to the Grid and the Grid has a DataContext and the DataContext is the negativeRotation resource.

Note that ( if I’ve got my examples right ) then this is in contrast to this sort of example involving FrameworkElements like TextBlock;

<UserControl x:Class="SilverlightApplication22.MainPage"
    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:sys="clr-namespace:System;assembly=mscorlib"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <sys:String
            x:Key="stringOne">String One</sys:String>
    </UserControl.Resources>

    <Grid
        x:Name="LayoutRoot"
        Background="White"
        DataContext="{StaticResource stringOne}">
        
        <Grid.Resources>
            <TextBlock
                x:Key="myTextBlock"
                Text="{Binding}" />
        </Grid.Resources>
        
        <Border>
            <Border.DataContext>
                <sys:String>String Two</sys:String>
            </Border.DataContext>
            <Viewbox
                Child="{StaticResource myTextBlock}" />            
        </Border>

    </Grid>
</UserControl>

which displays “String Two” rather than “String One” and so works the other way around ( hope I got that example right ).

So, there’s going to be some edge cases to understand around how this works and it’ll take a bit of reading but, generally, the way in which it works is intuitive and it’s better than not being able to make these kinds of bindings!

Binding to String Indexers

Lots of people have written classes like this in the past;

 public class Customer
  {
    public Customer()
    {
      otherFields = new Dictionary<string, object>();
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public object this[string indexer]
    {
      get
      {
        return (otherFields[indexer]);
      }
      set
      {
        otherFields[indexer] = value;
      }
    }
    Dictionary<string, object> otherFields;
  }

and Silverlight 4 introduces new binding syntax that makes it easy to bind to those ( string indexed ) properties that are hidden away in that dictionary. That is, I can author XAML like;

<UserControl x:Class="SilverlightApplication23.MainPage"
    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:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <df:DataGrid
        AutoGenerateColumns="False"
        ItemsSource="{Binding}">
        <df:DataGrid.Columns>
            <df:DataGridTextColumn
                Header="Forename"
                Binding="{Binding FirstName}" />
            <df:DataGridTextColumn
                Header="Surname"
                Binding="{Binding LastName}" />
            <df:DataGridTextColumn
                Header="Balance"
                IsReadOnly="False"
                Binding="{Binding [BankBalance]}" />
            <df:DataGridTextColumn
                Header="Address"
                IsReadOnly="False"
                Binding="{Binding [Address]}" />
        </df:DataGrid.Columns>
    </df:DataGrid>
</UserControl>

using a DataGrid and note that both the Balance and Address columns are bound to indexed properties from the DataContext which is just an array of Customer as below;

 public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      this.Loaded += (s, e) =>
        {
          Customer c = new Customer()
          {
            FirstName = "Fred",
            LastName = "Smith"
          };
          c["BankBalance"] = 1000.5m;
          c["Address"] = "1, The Street, The Town, The Country";

          this.DataContext = new Customer[] { c };
        };
    }
  }

and I get my UI;

image

Note that in the preview that I wrote the XAML against I had to explicitly set the IsReadOnly flag – not sure if that should be strictly necessary.

One of the downsides here is that the type information isn’t round tripped from getter to setter. In my example I’m setting the “BankBalance” property to a decimal value but if I make a change to it in the editor then it’s going to come back to me as a string from the TextBox so you’d need to do more work there to preserve the type.

The other thing is how to do property change notifications for this kind of property? In my case I think the only way to do that is to fire change notification with the property name set to String.Empty or perhaps Null and ( from a quick experiment ) that seemed to work fine although in my particular case I noticed that doing this caused the DataGrid to re-read everything from my object ( not just the indexed properties ) so it’s perhaps not quite right.

There’s some other bits that relate to binding in Silverlight 4 but I’ll put them into a separate post – this one is getting long.