Wednesday, July 15, 2009

[2009.07.15] WPF Centric XAML Markup Extensions [II/IV]

Binding Summary

In this section I will get into the most commonly used WPF centric markup extensions. WPF is very data oriented and as such has a rich data model. Data is a broad term but from the standpoint of WPF, it can be viewed as the representation of arbitrary objects in the .NET framework. Data binding then is the connection between these arbitrary pieces of data and how they communicate with each other.

Since "data" is such a loose term with respect to the WPF framework, then that implies that the connection between data can be more transparent. At the end of the day, data in .NET are just objects where data binding can occur between different "types" of objects such as WPF elements, CLR objects, database tables and XML. In addition, you have pretty fine grained control over how to interact with and present the data to the user. This little digression into data binding was necessary in order to illustrate how XAML markup extensions provide a clean interface to work with data binding.


The Binding Markup Extension

This is the big daddy of markup extensions and you can basically achieve all of your binding needs just using this extension. However, as I will illustrate, how you use it is more of a personal preference, as there are more succinct ways of achieving the same results using other markup extensions. If you want some sort of consistent look in your code perhaps, then just using this is the way to go. Either way, it is your choice.

Recall that XAML markup extensions provide a way to defer evaluation of the value of an attribute until the data is needed. So rather than using a literal string to indicate the value of the attribute, you use a binding as a place holder. The XAML parser, when encounters the notation for binding, which is {Binding}, it understands that the value for this attribute is not a literal string, and it comes from someplace else. So it will create an intermediate expression as a placeholder until the actual value is available, then substitute it back at runtime.

In order to get the actual value, the source of the data that has the value you want has to be examined and if the context in which it is used is something the compiler can understand, then the actual value that you want associated with the attribute will be assigned to it.

In case that did not make a whole lot of sense, look at it this way. I have an element with a particular property/attribute that I want to set. However, the value I want to use to set that attribute cannot be expressed as a literal string. So I need to use a binding to pull out the relevant information from some blob of data, and use that to set the value of the attribute. Since this blob of data is associated with my element, that is, it is the source of the data for my element, then the blob can be referred to as the data context of my element. Depending on the complexity of my blob, I will also need to instruct my binding expression what part of the blob I want returned to me.

The DataContext property/attribute is defined in the FrameworkElement class which is high up the class hierarchy of the WPF framework. That means the majority of WPF elements derive from it and as such, each one has the DataContext property. In addition, the FrameworkPropertyMetadata is set to Inherits which means that if an element does not explicitly specify a data context, it will try to inherit one from its parent element. ElementName lets you override the inherited data context and specify your own binding source.

A binding will typically consist of:

  • a target object
  • a target property
  • a binding source
  • a path to the value in the binding source to use

A simple example to envision is to connect to a web service, grab some text and display that in a TextBox element. Since I do not know what the text will be at compile time, you can use a Binding extension to defer the value of the Text property until runtime when your application can query the web service and return the requested data. Then that data can be bound to Text and displayed. This will become a lot clearer once the examples roll out.

Recall that Binding is the only extension that does not follow the naming convention for markup extension, namely appending the word Extension to the markup class. However, Binding derives from BindingBase which in turn derives from the MarkupExtension class. I could not find any information as to exactly why the naming convention was not followed but I will venture a guess. According to the WPF documentation, the Binding class was designed to be more than just a simple markup extension class. It has several additional members and properties that have nothing to do with XAML, but it does make the class more self-contained and versatile. This allows Binding to handle a wider variety of data binding scenarios as compared to how simple the other WPF markup extensions are.

Binding has one overloaded constructor that takes single parameter:

public Binding(string path)

Therefore, when you use Binding, you can indicate at least one positional parameter in the XAML implementation. At the same time, Binding also exposes a Path property which gets or sets a tokenized string that represents a PropertyPath object. The PropertyPath class implements a data structure for describing a property as a path below another property, or below an owning type [taken from Visual Studio 2010 B1 class summary]. Because Path is a named public property exposed by Binding then it can be used as the named parameter in a XAML implementation. Fig 1 is the list of all the properties/ named parameters that can be set by the Binding markup extension.

[2009.07.15].01.binding.markup.properties
Fig.1 Properties that can be set by Binding

Some of these properties cannot be set with a literal string as they require object types that do not support a native type conversion. That means that they also have to be set using markup extensions. One such instance is the RelativeSource property which can be used both as a data object and a markup extension. I will only go over examples that illustrate some of the more commonly used properties. The remaining can be worked out if need be.


Example 1:
Element to Element binding using markup extensions
Properties used:
ElementName (named parameter) : select a source to bind to
Path (named and positional parameter) : select the particular property in the source to bind to
Mode : determines the direction of data flow in the binding

