Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / XAML
Article

WPF: XmlDataProvider Two-Way Data Binding

Rate me:
Please Sign up or sign in to vote.
4.91/5 (35 votes)
11 Jun 2008CPOL5 min read 229.7K   5.7K   57   45
How to perform two-way data binding with the XmlDataProvider within the Windows Presentation Foundation.

Introduction

This article is targeted for Beginner to Intermediate level WPF programmers. This article assumes that you have some basic knowledge of WPF in regards to layout. If you have no experience with WPF, I strongly suggest reading Sacha Barber’s “WPF: A Beginner’s Guide” series.

The Windows Presentation Foundation (WPF) provides developers with a vast amount of data binding functionality that has been praised by many, including yours truly. However, one area that I’ve found where the data binding falls short is with the XmlDataProvider. Unlike most data binding options in WPF, the XmlDataProvider does not natively support two-way binding. Meaning, if you make modifications to the data via a bound control, the changes are not persisted to the source XML file. This article will explain how to overcome that shortcoming and mimic two-way binding with the XmlDataProvider.

Background

The application in this article was developed with Visual Studio 2008, and targets the 3.5 version of the .NET framework.

Overview

The attached demo code consists of a WPF application with a single window and an XML file. Since I enjoy fantasy football, my XML file consists of NFL teams and their associated conferences. An example of the data within the Teams.xml file looks like this:

XML
 <Teams xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Team>
    <Id>1</Id>
    <Name>Arizona Cardinals</Name>
    <Conference>NFC West</Conference>
  </Team>
  <Team>
    <Id>2</Id>
    <Name>Atlanta Falcons</Name>
    <Conference>NFC South</Conference>
  </Team>
</Teams>

The single window in the application consists of several controls: a ListBox, three Labels, and three TextBoxes. The ListBox displays the NFL team name. The three TextBox controls display the team data associated with the currently selected team within the ListBox.

The WPF window looks like this:

AppScreenshot.gif

One Way Binding

I won’t get into the details of layout here. However, I want to point out the items within the XAML that make the binding from the source to the control possible. The first, and the most important, item is the XmlDataProvider. The XmlDataProvider specifies the underlying source XML file through its Source attribute. The XPath attribute is used to specify the level at which the data binding occurs. The x:Name attribute exposes the XmlDataProvider to the code-behind.

XML
<Grid.DataContext>
  <XmlDataProvider x:Name="TeamData" Source="Teams.xml" XPath="Teams/Team" />
</Grid.DataContext> 

You’ll notice in the example above that the XmlDataProvider is contained within Grid.DataContext tags. The layout of the application’s single window is a Grid. Since the XPath statement starts the binding at the team level and the XmlDataProvider is defined within the Grid’s DataContext, the Grid is bound to the data at the team level.

Note: The binding does not have to be done at the Grid level. We could bind the ListItem and other controls to the data within the XmlDataProvider individually. However, there is a synchronization issue when multiple controls bind directly to the XmlDataProvider and use the XPath statement. Explanation of the issue is out of the scope for this article, but can be found at Ian Griffiths’ blog. The approach recommended by Ian was incorporated into this article and the demo code.

The next item in the binding architecture is the ListBox. Since the ListBox is contained within the Grid, and the Grid is bound to the data at the team level, the bindings can be inherited. We can accomplish this simply by setting the ListBox's ItemsSource property to the current bindings.

XML
<ListBox x:Name="TeamsListBox" Margin="0,0,0,20" DockPanel.Dock="Left"
    ItemsSource="{Binding}"
    ItemTemplate="{StaticResource teamItemTemplate}"
    IsSynchronizedWithCurrentItem="True"
    Visibility="Visible" SelectionMode="Single">
</ListBox>

Each ListBox item is now bound to a Team element in the XmlDataProvider. To display the team name in the place of the ListBox item, we utilize the teamItemTemplate DataTemplate. The DataTemplate displays a Label control and utilizes the XPath attribute to traverse down to the Name element.

XML
<DataTemplate x:Key="teamItemTemplate">
    <Label Content="{Binding XPath=Name}"/>
</DataTemplate>

