Data Binding with WPF: Binding to XML
Join the DZone community and get the full member experience.
Join For FreeThis article is taken from WPF in Action with Visual Studio 2008 from Manning Publications. It demonstrates how binding to XML in WPF is extremely easy. For the table of contents, the Author Forum, and other resources, go to http://www.manning.com/feldman2/.
It is being reproduced here by permission from Manning Publications. Manning ebooks are sold exclusively through Manning. Visit the book's page for more information.
Softbound print: November 2008 | 520 pages
ISBN: 1933988223
Use code "dzone30" to get 30% off any version of this book.
The idea behind data binding is not all that complex. Given that essentially all applications are some sort of user interface over some kind of data, the problem of connecting that data to the interface is a problem that virtually every application must handle. This is precisely the problem that data binding addresses—connecting data instances to user interfaces—fast, and in a way that requires a minimum of effort and code. As usual, the devil lies in the details.
For the longest time, every application had its own approach for tying data to UI. Over time, though, different frameworks tried to genericize the problem—with various degrees of success. Windows Forms was the first Microsoft UI technology to really have a solid data binding model, which it did by baking binding deep into the framework. WPF takes this even further. Data binding has the status of a “first class citizen” in WPF, and support is pervasive and flexible.
In Windows Forms, certain properties of certain objects were set up to allow data binding, and only that limited subset of properties supported binding. In WPF, almost every property you can think of can be bound—certainly every property that participates in the property system.
Binding to XML
WPF supports binding directly to XML objects, as we will demonstrate in this section. For this exercise, we really wanted to push the binding system, so we found some nice, large XML examples.
vulnerabilities and exposures in computer systems, and it just so happens the list is published as an XML file. The latest version of the XML as of this writing weighed in at around 30MB. That sounds like a nice chunk of XML to give the binding engine to chew on. In effect, the XML is going to be our model. MITRE is a federally funded research lab. One of the projects MITRE works on is called the Common Vulnerabilities and Exposures (CVE) list. This list provides a single source to identify and describe
However, even for something like a web browser, an intermediate object model(1) is generally used to encapsulate behavior. For all but the simplest applications, using a data format as the abstraction for your model is almost certainly a lousy idea. If we were to actually write an application around CVEs, like a CVE editor, for instance, we’d build business objects with interactive behavior, and the details of how we stored it would be invisible from the UI.
That all being said, sometimes a light wrapping over XML or SQL is all you need. Along those lines, we’re going to create a little application to view the data in these XML files (figure 11.6).
Figure 11.6. The finished CVE Viewer utility
The CVE XML also provides us some nested data, which is something we’re after for this example. Before we get too far, though, we will need the XML data file—the CVE list from MITRE. The main site for CVE is located at:
and the CVE list downloads are available at:
http://cve.mitre.org/data/downloads/index.html
There are three files: All, CANs, and Entries. The Entries file is smaller (about 2MB) while the All and CANs files are closer to 30MB. For the purpose of this exercise, we want to see how the data binding will hold up under some pressure, so we downloaded the “All” file (allitems.xml) for our experiment. Feel free to choose the smaller file if you desire. Here is a sample entry from the allitems.xml:
<cve> <item type="CVE" name="CVE-1999-0002" seq="1999-0002"> <status>Entry</status> <desc>Buffer overflow in NFS mountd gives root access to remote attackers, mostly in Linux systems.</desc> <refs> <ref source="CERT">CA-98.12.mountd</ref> <ref source="BID" url="http://www.securityfocus.com/bid/121">121</ref> <ref source="XF">linux-mountd-bo</ref> </refs> </item> …A billion more items here…</cve>
We will be displaying the list of items in the left-hand column, and the details from the various tags on the right.
Creating the CVE Viewer application
Once you have the files, create a new WPF Application project called “CVE Browser” and as usual, delete Window1.xaml, create a new window called CveViewer.xaml, and point the StartupUri to it. The layout here is going to be a bit more involved than the ProcessMonitor, so we need to do a bit more setup than we did before. The final layout appears in figure 11.7.
Figure 11.7. The basic layout of the CVE Viewer application
To setup this layout, do the following:
- Divide the grid into three columns with widths of 120, 5, and 1*
- In the first column, create a DockPanel
- Add a TextBox followed by a ListControl in the DockPanel
- In the second column, add a GridSplitter. While the CVE names are currently a predictable width, we don’t want the app to behave poorly if more verbose names are used.
- In the third column, add a GroupBox
- In the GroupBox, we want some areas for description, references, and comments. The StackPanel is going to give us the document “effect” for this part of the UI, and we’ll style the TextBlock elements to look like headers.
Finally, we need some TextBox elements and Lists to display the nested lists of data for references and comments. Listing 11.4 shows the XAML for the layout, along with the controls we need.
Listing 11.4 XAML for CVE Viewer
<Window x:Class="CVE_Viewer.CveViewer" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase" Title="Common Vulnerabilities and Exposures Viewer" Width="600" Height="400"><Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="120" /> <ColumnDefinition Width="3" /> <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> <DockPanel> #1 <TextBox Name="filter" DockPanel.Dock="Top" /> <ListBox Name="listBox1" /> </DockPanel> <GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" /> #2 <GroupBox Grid.Column="2" Header="CVE Details"> #3 <StackPanel> <WrapPanel> <Label Height="23">Name:</Label> <Label FontWeight="Bold" Height="23" MinWidth="100" /> #4 <Label Height="23">Status:</Label> <Label FontWeight="Bold" Height="23" MinWidth="80" /> </WrapPanel> <TextBlock FontSize="12" FontWeight="Bold" Background="SteelBlue" Foreground="White" Padding="10,2,2,2">Description</TextBlock> #5 <TextBlock TextWrapping="Wrap" Margin="10,10,10,20" /> <TextBlock FontSize="12" FontWeight="Bold" Background="SteelBlue" Foreground="White" Padding="10,2,2,2">References</TextBlock> <ListBox Margin="10,10,10,20" BorderThickness="0" /> <TextBlock FontSize="12" FontWeight="Bold" Background="SteelBlue" Foreground="White" Padding="10,2,2,2">Comments</TextBlock> <ListView Margin="10,10,10,20" BorderThickness="0" /> </StackPanel> </GroupBox></Grid></Window>
(annotation) #1 Controls on left
(annotation) #2 Splitter
(annotation) #3 Detail data
(annotation) #5 Banners are just wide Blue TextBlocks
You may notice that there are a number of controls that don’t have any value (#4). That is because we are going to eventually bind their values to our source XML.
Binding controls to XML
For the next task, we’re going to use the XmlDataProvider. Like the ObjectDataProvider, the XmlDataProvider allows simple XAML based declaration of XML resources for use in your WPF application. In this case, we’re going to declare it as a resource on the top level Window element. Also, be sure to bring in a namespace to enable the PresentationTraceSources attribute on the window element itself:
xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Now, we’ll add the XmlDataProvider to the Window element (listing 11.4).
Listing 11.4 Adding the XMLDataProvider
<Window.Resources> <XmlDataProvider x:Key="cve" Source="X:\Path\to\allitems.xml" #1 XPath="/cve/item" #2 IsAsynchronous="False" #3 IsInitialLoadEnabled="True" #4 debug:PresentationTraceSources.TraceLevel="High" /></Window.Resources>
Ensure that you specify the correct path to the XML file in for the Source attribute (#1). Also note a few interesting attributes on this DataProvider which enable asynchronous loading of the XML document (#3). We are also telling the provider to automatically load the XML when the window is created (#4). The last line is to enabling debugging on the provider to make our lives easier later.
This is pretty much all we have to do to make the XML available to our application. We could just as easily pointed the provider to a valid URI, or brought in an XmlDocument or XmlReader. One attribute we haven’t mentioned, though is the XPath attribute (#2).
XPath is a standard for defining selections within XML. The standard is maintained by the W3C, and is one of the most common ways of selecting items from within an XML document. The particular expression here, /cve/item, says to select all of the item elements underneath the root cve element. This is our initial data set.
XPath binding notation
In the previous section, we used Path to specify the specific property we wanted to bind to. With the XmlDataProvider, the Path is still in play, but an additional property, called XPath, is going to be more interesting. The first binding we want is on the left-hand side ListBox. This will display all the CVEs in the XML data source:
<ListBox ItemsSource="{Binding Source={StaticResource cve}}">
So far, the only difference between the object binding and XML binding is the configuration of the data source. You may also notice in the designer that your ListBox now contains items from the live XML file. This can certainly be annoying at times, especially if your UI binds to a remote data source at design-time. At the same time, it’s rather convenient to see the effects of binding without having to run the program (figure 11.8)
Figure 11.8 The binding is executed in real-time against our data in the editor. It only looks like a bunch of errors because it is, well, a list of a bunch of errors.
Now we’ve got a rather ugly list, as it’s a list of the InnerText of the XmlElements. What we really want in the list on the left are the values from the name attributes of each item tag. As we did before, we need to set up a DataTemplate. Enter the following XAML within the ListBox tags:
<ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding XPath=@name}" /> </DataTemplate></ListBox.ItemTemplate>
“@name” is the XPath syntax to request an attribute called name from within the current element. Figure 11.9 shows the ListBox after the template has been defined.
Figure 11.9 Now we have a data template, the ListBox data is much more readable.
Much better—now our list is a lot more sensible. This is a good time to take a closer look at what is happening between the source (the XMLDataProvider) and the target (the ListBox) in this example. XML is a particularly good medium for exploring the relationship between sources and targets.
With this setup, the XPath we specified in the XmlDataProvider is exposing the XML document as a collection of XmlElements—the XPath defines the set of item nodes under the root cve node. So, our source is a collection of XMLElements of type item. Because ListBoxes can handle collections, all is well.
However, if we wanted to, we could change the source by removing the XPath expression. Go ahead and remove the XPath="/cve/item" attribute from the XmlDataProvider. Note that the list in the designer is now empty. The reason is that, without any XPath, the XmlDataProvider will provide the root element (the cve element) of the document. So the ListBox will attempt to display a collection with one item in it, but since the cve element doesn’t have a name attribute, it will not display anything at all.
To fix this, we can modify the ItemsSource attribute of the ListBox:
<ListBox ItemsSource="{Binding Source={StaticResource cve}, XPath=/cve/item}">
Now we’ve got elements again. That is because we are now telling the ListBox to bind to the specified XPath within the data provided by the data source. This gets us back to the same data we had before.
All we’re really demonstrating here is that, particularly with the power of XPath, there is no single “right” way to accomplish any particular binding. It is the binding itself that understands both sides of the relationship and does the mapping, so you can convert the source into a list of XmlNodes and take them as the default binding, or have the target do the job by applying an XPath to something “XPathable.”
Now let’s take a look at how you use Path vs. XPath.
Path vs XPath
Both Path and XPath provide a way to reference the “bit of data” we want out of our current item, but they have somewhat different applications. For example, you can think of our ListBox as showing a list of XmlNodes. We are using the XPath notation to select the name attribute from each of those nodes. However, XmlNode is an object with properties. If we wanted to access the value of a property of the XmlNode object (ignoring the fact that it happens to hold XML) we could use the Path notation. For example, if we wanted to get the OuterXml (a property of XmlNode) we could do it by specifying:
<TextBlock Text="{Binding Path=OuterXml}" />
This is something that would be hard to do using XPath.
Now you should have a list showing each ref XML item in the list. Among other things, this happens to be a convenient way to quickly visualize which XML elements are bound in a particular context, and what is available on them. When we first set up the XML bindings, we bound to the OuterXml everywhere to watch as the context of the data changed. Before we head to the next section, go ahead and set the binding back to using XPath:
<TextBlock Text="{Binding XPath=@name}" />
One thing that might not be entirely clear is how the binding knows what to execute the Path or the XPath against. The way this works is based on the current DataContext, which is what we will cover next.
Understanding and using DataContexts
Whenever you specify a binding, you are implicitly setting up a data context. A data context is basically the data source at any given visual element, and it is used by every subsequent element up the tree until it changes. For example, the ListBox’s data context is the collection of elements returned from the XmlDataProvider. Because the ListBox is designed to work with lists, it automatically doles out each element in the collection to each list item, so the data context for an individual item in the ListBox is the element from the collection.
We will take this a little bit farther, by hooking up some of the controls in the right-hand pane—the details from the currently selected item in the list box. UIElements all have a DataContext property, that specifies where they will go looking for data if no explicit source is specified as part of a Binding operation. We could set the DataContext on each of the controls that we want to bind, but since the DataContext is inherited, if we set it on the GroupBox that holds all of the controls, they will automatically have the same context:
<GroupBox.DataContext> <Binding ElementName="listBox1" Path="SelectedItem" /></GroupBox.DataContext>
What this says is that the DataContext for the GroupBox (and its children) is the SelectedItem property on the listBox1 ListBox control. Now, when we bind the individual elements, we just have to specify the binding relative to that data context. Figure 11.10 shows a visual representation of this. If we had an even deeper hierarchy, we could repeat this process ad nauseum.
Figure 11.10 Because a binding target can be a source as well, the detail view can bind to the SelectedItem of the UI List, rather than working out how to track the active item in the XML source itself.
We have four labels set up across a WrapPanel to show the name and status of each item as we click on it. Without defining any sources on the Label controls themselves, we can specify Path or XPath bindings as if we specified the XML element. Add the following Content tags to the Labels:
<WrapPanel> <Label Height="23">Name:</Label> <Label FontWeight="Bold" Height="23" Content="{Binding XPath=@name}" MinWidth="100" /> <Label Height="23">Status:</Label> <Label FontWeight="Bold" Height="23" Content="{Binding XPath=status}" MinWidth="80" /></WrapPanel>
We are binding the first label to the value from the name attribute and the second label to the value of the status element (since there is no @ sign in front of status, XPath interprets that to mean that we want the contents of a child element). Directly after the WrapPanel, we can now bind our description as well:
<TextBlock Margin="10,10,10,20" TextWrapping="Wrap" Text="{Binding XPath=desc}" />
Since there’s no selected item in the designer, the property will be null and we won’t see anything as we set all these up. However, when you run the application, you should be able to click through the list and see the name, description and status fields all populated. When the SelectedItem changes, the Binding we set on the DataContext property of the GroupBox catches the PropertyChanged event fired from the first ListBox and sets the DataContext accordingly. When the DataContext changes, the subsequent controls are then notified and all the bindings we just defined are re-evaluated and updated. Beautiful.
The next thing we want to do is to populate the ListBox that shows all of the “refs” from the item xml –hyperlinks to related data. Listing 11.5 shows the XAML for this.
Listing 11.5 Binding to the list of refs
<ListBox ItemsSource="{Binding XPath=refs/ref} #1 "Margin="10,10,10,20" BorderThickness="0" BorderBrush="Transparent"> <ListBox.ItemTemplate> <DataTemplate> <WrapPanel> <TextBlock MinWidth="50" Text="{Binding XPath=@source}" /> #2 <TextBlock> <Hyperlink NavigateUri="{Binding XPath=@url}" #3 RequestNavigate="Hyperlink_RequestNavigate"> <TextBlock Text="{Binding Path=InnerText}" /> </Hyperlink> </TextBlock> </WrapPanel> </DataTemplate> </ListBox.ItemTemplate></ListBox>
(annotation) #1 Bind to list of refs
(annotation) #2 Source from under ref
There is a fair amount going on here, so we’ll take it slow. First of all, we are setting the ItemsSource for the ListBox to use the XPath “refs/ref” (#1). Since we are inside of the DataContext set on the GroupBox, this XPath will be relative to that, so will return all of the ref elements under the refs element under the current item (no, really). Further, because we are setting the Source, we are implicitly setting a new DataContext that will apply to all of the items in the ListBox. Any binding that we do within an item will be relative to the current ref object.
The first control we are putting in our template is a TextBlock that is bound to the source attribute (#2). This is an attribute on ref elements. The next thing we want to do is create a hyperlink based on the data in the ref tag (#3). This is tricky because not everything inside of a Hyperlink can be directly bound. So, let’s take the pieces one at a time:
NavigateUri="{Binding XPath=@url}"
This is the easy one – we want the value from the URL attribute in the ref to be where the hyperlink will navigate us to.
RequestNavigate="Hyperlink_RequestNavigate"
This is just an event handler. The Hyperlink_RequestNavigate method gets the NavigateUri from the passed Hyperlink, and then does a Process.Start(). We haven’t bothered showing the code, but it is in the on-line version.
<TextBlock Text="{Binding Path=InnerText}" />
Because you can’t bind to the contents of a Hyperlink directly, we have to put something inside of the Hyperlink that will display the text we want to display. So we are putting a TextBlock inside of the Hyperlink (which is inside of a TextBlock) so that we can bind the TextBlock’s Text property. Notice that we are using Path instead of XPath here, since we want the InnerText of the XmlElement.
The binding for the comments ListBox is pretty similar, albeit simpler (listing 11.6):
Listing 11.6 Binding the list of comments
<ListView ItemsSource="{Binding XPath=comments/comment}" #1 Margin="10,10,10,20" BorderThickness="0" /> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=InnerText}"/> #2 </DataTemplate> </ListView.ItemTemplate></ListView>
(annotation) #1 Collecting all the comments from the item
(annotation) #2 Just bind the comment text, nothing fancy.
At this point, we have a functional CVE viewer that binds XML remarkably fast. With the XML support in WPF, creating custom editors for XML is extremely easy, and can be used to mitigate the pain of manually editing XML configuration files.
Master-Detail Binding
As you saw in figure 11.10, the list is driven from the data source, but the detail view is driven off of the list, rather than the data. From the user’s perspective, this is irrelevant, but there are certainly situations where you really want to make sure that what the user is viewing is tied to the data, and not a selected control. For example, if there are multiple controls that can potentially control the current record. Also, from a purist’s perspective, it is more “correct” to tie to data if possible (although not, perhaps as simple).
The nice thing is that WPF Data Binding has automatic handling for master-detail binding. If you bind a list data source to a list control, as we do above—tying the list of Items to the ListBox—then the list control shows a list of values. However, if you bind a non-list control to a list, like a TextBox, the binding mechanism automatically associates the binding with the current record in the list. So, instead of doing this:
<GroupBox.DataContext> <Binding ElementName="listBox1" Path="SelectedItem" /></GroupBox.DataContext>
We could do this:
<GroupBox.DataContext> <Binding Source="{StaticResource cve}"/></GroupBox.DataContext>
Which means that our individual controls are bound to exactly the same thing as the list. If you run the application with this binding, you will notice two things. First of all, the controls on the right of the application will all be populated even before you select a record in the list and second, changing the current selection in the list does not change what is displayed on the right.
So, the cool thing is that the binding code automatically knew what to do as far as figuring out to hand the current record to all of our controls on the right. The reason that we have data automatically is that the binding automatically assumes that the first record is the selected record. However, since we are no longer binding to the selected item in the ListBox, we need to somehow let the binding know that the “current” record has changed when the value changes in the ListBox. This is easily done by setting a property on the ListBox:
<ListBox Name="listBox1" ItemsSource="{Binding Source={StaticResource cve}}" IsSynchronizedWithCurrentItem="True">
What IsSynchronizedWithCurrentItem does is tell the ListBox to update the binding source with the currently selected item—assuming that the binding source is one that can handle that (which is most). Now, when you run, everything works as it did before, except that we are tied to the data source for the current item, rather than the ListBox. Figure 11.11 shows how this binding is working.
Figure 11.11 - It is perfectly legal to bind controls that can only handle a single item to a multiple-item data source. The master-detail support in WPF Binding will automatically associate those controls with the currently selected item.
Both approaches (binding to the selected item in a list or relying on master-detail support to automatically bind to the data source) produce the same results. For simple UI, the first approach makes it easier to see what is going on, while the second approach is more “correct.” For more complex situations, this correctness can often help make things work a little more cleanly.
(1) Of course, if you were writing an XML editor, these would be ideal domain objects.
Opinions expressed by DZone contributors are their own.
Comments