The value of ElementName is the either the Name or the x:Name attribute of the source element. It is used primarily when you want want to bind properties of one WPF element to properties of another WPF element.

The value of the Path can actually take several different forms. In the simplest case, this can be a PropertyName. Other forms include sub-properties, attached properties, indexers of a property, a specific items in a collection or even a markup extension if the value is too complicated that it needs further evaluation.

The Mode of the binding takes its value from an enumeration with several possible values:

  • Default: whatever the default for that dependency property is set to be. In general, user-editable control properties, such as those of text boxes and check boxes, default to two-way bindings, whereas most other properties default to one-way bindings.
  • OneWay: target property is updated when source is changed.
  • TwoWay: changes in either source or target will automatically update the other one.
  • OneTime: target is updated when the application starts or the data context changes.
  • OneWayToSource: source is updated when the target is changed.

However, if the property you are binding to is not a dependency property, then in order for OneWay or TwoWay binding to work, the underlying class that has the property you are trying to bind to must implement the INotifyProperyChanged interface.

Note: ElementName is mutually exclusive with respect to both RelativeSource and Source properties. That means, only a single one can be used at a time in a binding markup. So if you want to use the ElementName, the other two cannot be included in the same markup extension.

Let's get our hands dirty with some code. The source/data context will be a TextBox that has the Name and Text properties set. The target for the binding will the Text property of a TextBlock and a TextBox element.

  1: <!-- the named source binding element -->
  2: <!-- will be the data context of the target -->
  3: <TextBox Name="textBox1" 
  4:          Text="markup what?" />
  5: 
  6: <TextBlock Name="textBlock1" 
  7:            Text="{Binding Text, ElementName=textBox1}" />
  8: 
  9: <TextBox Name="textBox2" 
 10:          Text="{Binding ElementName=textBox1, 
 11:                         Path=Text, 
 12:                         Mode=TwoWay}" />

Listing 1

Both the TextBlock and the TextBox will produce the exact same result. Notice however, the markup between the two is slightly different with respect to how the path is being set. Line 7 is the variation using the positional parameter based on the overloaded constructor of the Binding class whereas Line 11 uses the more explicitly named paramter for the path. Of course either way works just fine, but of course, the second one is more readable.

In addition, setting the Mode in Line 12 to be TwoWay means that if you change the text in textBox2, the both then source (textBox1) and whatever is bound to it (textBlock1) will also be updated.


Example 2:
Element to CLR Object binding using markup extensions
Properties used:
Source
Path

I have also used the StaticResource markup extension, but I will put that in its own section later on.

The object I am binding to is a simple custom class that exposes two public String properties.


  1: public class MarkupExtDemoClass
  2: {
  3:     public String MyProperty1 { get; set; }
  4:     public String MyProperty2 { get; set; }
  5: }
Listing 2

  1: <Window x:Class="MarkupExtDemo.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         xmlns:local="clr-namespace:MarkupExtDemo"
  5:         Title="ME Demo" Width="100" Height="100">
  6:     <Window.Resources>
  7:         <local:MarkupExtDemoClass x:Key="mrkupExtDemoResource"
  8:             MyProperty1="hello" MyProperty2="markup" />
  9:     </Window.Resources>
 10:     <StackPanel>
 11:         <TextBlock Name="textBlock1" 
 12:                    Text="{Binding Source={StaticResource ResourceKey=mrkupExtDemoResource}, Path=MyProperty1}" />
 13:     </StackPanel>
 14: </Window>
Listing 3

There are a few things going on here. Since I am using a custom class that is not part of the XAML namespaces, I have to import the namespace that my class is under into XAML and give it a matching namespace attribute given by xmlns:local in Line 4.

The next step is to create an instance of my class in XAML, and this is done under the Resources section of the root element. In Line 6-9, I created an instance of the class and also set its properties using just markup. Of course, this can be done in code as well. Note also that I have included a ResourceKey attribute. In this way, the instance of the class can be referred to by any child element of the Window using this key.

Line 11-12 is where the custom class is being used as the source for a binding to a TextBlock element. Here, I am using the Source attribute to explicitly refer to the data source and the Path simply grabs one of the properties of the custom class. Because the data is defined as a resource, the Source cannot be set with a simple literal value, and instead further markup is needed to resolve the resource reference.

Let's take a look at how the DataContext property of the TextBlock is inherited when not stated explicitly.

  1: <StackPanel DataContext="{Binding Source={StaticResource ResourceKey=mrkupExtDemoResource}}">
  2:     <TextBlock Text="{Binding Path=MyProperty1}" />
  3:     <TextBlock Text="{Binding Path=MyProperty2}" />
  4: </StackPanel>