The final items in the binding architecture are the TextBox controls to display the selected team’s data. For layout purposes, all of the Label and TextBox controls for the team’s individual items are displayed within a StackPanel. The StackPanel, like the previously mentioned ListBox, is contained within the Grid. We again use the inherited bindings, and by way of the XPath attribute, traverse down to the corresponding element.

XML
<StackPanel Grid.Column="1" Margin="0,0,5,0">           
 <StackPanel Orientation="Horizontal">
   <Label Style="{StaticResource labelStyle}">ID:</Label>
   <TextBox Style="{StaticResource textboxStyle}" Text="{Binding XPath=Id}" />
 </StackPanel>
 <StackPanel Orientation="Horizontal" Style="{StaticResource fieldsetStyle}">
   <Label Style="{StaticResource labelStyle}">Team:</Label>
   <TextBox Style="{StaticResource textboxStyle}" Text="{Binding XPath=Name}" />
 </StackPanel>
 <StackPanel Orientation="Horizontal" Style="{StaticResource fieldsetStyle}">
   <Label Style="{StaticResource labelStyle}">Conference:</Label>
   <TextBox Style="{StaticResource textboxStyle}" Text="{Binding XPath=Conference}" />
 </StackPanel>            
</StackPanel>

This is all that is needed to provide one way binding of WPF controls to an XML file.

Two-Way Binding

Now that we can consume an XML file and bind it to our GUI, it would be really nice if we could persist any changes that we make to the data. Here is where we realize the shortcomings of the XmlDataProvider. If you modify the data within the Team Name textbox, you will see that the changes are persisted within the ListBox. Unfortunately, these changes are only in memory and are not persisted to the underlying XML file. If you stop and restart the application, the original data will be reloaded from the unchanged XML file. Luckily, we can very easily write some code to mimic the binding from the GUI back to the XML source file.

Before I discuss the code, there is one configuration option we must modify. Since the Teams.xml file will need to be deployed with the application’s executable, it is easier to copy it to the output directory upon a successful build. We can right click the Teams.xml file, choose Properties, and select the “Copy if newer” option for the Copy to Output Directory configuration. This will copy the XML file to the same location as the application’s executable. Now, anytime we need to reference the file, we can simply append “Teams.xml” to the path of the currently executing assembly.

CopyToOutput.gif

There are two code blocks that must be added to the window’s code-behind file to finalize the two-way binding architecture. First, we add code to set the Source property of the XmlDataProvider to the copy of the XML file located in the output directory. This ensures that we are reading from and writing to the same instance of the XML file. Second, we utilize an event handler to execute the code to persist the in-memory data to the XML file. In the demo, I used the Click event handler for the Save button. This could easily be modified to execute within a different event handler such as a TextBox.LostFocus or a Window.Closing event. To persist the changes, we simply call the Save method on the XmlDataProvider’s Document property and specify the XmlDataProvider’s Source as the destination file.

C#
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
   public Window1()
   {
       InitializeComponent();
       string appPath = System.IO.Path.GetDirectoryName(
         System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
       TeamData.Source = new Uri(appPath + @"\Teams.xml");
   }
   private void SaveTeamsButton_Click(object sender, RoutedEventArgs e)
   {
       string source = TeamData.Source.LocalPath;
       TeamData.Document.Save(source);
   }
}

In Conclusion

Although the XmlDataProvider does have its limitations, they are easily overcome with a few lines of code. Hopefully, this article will benefit those searching for an easy solution.

History

  • June 9, 2008 – Version 1.0 – Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Ryan is a good ol' boy from North Carolina who is obsessed with .NET and general web design.

When he is not coding, he enjoys...

  • spending time with his wife -- knowledge base of locations for all my worldly possessions--honey, where's my...
  • spending time with his twin sons -- my mom says this is payback for my behavior as a child : )
  • playing with his dogs -- they can't hear me calling them to come inside, but they can hear a refrigerator open from a different hemisphere
  • watching college basketball -- Go Tarheels!
  • watching movies from his DVD collection -- over 750 and counting

Comments and Discussions

 
GeneralGreat solution.. Here's is another way.. [modified] Pin
Jostein Kjellsen22-Feb-10 23:15
Jostein Kjellsen22-Feb-10 23:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.