WPF: Visualizing arbitrary XML documents in a TreeView control

by Olaf Rabbachin 28. December 2009 14:08

 

When I was playing around with the TreeView control and HierarchicalDataTemplates yesterday I thought it would be nice to have some sort of generic piece of XAML/code (window) that would allow me to render an arbitrary XML document in a TreeView control. Surprisingly, a little search on the web turned up this thread in the WPF forum which was already very close to what I had in mind.

In this blog, I’ll show you how to create a window that’ll render arbitrary XML documents in a WPF TreeView control. The window will have an initial XML-doc provided directly via XAML, but will allow you to load any other XML document into the TreeView. I’ll also show how to, instead of iterating through all nodes via code, utilize Styles to help expanding or collapsing the entire XML hierarchy, allowing to then do the trick with a single line of code.

See the bottom of this post for downloading the complete solution (both VB and C# are provided).

Here’s an image of what the application’s window will look like:

Sample App - Startup-Screenshot 

Using a HierarchicalDataTemplate for rendering

We’ll need to define a HierarchicalDataTemplate that will render the XML-document’s nodes and values. Here’s its definition:

  1:   <HierarchicalDataTemplate x:Key="NodeTemplate">
  2: 	 <TextBlock x:Name="tb"/>
  3: 	 <HierarchicalDataTemplate.ItemsSource>
  4: 		<Binding XPath="child::node()" />
  5: 	 </HierarchicalDataTemplate.ItemsSource>
  6: 	 <HierarchicalDataTemplate.Triggers>
  7: 		<DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
  8: 		   <Setter TargetName="tb" Property="Text" Value="{Binding Path=Value}"></Setter>
  9: 		</DataTrigger>
 10: 		<DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
 11: 		   <Setter TargetName="tb" Property="Text" Value="{Binding Path=Name}"></Setter>
 12: 		</DataTrigger>
 13: 	 </HierarchicalDataTemplate.Triggers>
 14:   </HierarchicalDataTemplate>

It's actually pretty simple - each node is to be displayed in the TextBlock named "tb". To display data, we bind the HDT's ItemsSource to the path expression "child::node()" - this will display the child nodes for each child axis. However, the nodes in XML documents will contain either the node's name (i.e., an Element) or a the value-representation (i.e., a Value), hence we'll need to bind to either the element or the value. Attaching a DataTrigger to the NodeType property though, the TextBlock's Text-property - which actually renders the content - can be bound to the appropriate property.

Expanding and collapsing nodes

Since the TreeView control doesn't offer any method that would allow to easily expand or collapse all its nodes, we would obviously have to use code where we iterate through all the TreeView's nodes and set the IsExpanded-property to the appropriate value in order to allow users to expand/collapse by means of a button. Obviously, yes, but with WPF, a Style will do the trick without having to do the iteration. Here’s the XAML required for the two styles – one that will expand all nodes and one that’ll collapse them:

  1:   <Style x:Key="TV_AllExpanded"  TargetType="{x:Type TreeView}">
  2: 	 <Style.Resources>
  3: 		<Style TargetType="TreeViewItem">
  4: 		   <Setter Property="IsExpanded" Value="True" />
  5: 		</Style>
  6: 	 </Style.Resources>
  7:   </Style>
  8:   <Style x:Key="TV_AllCollapsed" TargetType="{x:Type TreeView}">
  9: 	 <Style.Resources>
 10: 		<Style TargetType="TreeViewItem">
 11: 		   <Setter Property="IsExpanded" Value="False" />
 12: 		</Style>
 13: 	 </Style.Resources>
 14:   </Style>

The trick here is to not only define the Style for TreeViewItems, but to wrap it in another style applied to the TreeView control itself.

With those two styles in place, we can simply apply the respective Style to the TreeView control itself rather than having to apply it to TreeViewItems. That said, expanding/collapsing the complete hierarchy is now a matter of a single line of code. For instance, to expand all nodes in C#, all we need is a:

tv.Style = (Style)this.FindResource("TV_AllExpanded");

Some sample XML-data

It would be nice to have some data to initially be displayed. Again, XAML to the rescue – while the XmlDataProvider enables us to load an arbitrary doc at runtime, we can also define XML-content directly in XAML. This MSDN articlecontains a nice little example that I burrowed for the blog. Here’s the appropriate section in our XAML:

  1: <XmlDataProvider x:Key="xmlDP" XPath="*">
  2:   <x:XData>
  3:     <Inventory xmlns="">
  4:       <Books>
  5:        <Book ISBN="0-7356-0562-9" Stock="in" Number="9">
  6:         <Title>XML in Action</Title>
  7:         <Summary>XML Web Technology</Summary>
  8:        </Book>
  9:        <Book ISBN="0-7356-1370-2" Stock="in" Number="8">
 10:         <Title>Programming Microsoft Windows With C#</Title>
 11:         <Summary>C# Programming using the .NET Framework</Summary>
 12:        </Book>
 13:        <Book ISBN="0-7356-1288-9" Stock="out" Number="7">
 14:         <Title>Inside C#</Title>
 15:         <Summary>C# Language Programming</Summary>
 16:        </Book>
 17:        <Book ISBN="0-7356-1377-X" Stock="in" Number="5">
 18:         <Title>Introducing Microsoft .NET</Title>
 19:         <Summary>Overview of .NET Technology</Summary>
 20:        </Book>
 21:        <Book ISBN="0-7356-1448-2" Stock="out" Number="4">
 22:         <Title>Microsoft C# Language Specifications</Title>
 23:         <Summary>The C# language definition</Summary>
 24:        </Book>
 25:       </Books>
 26:       <CDs>
 27:        <CD Stock="in" Number="3">
 28:         <Title>Classical Collection</Title>
 29:         <Summary>Classical Music</Summary>
 30:        </CD>
 31:        <CD Stock="out" Number="9">
 32:         <Title>Jazz Collection</Title>
 33:         <Summary>Jazz Music</Summary>
 34:        </CD>
 35:       </CDs>
 36:     </Inventory>
 37:   </x:XData>
 38: </XmlDataProvider>

Setting the display-root of the XML file

In addition, the sample app allows for specifying what node should be used as the root-node. That is, a TextBox control allows you to specify the XPath that should be the display-root. The sample XML-data (see the previous section) contains Books and CDs. If you wanted to only render the CDs in the TreeView, you could filter accordingly by specifying Inventory/CDs in the TextBox and clicking the Go-button. The result would look like this:

Sample App - XPath-Filter Screenshot

The code behind the Go-button is again very simple. Since we defined the XmlDataProvider in the Window’s resources, we can reference the resource and then, using whatever the user entered before clicking the Go-button, either apply that to the XmlDataProvider’s XPath-property or, if the Reset-button was clicked, reset the XPath to show all nodes by setting it to “*”:

  1: //Get a reference to the XDP that has been created as one of the Window's resources
  2: XmlDataProvider dp = (XmlDataProvider)this.FindResource("xmlDP");
  3: if (txt.Text == "")
  4:    //(Reset to root)
  5:    dp.XPath = "*";
  6: else
  7:    //Use the specified path as the new root display-node.
  8:    dp.XPath = txt.Text;

The sample solution

I’ve created a sample solution that contains everything discussed here, with a project for each the VB and the C# version. Download: Load_XML_Into_TreeView.zip (27.36 kb)


Location: SinglePost

Tags: , ,

WPF (.net) | XML

Comments are closed

About

Hi and welcome to my blog!

I'm a developer from Germany, currently focusing on .Net and WPF.

More about me ...