Mini-Tutorial – Element Binding - Jesse Liberty - Silverlight Geek Page view counter

Mini-Tutorial – Element Binding

What It Is

With element binding, it is now possible to bind the value of one object in your UI directly to a second object, without recourse to binding to a data object or handling events.

How You Do It

The canonical example (for me) is binding a TextBlock to the value of a slider. As the slider moves, the TextBlock is updated with the changing value.

SliderAndTextBlock

Two Approaches

Until Silverlight 3, the easiest way to get this to work was to bind both the Slider and the TextBlcok to a data object. When the slider’s value changed, a property in the data object would be modified, and the TextBlock, also bound to that property would be updated (assuming you had implemented the INotifyPropertyChanged interface as discussed here.

This can still be done, but now you can leave out the data object.

The story is told that Einstein’s explanation of how radio works went like this: “you see, telegraph is like a very long cat. You pull his tail in New York and he meows in L.A.  Radio is exactly the same, but without the cat.”

iStock_Cat_With_HeadphonesXSmall

In Silverlight 3 you leave out the cat, er, DataObject and bind the TextBlock’s text value directly to the Slider’s value:

   1: <TextBlock x:Name="myTextBlock"
   2:      Text="{Binding Value, Mode=OneWay, ElementName=mySlder}"/>

 

An Example is Worth A Thousand Words

The best way to see how the new approach works, and how it simplifies your code (and thus makes maintenance much easier) is to build an application that takes both the old and the new approach.

Our quick demo program will create two sliders, each with an associated TextBlock. The first pair will bind through the agency of a data object, and we’ll track the calls to the get and set of the properties of that data object.

The second pair will bind the Text property of the TextBlock directly to the Value property of the slider.

 

Building It

Begin by creating a new Silverlight Application. I’ll call my project ElementBinding.

I find it much easier to set up my form in Expression Blend 3, so I’ll open that to the same project as well, and draw four rows and 3 columns, adding in the two sliders, three TextBlocks and list box as shown here:

 

ElementBindingLayout

 

The Xaml this creates is shown below (collapsed to its bare essentials):

   1: <UserControl x:Class="ElementBinding.MainPage"
   2:     <!-- name spaces here -->
   3:     Width="650"
   4:     Height="300">
   5:  
   6:   <! other cool stuff here to look at later -->
   7:  
   8:     <!-- the grid created firt in Expression Blend
   9:        then tweaked here -->
  10:   <Grid x:Name="LayoutRoot"
  11:         Background="wheat"
  12:         Margin="10">
  13:    
  14:     <!-- columns and rows defined here -->
  15:  
  16:     <TextBlock x:Name="Title"   />
  17:  
  18:     <Slider x:Name="sBoundToObject"/>
  19:  
  20:     <Slider x:Name="sUnBound"/>
  21:  
  22:     <TextBlock x:Name="ValueFromObject"/>
  23:  
  24:     <TextBlock x:Name="valueFromElement" />
  25:  
  26:     <ListBox x:Name="MessagesFromSliders"/>
  27:   </Grid>
  28: </UserControl>

We’re going to examine all of this in depth, but not all at once.

Note that the first TextBlock (ValueFromObject) and its associated Slider (sBoundToObject)  will both be bound to the same data object.  This is the way things were done before Silverlight 3 and you may well have good reason to continue with this model (e.g., the data object will be applying a transformation or other logic that make the shared connection useful.

 

Element Binding

The new element-to-element binding, however, allows you to bind the second TextBlock (ValueFromElement) directly to a property of the Slider (sUnBound), dramatically reducing the amount of code needed to create this connection.

To be clear, if what you are doing is element binding, then this entire project could be stripped down to the creation of the slider and the TextBlock and the binding of the latter to a property of the former, with no helping code behind.

To see that, you can start a second project, name it ElementBindingSimple, and put the following into the MainPage.xaml file,

   1: <UserControl x:Class="ElementBindingSimple.MainPage"
   2:   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:   xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   5:   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:   mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   7:   <Grid x:Name="LayoutRoot">
   8:     <Grid.RowDefinitions>
   9:       <RowDefinition Height="2*" />
  10:       <RowDefinition Height="8*" />
  11:     </Grid.RowDefinitions>
  12:     <Grid.ColumnDefinitions>
  13:       <ColumnDefinition Width="1*" />
  14:       <ColumnDefinition Width="1*" />
  15:     </Grid.ColumnDefinitions>
  16:     <Slider x:Name="sUnBound"
  17:         Margin="5"
  18:         Grid.Row="0"
  19:         Grid.Column="0"
  20:         LargeChange="10"
  21:         Maximum="100"
  22:         SmallChange="1"
  23:         Value="50"
  24:         VerticalAlignment="Bottom" />
  25:     <TextBlock x:Name="valueFromElement"
  26:      Height="35"
  27:      HorizontalAlignment="Left"
  28:      Margin="5,0,0,0"
  29:      VerticalAlignment="Bottom"
  30:      Width="150"
  31:      Grid.Column="1"
  32:      Grid.Row="0"
  33:      FontFamily="Georgia"
  34:      FontSize="14"
  35:      TextWrapping="Wrap"
  36:      Text="{Binding Value, Mode=OneWay, 
  37:           ElementName=sUnBound}" />
  38:   </Grid>
  39: </UserControl>

You don’t even need to open the code-behind, just run it, as you move the slider, the TextBlock will instantly update with the slider’s current value.

Chasing The Magic

To chase down the difference in the two approaches, let’s get the data-object version working. First we need a data object to bind to. The simpler the better:

I’ll create a new class creatively named DataObject and as you would expect, it will implement INotifyPropertyChanged

In a somewhat unusual design, whose benefit will become apparent when we implement the code to tell us which getters and setters are being called, I’m going to pass the Slider into the constructor for the DataObject, and let the DataObject hold a reference to that slider.

Passing a reference to a UI object to a data object is almost never a great idea; we’re giving the data object way way way too much awareness of the UI tier.

Thus I tie my beloved encapsulation to a rock and flay it with a sharp code editor, in service to my greater concern: keeping our eye on how the slider and the data object and the TextBlock communicate.  (Forgive me 3 Amigos, for I know not what I do).

The constructor will stash the reference to the Slider object in a private member variable (see lines 4-9 below).

   1: public class DataObject  : INotifyPropertyChanged
   2: {
   3:    public event PropertyChangedEventHandler PropertyChanged;
   4:    private Slider mySlider;
   5:  
   6:    public DataObject(Slider theSlider)
   7:    {
   8:       mySlider = theSlider;
   9:    }
  10:  
  11:  
  12:    public double SliderValue
  13:    {
  14:       get
  15:       {
  17:          return mySlider.Value;
  18:       }
  19:       set
  20:       {
  21:          if (PropertyChanged != null)
  22:          {
  23:             PropertyChanged(this, new PropertyChangedEventArgs("SliderValue"));
  24:          }     // end if
  25:          mySlider.Value = value;
  26:       }        // end setter
  27:    }           // end property
  28: }           // end class
  29:  

With the appropriate Binding statements in both the the slider and the TextBlock the sequence of events will be that the user will move the slider, the slider will bind back to the instance of DataObject and the TextBlock, also bound to that instance, will be updated.

   1: <Slider x:Name="sBoundToObject"
   2:   Margin="5"
   3:   Grid.Row="1"
   4:   ToolTipService.ToolTip="Bound to an object"
   5:   LargeChange="10"
   6:   Maximum="100"
   7:   SmallChange="1"
   8:   Value="{Binding SliderValue, Mode=TwoWay}"
   9:   VerticalAlignment="Bottom" />
  10:  
  11: <TextBlock x:Name="ValueFromObject"
  12:     HorizontalAlignment="Left"
  13:     Margin="5,0,0,0"
  14:     Grid.Column="1"
  15:     Grid.Row="1"
  16:     Foreground="Blue"
  17:     VerticalAlignment="Bottom"
  18:     FontFamily="Georgia"
  19:     FontSize="14"
  20:     Width="150"
  21:     Height="35"
  22:     Text="{Binding SliderValue, Mode=TwoWay />
  23:  

Creating The Data Context

Both the slider and the TextBlock are bound to a property SliderValue, but not to a particular object that has that property. In the code-behind we solve that by setting the DataContext for the grid to an instance of DataObject (the dataContext is inherited by all the controls in the grid); see lines 7 and 8 below:

   1: public partial class MainPage : UserControl
   2: {
   3:     public MainPage()
   4:     {
   5:         InitializeComponent();
   6:         sBoundToObject.Value = 50;
   7:         DataObject d = new DataObject( sBoundToObject );
   8:         LayoutRoot.DataContext = d;
   9:     }
  10: }

This still won’t tell us much unless we display the calls to the getter and setter.

For that, we’ll use the Listbox and some code to tell the ListBox that getting and setting is happening, and who it is happening to! 

Let’s return to better practices, and accomplish the notification of a get or set by firing an event: MessageReady

   1: public class DataObject : INotifyPropertyChanged
   2: {
   3:    public event PropertyChangedEventHandler PropertyChanged;
   4:    public delegate void MessageReadyHandler(object sender, MessageEventArgs e);
   5:    public event MessageReadyHandler MessageReady;

Note that our event returns void and has the usual two parameters: an instance of type object, and an instance of EventArgs or a class that derives from EventArges, in this case, a class we’ll created called MessageEventArgs.

Since DataObject is the only class that cares about the MessageEventArgs class, it makes good sense to define it as a nested class as shown on line 3 below

   1: public class DataObject : INotifyPropertyChanged
   2: {
   3:    public class MessageEventArgs : EventArgs
   4:    {
   5:       public MessageEventArgs(object src, string msg)
   6:       {
   7:          Source = src; Message = msg;
   8:       }
   9:       public Object Source { get; set; }
  10:       public string Message { get; set; }
  11:    }
  12:  
  13:    public event PropertyChangedEventHandler PropertyChanged;
  14:    public delegate void MessageReadyHandler(object sender, MessageEventArgs e);
  15:    public event MessageReadyHandler MessageReady;

Using The Event To Display A Message

We’re now in a position to raise the MessageReady event whenever we have something for the ListBox to display. We’ll add code to the get and set of all one of our properties, and to be extra clever we’ll factor out the work of seeing if anyone is interested before we raise the event into a helper method.

   1: public partial class MainPage : UserControl
   2: {
   3:     public MainPage()
   4:     {
   5:          // register the handler for the event
   6:         d.MessageReady += ShowMessage;
   7:     }
   8:  
   9:     // the event handler asks the slider to identify itself before reporting the message
  10:     // and all of that is added to the list box
  11:    public void ShowMessage(object sender, DataObject.MessageEventArgs e)
  12:    {
  13:       Slider sldr = sender as Slider;
  14:       if ( sldr != null )
  15:       {
  16:          MessagesFromSliders.Items.Add(sldr.Name + ": " + e.Message);
  17:          MessagesFromSliders.SelectedIndex = MessagesFromSliders.Items.Count - 1;
  18:       }
  19:    }
  20: }

Testing That Sliders Can Send Messages

To test this, I’ll add a tiny bit of code to the DataObject that will take any slider and have it identify itself through this event handler:

public void SayHi(Slider sldr) { MessageReady(sldr, new MessageEventArgs(sldr, "Hi")); } 

 

Pass this helper method any slider and the slider displays its name followed by the word “Hi.”  Back in MainPage.xaml.cs we can call that method to  test that both sliders can display messages to the ListBox.

DataObject b = new DataObject( sBoundToObject );
LayoutRoot.DataContext = b;
b.MessageReady += ShowMessage;
 
// prove it works
b.SayHi(sBoundToObject);
b.SayHi(sUnBound);

 

The result is two lines added to the list box,

SlidersIdentify

Sending A Message On Each Get or Set

We now tell we tell the getter and setter of all (one) of the DataObject’s properties to report when they are invoked,

   1: public double SliderValue
   2: {
   3:    get
   4:    {
   5:       SendMessage("Getting: " + mySlider.Value.ToString("N2"));
   6:       return mySlider.Value;
   7:    }
   8:    set
   9:    {
  10:       if (PropertyChanged != null)
  11:       {
  12:          PropertyChanged(this, new PropertyChangedEventArgs("SliderValue"));
  13:       }     // end if
  14:       SendMessage("Setting: " + SliderValue.ToString("N2"));
  15:       mySlider.Value = value;
  16:    }        // end setter
  17: }           // end property
 
We’re almost home!  All would be well, but we want the TextBlock to format the slider’s value to two decimal places and there is not a way to do that with what we have so far. What we need is to give the Binding object, that mediates between the UI object (the TextBlock) and its data source (DataObject), a converter. The job of the converter will be to take a value and convert it into an appropriately formatted string,
 
   1: public class LongToFormattedString : IValueConverter
   2: {
   3:    public object Convert(
   4:     object value,
   5:     Type targetType,
   6:     object parameter,
   7:     System.Globalization.CultureInfo culture)
   8:    {
   9:       return string.Format(culture, "{0:N2}", value);
  10:    }
  11:  
  12:  
  13:    public object ConvertBack(
  14:      object value,
  15:      Type targetType,
  16:      object parameter,
  17:      System.Globalization.CultureInfo culture)
  18:    {
  19:       return System.Convert.ToDouble(value);
  20:    }
  21: }     // end class
 
 

Finally we must ensure that this DataConverter is used in the Xaml. That requires registering it as a resource at the top of the Xaml file,

   1: <UserControl.Resources>
   2:     <local:LongToFormattedString x:Key="longConverter" />
   3: </UserControl.Resources>

WE can now use that resource to instruct the binding object to call our converter when given a value:

   1: <TextBlock x:Name="ValueFromObject"
   2:     HorizontalAlignment="Left"
   3:     Margin="5,0,0,0"
   4:     Grid.Column="1"
   5:     Grid.Row="1"
   6:     Foreground="Blue"
   7:     VerticalAlignment="Bottom"
   8:     FontFamily="Georgia"
   9:     FontSize="14"
  10:     Width="150"
  11:     Height="35"
  12:     Text="{Binding SliderValue, Mode=TwoWay, 
  13:           Converter={StaticResource longConverter}}" />  
  14:           <!-- adding the converter  -->

That’s it, piece of pie. 

In the live demo, below, the two sliders identify themselves, and then as you move the sliders the top one makes many a call to the bound-to dataObject, but the bottom slider makes none, as it is bound directly to its slider.

 


Published Wednesday, July 22, 2009 12:54 AM by jesseliberty

Comments

# Mini-Tutorial – Element Binding - Jesse Liberty - Silverlight Geek

Thank you for submitting this cool story - Trackback from NewsPeeps

Wednesday, July 22, 2009 8:38 AM by NewsPeeps

# What’s New In Silverlight 3 – Element Binding

In Silverlight 3 you can now use one User Interface element as the binding object for another. This can

Wednesday, July 22, 2009 10:41 AM by Jesse Liberty - Silverlight Geek

# What’s New In Silverlight 3 – Element Binding

In Silverlight 3 you can now use one User Interface element as the binding object for another. This can

Wednesday, July 22, 2009 11:21 AM by Microsoft Weblogs

# re: Mini-Tutorial – Element Binding

If you have two UserControls within some container (a canvas, for example), is it possible to bind elements in one UserControl to elements in the other?

Wednesday, July 22, 2009 12:18 PM by prujohn

# Mini-Tutorial ??? Element Binding | Silverlight Coder

Pingback from  Mini-Tutorial ??? Element Binding | Silverlight Coder

Thursday, July 23, 2009 5:14 AM by Mini-Tutorial ??? Element Binding | Silverlight Coder

# What’s New In Silverlight 3? Easier Easing

Animated movement often seems much more natural and realistic if the moving object accelerates up to

Friday, July 24, 2009 10:55 AM by Jesse Liberty - Silverlight Geek

# Programming news: iPhone development emergency guide, Embarcadero's great deal | Programming and Development | TechRepublic.com

Pingback from  Programming news: iPhone development emergency guide, Embarcadero's great deal | Programming and Development | TechRepublic.com

# Programming with Silverlight, WPF &amp; .NET &raquo; Element Binding

Pingback from  Programming with Silverlight, WPF &amp; .NET &raquo; Element Binding

# Programming with Silverlight, WPF &amp; .NET &raquo; The Silverlight Geek Mini-Tutorials

Pingback from  Programming with Silverlight, WPF &amp; .NET &raquo; The Silverlight Geek Mini-Tutorials