Click here to Skip to main content
Click here to Skip to main content

WPF: XmlDataProvider Two-Way Data Binding

By , 11 Jun 2008
 

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:

 <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.

<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.

<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.

<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.

<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.

/// <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)

About the Author

Ryan Bost
Software Developer (Senior)
United States United States
Member
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
  •  

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionnavigate items using buttons [modified]membermiga11129 Nov '11 - 9:14 
hi, how can i change the code so i can navigate using 2 buttons instead of the listbox ?
a forward button and a back button without binding to the listbox (assume that the listbox does not exists)?
 
and is there a way to read the xml as stream and assign it as a binding source ?
(i have a quiz app and i would like to shuffle the questions in the xml file everytime i display a new quiz !!)
 
thanks in advance
Please HELP

modified 30 Nov '11 - 14:40.

GeneralMy vote of 5memberdelibey7 Nov '11 - 10:11 
verry good thx
GeneralBound target only updating when application is restarted/reloadedmemberMember 779681729 Mar '11 - 12:34 
Hi Ryan,
 
Great article(s).
 
Regarding what you mentioned in the first paragraph of the section "Two-Way Binding" concerning the shortcomings, I experience a similar issue in that an operation is made from the window to affect the source xml file (by either adding, editing or deleting an element) and the xml file is modified, however the changes/updates are not reflected in the target control (and in this case the listbox) unless the app is restarted.
 
The idea of my code is this, I have a listbox control bound to an xml file as source and another listbox bound to the first listbox control. Operations of add, edit and delete are performed in c# to modify the xml file as appropriate however the changes do not persist to the listbox controls unless the application is reloaded/restarted. The mode is set to two way and the binding seems right as the xml elements are displayed as listbox items when the application is started.
 
Showing the code may give more insight and I know that the error may be a minor one perhaps something overlooked, but I have been at this obstacle for weeks now. Any help is appreciated.
 
Thanks
 
IB
GeneralRe: Bound target only updating when application is restarted/reloadedmemberRyan Bost31 Mar '11 - 15:18 
It depends how you are persisting your changes to your xml file. If you are using an XmlDataProvider, then you need to make changes to the XmlDataProvider's Document property and then persist that document to the underlying xml file. By making changes to the Document property, your UI should reflect the changes. Then the persisting of the changes to the underlying xml file will keep it in sync with the in memory Document.
 
I'm assuming that you are bypassing the update to the Document property and writing directly to the underlying xml file. This would explain why you only see the changes on restart as the XmlDataProvider only loads that document when you set it's source property.
 
I extended this example in another article to show CRUD operations on the file. You can see it here WPF: XmlDataProvider Two-Way Data Binding - Enhanced.
 
Hope this helps.
Ryan Bost

Generalnice onemvpSacha Barber11 Jan '11 - 2:46 
I like this, have a 5
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralGreat article! However, data not displaying in listbox when I run sample projectmemberrich123xyz10 Jan '11 - 12:33 
Thank you for sharing your sample project and article. I am new to WPF. I unzipped the sample project and opened it in VS2008. When I run the sample -- the form displays -- but no data from the xml file is displayed. May I ask what I am missing?
 
Thanks
GeneralRe: Great article! However, data not displaying in listbox when I run sample projectmemberRyan Bost10 Jan '11 - 15:47 
Are you running your Visual Studio as Administrator? The project copies the Teams.xml file to the output directory for reading/writing. If you aren't running Visual Studio as Administrator, it may not allow access to the file. I'm guessing at that as I always run Visual Studio with Administrative privileges.
 
If that doesn't correct your issue, do you see the data in the designer when you open the Window1.xaml file?
Ryan Bost

GeneralRe: Great article! However, data not displaying in listbox when I run sample projectmemberrich123xyz11 Jan '11 - 5:17 
Hi Ryan, Thanks for getting back to me. I log into my workstation with administrator rights (I think -- I can manipulate IIS, Defaul website, add websites on the local workstation) so I guess I am running VS as administrator. And yes, I can see the data in the listbox in the designer. But when I run the project the data does not display in the listbox. I have tried different configurations of the xml file - content/copy always, Resource/Do Not Copy, Resource/Copy Always, along with Content/Copy if Newer but still no data is displayed in run mode. Note: I was working on another sample project which uses an xml file as a datasource and having the same problem there too. Am I administrator in VS just because I logged in to my workstation with administrator rights?
GeneralRe: Great article! However, data not displaying in listbox when I run sample projectmemberrich123xyz11 Jan '11 - 5:24 
I just tried something else -- I moved the project to a different directory structure on C:\, now I can see the data. I have had this problem before when copying projects from the day job workstation to my home workstation where the project would not run from the default directory, but if I copied the project to a generic directory on the root then the project would run. So this is happening on my day job workstation also. Any idea what would cause this?
GeneralRe: Great article! However, data not displaying in listbox when I run sample projectmemberrich123xyz11 Jan '11 - 5:52 
BTW, this is an exellent example. It works exactly as advertised and really shows a great workaround for writing to/from XmlDataProvider.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 11 Jun 2008
Article Copyright 2008 by Ryan Bost
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid