Where does a Binding find its data?
WPF 2007. 9. 20. 10:32 |Where does a Binding find its data?
If you’ve look at much WPF Xaml you’ve probably seen bindings like this:
<TextBlock Text="{Binding Name" />
… which binds the Text property of the TextBlock to the Name property of some data object.
The question that begets is: where does the data come from? The rest of this post looks at the answer.
Properties on Binding
The Binding class has a few properties that provide ways to let you set the source of the data onto the Binding, depending on what works best for your situation. Binding looks like this:
public class Binding : BindingBase
{
...
public object Source { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
...
}
The Source Property
The most straightforward way to set the source of the Binding is to use the Source property. One of the more common ways to do that is to reference the data out of a resource dictionary:
<Grid>
<Grid.Resources>
<Int32Collection x:Key='DataSource1'>1,2,3</Int32Collection>
</Grid.Resources>
<ListBox ItemsSource='{Binding Source={StaticResource DataSource1}}' />
</Grid>
(This example creates a ListBox with 3 entries in it, labeled “1”, “2”, and “3”.)
You can be even more explicit by setting the Source directly, specifying the Binding in full Xml syntax (rather than the above markup extension syntax):
<Grid>
<ListBox>
<ListBox.ItemsSource>
<Binding>
<Binding.Source>
<Int32Collection>1,2,3</Int32Collection>
</Binding.Source>
</Binding>
</ListBox.ItemsSource>
</ListBox>
</Grid>
You can also set the Binding.Source by referencing a static member:
namespace MyNamespace
{
class MyClass
{
public static List<int> MyData;
static MyClass()
{
MyData = new List<int>();
MyData.Add(1);
MyData.Add(2);
MyData.Add(3);
}
}
}
…
<Page
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:my="clr-namespace:MyNamespace" >
<ListBox ItemsSource='{Binding Source={x:Static my:MyClass.MyData}}' />
</Page>
The ElementName Property
Aside from the Binding.Source property, you can also reference your data by name, using Binding.ElementName, such as:
<StackPanel>
<TextBox Name='TextBox1' />
<Button Content='{Binding Text, ElementName=TextBox1}' />
</StackPanel>
(This example causes the button label to be whatever you type into the text box.)
The RelativeSource Property
The RelativeSource property provides a way to tell the Binding to look around where it’s used to find its source. For example, the ‘Self’ RelativeSource binds to the object on which the Binding is placed, such as in the following (which creates a TextBlock that says “Hi”):
<TextBlock Tag='Hi' Text='{Binding Tag, RelativeSource={RelativeSource Self}}' />
As another example, the following binds the TextBlock.Text property to the Grid in its ancestry (again displaying “Hi” in this case):
<Grid Tag='Hi'>
<Border Background='LightGray'>
<TextBlock
Text='{Binding Tag, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}}' />
</Border>
</Grid>
The other two modes of a RelativeSource are TemplatedParent and PreviousData.
Explicit DataContext Property
A common way the Binding can get its source is via a DataContext property set on an element (on the element itself, not on the Binding). For (a rather boring) example, this creates a Button that says “Click”:
<Button Content='{Binding}'>
<Button.DataContext>
<sys:String>Click</sys:String>
</Button.DataContext>
</Button>
This gets more interesting and typical if you set the DataContext somewhere else, usually on the root of the page/window. That works because the DataContext property inherits. The following example shows that by setting the DataContext onto the root. In this case, it is set to be an XmlDataProvider which is querying a Yahoo web service for the weather in Barrow, Alaska. Since the DataContext inherits, Bindings throughout the page have access to it automatically. Note also in this case that since we’re binding to XML, the Bindings are using XPath syntax.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.DataContext>
<XmlDataProvider
Source="http://xml.weather.yahoo.com/forecastrss?p=99723"
XPath="/rss/channel"/>
</Page.DataContext>
<Border BorderBrush="Black"
BorderThickness="1" Width="370" Height="170" CornerRadius="6">
<StackPanel>
<!-- Image for "Yahoo! News" -->
<Image Margin="15,15,0,0"
Stretch="None"
HorizontalAlignment="Left"
Source="{Binding XPath=image/url}" />
<!-- Text to say e.g. "Yahoo! Weather - Bellevue, WA", with a hyperlink to a
detailed weather page -->
<TextBlock Margin="15,15,0,0">
<Hyperlink NavigateUri="{Binding XPath=item[1]/link}">
<TextBlock Text="{Binding XPath=title}"/>
</Hyperlink>
</TextBlock>
<!-- Text to say e.g. "Conditions for Belleveue, WA at 9:53am ..." -->
<TextBlock FontWeight="Bold" Margin="15,15,0,0"
Text="{Binding XPath=item[1]/title}"/>
<!-- Weather details -->
<TextBlock Margin="15,0,0,0">
<!-- Text to say current condition and temp -->
<TextBlock>
<TextBlock.Text>
<Binding >
<Binding.XPath>
item[1]/*[local-name()="condition" and
namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@text
</Binding.XPath>
</Binding>
</TextBlock.Text>
</TextBlock>,
<TextBlock>
<TextBlock.Text>
<Binding >
<Binding.XPath>
item[1]/*[local-name()="condition" and
namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@temp
</Binding.XPath>
</Binding>
</TextBlock.Text>
</TextBlock>°
<TextBlock>
<TextBlock.Text>
<Binding >
<Binding.XPath>
*[local-name()="units" and
namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@temperature
</Binding.XPath>
</Binding>
</TextBlock.Text>
</TextBlock>
<LineBreak/>
<!-- Text to say sunrise/sunset times -->
Sunrise:
<TextBlock>
<TextBlock.Text>
<Binding >
<Binding.XPath>
*[local-name()="astronomy" and
namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@sunrise
</Binding.XPath>
</Binding>
</TextBlock.Text>
</TextBlock>, sunset:
<TextBlock>
<TextBlock.Text>
<Binding >
<Binding.XPath>
*[local-name()="astronomy" and
namespace-uri()="http://xml.weather.yahoo.com/ns/rss/1.0"]/@sunset
</Binding.XPath>
</Binding>
</TextBlock.Text>
</TextBlock>
</TextBlock>
</StackPanel>
</Border>
</Page>
… Sunrise at midnight, sunset at noon:
Implicit DataContext
Finally, there are a couple of places where the DataContext gets set automatically for you. That usually happens with ContentControl (e.g. Button) and ItemsControl (e.g. ListBox).
Here’s a ContentControl example. In this markup we have a DataTemplate set up in a ResourceDictionary so that it will be used for any ContentControl that has a String as its content. So it’s automatically picked up in the subsequent Button. In that DataTemplate, we have a binding to the data item, which in this case is the ContentControl.Content. Therefore what we get here is a Button that says “Click” in bold italic.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib" >
<Page.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<!-- The Text property is bound to the Content property of
whatever ContentControl with which this DataTemplate is used -->
<TextBlock Text='{Binding}' FontStyle='Italic' FontWeight='Bold' />
</DataTemplate>
</Page.Resources>
<Button>Click</Button>
</Page>
And here’s an ItemsControl example. In this markup, we have a ListBox bound to an integer collection again (using the inherited DataContext as the source), but this time we have an item template, which is a template to use when displaying the items in the ListBox. And within that item template, the DataContext is again automatically set to be the item
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml >
<Page.DataContext>
<Int32Collection>1,2,3</Int32Collection>
</Page.DataContext>
<!-- ItemsSource is bound to the DataContext (Int32Collection) -->
<ListBox ItemsSource='{Binding}' >
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Triggers>
<!-- Within the DataTemplate, the DataContext is set to be the data item.
So this DataTrigger.Binding property's DataContext is going to be
the item (1, 2, or 3) -->
<DataTrigger Binding='{Binding}' Value='2'>
<Setter Property='TextBlock.FontStyle' Value='Italic' />
<Setter Property='TextBlock.FontWeight' Value='Bold' />
</DataTrigger>
</DataTemplate.Triggers>
<Border Padding='10'>
<!-- Similarly this Text property is bound to the item (1, 2, or 3) -->
<TextBlock Text='{Binding}' />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Page>