Listing 4

In this snippet, all I have done is move the binding that referred to the defined resource to a top level container, the StackPanel in this case. Because a Source hasn't been set on either of the TextBlock elements, XAML will look at the parent element to see if a DataContext has been defined. Since this is the case, the parser will have found the source of the data and can now set the required Path properties.


Example 3:
Element to XML binding using markup extensions
Properties used:
Source
XPath

In this example, I will use the XmlDataProvider class to refer to an xml file. This is best shown by example.

Here is the contents of the XML file (rss.xml) that will be used. It is the RSS feed from MSDN and is stored locally in the application directory.

  1: <?xml version="1.0" encoding="UTF-8"?>
  2: <?xml-stylesheet type="text/xsl" href="http://www.microsoft.com/feeds/rsspretty.xsl" version="1.0"?>
  3: <rss
  4:  xmlns:mscomdomain="http://msdn.microsoft.com/aboutmsdn/rss/domains"
  5:  xmlns:mscom="http://msdn.microsoft.com/aboutmsdn/rss/"
  6:  xmlns:dc="http://purl.org/dc/elements/1.1/"
  7:  version="2.0">
  8:   <channel>
  9:     <title>MSDN: U.S. Local Highlights</title>
 10:     <description>The latest developer information for the United States.</description>
 11:     <link>http://msdn.microsoft.com</link>
 12:     <language>en-us</language>
 13:     <lastBuildDate>Sat, 06 Jun 2009 03:07:00 GMT</lastBuildDate>
 14:     <pubDate>Mon, 08 Jun 2009 20:00:00 GMT</pubDate>
 15:     <ttl>1440</ttl>
 16:     <generator>FeedForAll v2.0 (2.0.2.9) http://www.feedforall.com</generator>
 17:     <item>
 18:       <title>Write Secure Code Using the Security Development Lifecycle Process Template for VSTS</title>
 19:       <description>The SDL Process Template for VSTS integrates the policy, process, and tools of the SDL v4 into Visual Studio Team Systems 2008, and eases adoption of the SDL, enables auditable security requirements and status, and demonstrates security return on investment.</description>
 20:       <link>http://go.microsoft.com/?linkid=9669356</link>
 21:       <mscom:simpleDate>Jun 08</mscom:simpleDate>
 22:       <pubDate>Mon, 08 Jun 2009 20:00:00 GMT</pubDate>
 23:     </item>
 24:     <item>
 25:       <title>Mark Your Calendar for July 1, 2009 - Windows 7 Beta Expiration</title>
 26:       <description>Heads up! The Windows 7 Beta will expire on August 1, 2009, so be prepared and plan to rebuild your PC with either the release candidate (RC) or another valid version of Windows before July 1, 2009. You will receive a warning two weeks prior to July 1; after this date, your PC will begin shutting down every two hours.</description>
 27:       <link>http://go.microsoft.com/?linkid=9669357</link>
 28:       <mscom:simpleDate>Jun 08</mscom:simpleDate>
 29:       <pubDate>Mon, 08 Jun 2009 20:00:00 GMT</pubDate>
 30:     </item>
 31:   </channel>
 32: </rss>
Listing 5

  1: <Window x:Class="MarkupExtDemo.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         Title="ME Demo" Width="200" Height="170">
  5:     <Window.Resources>
  6:         <XmlDataProvider x:Key="xmlData1" Source="rss.xml" />
  7:         <XmlDataProvider x:Key="xmlData2" Source="rss.xml" XPath="/rss/channel/item" />
  8:     </Window.Resources>
  9: 
 10:     <StackPanel DataContext="{StaticResource ResourceKey=xmlData1}">
 11:         <!--inherited data context from StackPanel parent element 
 12:         with explicit XPath setting-->
 13:         <TextBlock Text="{Binding XPath=/rss/channel/item/title}" 
 14:                    Margin="5" TextWrapping="Wrap"/>
 15:     
 16:         <!--explicitly set data context but the path to the data
 17:         has been defined in the resource-->
 18:         <ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=xmlData2}}"
 19:                  DisplayMemberPath="title" Margin="5" /> 
 20:     </StackPanel>
 21: </Window>
Listing 6


[2009.07.15].02.binding.markup.example3
Fig. 2 Results of Example 3

As with the custom class, the XmlDataProvider class is also defined as a resource and a resource key is set for it. However, in the binding, the XPath attribute is used to drill into the correct XML tag to get the requested data. Also note how the path can either be defined locally or on the top level container.

No comments: