System.Xml.Linq
Before binding XML data with WPF controls, let me give you an overview of the System.Xml.Linq
namespace. It contains the classes for LINQ to XML. LINQ to XML is an in-memory XML programming interface that enables you to modify XML documents efficiently and easily.
Using LINQ to XML, you can:
- Load XML from files or streams.
- Serialize XML to files or streams.
- Create XML trees from scratch using functional construction.
- Query XML trees using LINQ queries.
- Manipulate in-memory XML trees.
- Validate XML trees using XSD.
- Use a combination of these features to transform XML trees from one shape to another.
For data binding efforts, we need to focus on two classes in this namespace, namely: XAttribute
and XElement
. The dynamic properties of these classes help us to exactly bind the control to the respective elements in the XML data.
The XAttribute
class is derived from the XObject
class and it represents an XML attribute. Attributes are not derived from XNode
; they are not nodes in the XML tree. Instead, they are simply name/value pairs associated with an element.
The XElement
class is derived from the XContainer
class which derives from the XNode
class, and it represents an XML element, the fundamental XML construct. An element has an XName
, optionally one or more attributes, and can optionally contain contents like XElement
, XComment
, XText
, and XProcessingInstruction
.
Each XElement
contains a list of attributes for that element. Attributes must have a qualified name that is unique to the element.
As an example, the following C# code shows the common task of creating an element that contains an attribute:
XElement phone = new XElement("Phone",
new XAttribute("Type", "Home"), "555-555-5555");
Console.WriteLine(phone);
Dynamic Data Binding in WPF and Dynamic Properties
Having learnt that we have a couple of XLinq classes available with us, the next step is to understand how data binding can be performed with these classes.
By default, data binding occurs only when the target UI element is initialized. This is called one-time binding. For most purposes, this is insufficient; with two-way binding, changes to the source are automatically propagated to the target, and changes to the target are automatically propagated to the source.
For one-way or two-way binding to occur, the source must implement a change notification mechanism, for example, by implementing the INotifyPropertyChanged
interface or by using a PropertyNameChanged
pattern for each property supported.
Most LINQ to XML classes do not qualify as proper WPF dynamic data sources. To support WPF data binding, LINQ to XML exposes a set of dynamic properties. These dynamic properties are special run-time properties in the XAttribute
and XElement
classes to act as dynamic data sources for WPF and implement change notifications.
The dynamic properties in the XAttribute
and XElement
classes cannot be accessed like standard properties. In C#, dynamic properties can only be accessed at run time through facilities provided by the System.ComponentModel
namespace. However, in an XML source, dynamic properties can be accessed through a straightforward notation in the following form: object.dynamic-property.
The dynamic properties for these two classes either resolve to a value that can be used directly, or to an indexer such as Elements and Descendants property of XElement, that must be supplied with an index to obtain the resulting value or collection of values.
To implement WPF dynamic binding, dynamic properties will be used with facilities provided by the System.Windows.Data
namespace, most notably the Binding
class.
XML as Resource Data in XAML
Now, let us look at the various elements of XAML that can be used to define and use XML data. Also, this section explains the elements required for defining the resources, templates, and subsequent binding of these to the WPF controls.
Inside the Windows.Resources
tag, we can declare the data source to XML data using the <ObjectDataProvider>
and also a <DataTemplate>
that follows a certain pattern of data elements.
The <ObjectDataProvider>
tag declares an instance of ObjectDataProvider
class, named LoadedBooks
that uses an XElement
as the source. This XElement
is initialized by parsing an embedded XML document (a CDATA
element). Also, white space is preserved when declaring the embedded XML document and when it is parsed. The reason is that the TextBlock
control, which is used to display the raw XML, has no special XML formatting capabilities.
A DataTemplate named “BookTemplate
” is defined to display the entries in the respective controls in the Book List UI section. It uses data binding and LINQ to XML dynamic properties to retrieve the book ID and book name through the following assignments:
Text="{Binding Path=Attribute[id].Value}"
Text="{Binding Path=Value}"
<--<Window.Resources> tag with both
of these definitions is given below:-->
Once the DataTemplate
is ready as a data resource, data binding can be performed with the WPF controls in the user interface.
In the <StackPanel>
tag, the DataContext
property is set to the “LoadedBooks
” data provider.
DataContext="{Binding Source={StaticResource LoadedBooks}}
This makes it possible for the TextBlock
named tbRawXml
to display the raw XML by binding to this data provider's Xml
property:
Text="{Binding Path=Xml}"
The ListBox
in the Book List UI section sets the template for its display items to the BookTemplate
resource, and the actual values of the books are bound to the items in the list box.
<ListBox Name="lbBooks" Height="200"
Width="415" ItemTemplate ="{StaticResource BookTemplate}">
<ListBox.ItemsSource>
<Binding Path="Elements[{http://www.mybooks.com}book]"/>
</ListBox.ItemsSource>
</ListBox>
Upon selecting a book in the list box, we need to display the corresponding details in the textboxes for viewing and editing purposes. This requires a binding to the parent data source in the listbox, and a two way data binding so that the current values of the book elements are displayed to and updated from the two text boxes in this panel. Data binding to dynamic properties is similar to that used in the BookTemplate
data template:
<StackPanel Margin="1"
DataContext="{Binding ElementName=lbBooks, Path=SelectedItem}">
....
....
<TextBox Name="editAttributeTextBox"
Width="410" Text="{Binding Path=Attribute[id].Value}">
...
...
<TextBox Name="editValueTextBox" Width="410"
Text="{Binding Path=Value}" Height="25">
Walkthrough: Creating a WPF application that shows LINQ to XML Data Binding with Inline XML Data
Now, let us create a complete application that would make use of the above discussed features. This sample program enables the user to view and manipulate a list of books that is stored as an embedded XML element.
Start Visual Studio and create a C# WPF application named WPFDataBindingLinqToXml. The project must use the .NET Framework 3.5 (or later).
Design the UI as shown in the screenshot below as XAML markup in the Window1.Xaml.
Also, modify the code in the Window1.Xaml.cs file as below:
XNamespace mybooks = "http://www.mybooks.com";
XElement bookList;
public Window1()
{
InitializeComponent();
bookList = (XElement)((ObjectDataProvider)Resources["LoadedBooks"]).Data;
}
void OnRemoveBook(object sender, EventArgs e)
{
int index = lbBooks.SelectedIndex;
if (index < 0) return;
XElement selBook = (XElement)lbBooks.SelectedItem;
XNode nextNode = selBook.NextNode;
selBook.Remove();
if (nextNode != null && nextNode.ToString().Trim().Equals(""))
{ nextNode.Remove(); }
if (lbBooks.Items.Count > 0)
{ lbBooks.SelectedItem = lbBooks.Items[index > 0 ? index - 1 : 0]; }
}
void OnAddBook(object sender, EventArgs e)
{
if (String.IsNullOrEmpty(tbAddID.Text) ||
String.IsNullOrEmpty(tbAddValue.Text))
{
MessageBox.Show("Please supply both a Book ID and a Value!", "Entry Error!");
return;
}
XElement newBook = new XElement(mybooks + "book",
new XAttribute("id", tbAddID.Text),
tbAddValue.Text);
bookList.Add(" ", newBook, "\r\n");
}
Basically, the above code uses the XElement
, XNode
, and XNamespace
objects to manipulate XML data along with binding controls.
Lastly, build the solution by pressing Ctrl+Shift+B, and then run it by pressing F5. The project should compile without errors and run as a generic WPF application.
Using the WPFDataBindingLinqToXml sample application and its User Interface
The top section of the UI displays the raw XML that represents the book list. It is displayed using a WPF TextBlock
control, which does not enable interaction through the mouse or keyboard.
The second vertical section, labeled Book List, displays the books as a plain text ordered list. It uses a ListBox
control that enables selection though the mouse or keyboard.
The following tasks may be performed in this UI:
- To delete an existing book from the list, select it in the Book List section, then click the Remove Selected Book button. Notice that the book entry has been removed from both the book and the raw XML source listings.
- To add a new book to the list, enter values into the ID and ValueTextBox controls in the last section, Add New Book, and then click the Add Book button. Note that the book is appended to the list in both the book and XML listings. This program does not validate input values.
- Select the book entry in the second Book List section. Its current values should be displayed in the third section.
- To Edit Selected Book, edit the values using the keyboard. As soon as either
TextBox
control loses focus, changes are automatically propagated to the XML source and book listings.
Conclusion
This article has explained the theory and steps involved in developing a WPF application that can bind to XML data stored inline using LINQ to XML classes. The same can also be done with XML data stored in a file system. The next part of this article would explain how to bind to an external XML data with slighter modifications in this sample. Hope you enjoyed and found this article useful.