Load a WPF TreeView control from an XML file in C#

[WPF TreeView]

The post Load a TreeView control from an XML file in C# explains how you can display XML data in a Windows Forms TreeView control. This example does something similar for a WPF project.

In keeping with the unofficial WPF slogan, “Twice as flexible and only five times as complicated,” this example uses data binding to tell the WPF TreeView control how to display the data it gets from its data source.

This example demonstrates three ways to load data from external XML files: in XAML code, by using an XmlDocument, and by setting an XmlDataProvider object’s Source property.

Before I get to that, I want to say a few things about the data files. First make sure the files are properly formatted. I had some element names such as “Sweet peas” that contained spaces. Those aren’t allowed by XML, but the .NET Framework objects provided error messages that gave no clue about what was wrong. In some cases they provided no error messages and the WPF TreeView just appeared empty. So save yourself a lot of time and confusion by making sure you test your program with properly formatted XML files.

For this example, I added the XML files to the project. The Build Action property for foods.xml is Resource.

For the other two XML files, I set their Build Action properties to Content and set their Copy To Output Directory properties to Copy If Newer. That way they are automatically copied where the executable program is built so the program can find them easily.

In XAML Code

The program’s XAML code loads the first set of data when the program starts.

<Window x:Class="howto_wpf_load_treeview_from_xml.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="howto_wpf_load_treeview_from_xml"
    Height="300" Width="300">
    <Window.Resources>
        <XmlDataProvider x:Key="food_provider"
            Source="foods.xml" XPath="/Items" />

        <HierarchicalDataTemplate x:Key="NodeTemplate"
            ItemsSource="{Binding XPath=./*}">
            <TextBlock x:Name="nodetext"/>
            <HierarchicalDataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=NodeType}"
                    Value="Element">
                    <Setter TargetName="nodetext" Property="Text"
                        Value="{Binding Path=Name}" />
                </DataTrigger>
            </HierarchicalDataTemplate.Triggers>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid DataContext="{StaticResource food_provider}" Margin="4">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <TreeView Name="trvItems" Grid.Row="0"
            ItemTemplate="{StaticResource NodeTemplate}"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            ItemsSource="{Binding}"
            VirtualizingStackPanel.IsVirtualizing="False"
            VirtualizingStackPanel.VirtualizationMode="Standard" />

        <StackPanel Orientation="Horizontal" Grid.Row="1"
            HorizontalAlignment="Center">
            <Button Name="btnLoadFlowers" Grid.Row="1" Margin="4"
                Content="Load Flowers" Width="100"
                Click="btnLoadFlowers_Click" />
            <Button Name="btnLoadCars" Grid.Row="1" Margin="4"
                Content="Load Cars" Width="100"
                Click="btnLoadCars_Click" />
        </StackPanel>
    </Grid>
</Window>

The Window.Resources section defines items that should be available throughout the window. It first creates an XmlDataProvider named food_provider. The object’s data source is the file foods.xml.

The XPath property indicates the path to the root of the data that should be displayed in the WPF TreeView. The value /Items tells it to start at the document’s root node and use the Items element. In this example, that is the top of the XML data. For other examples, you could change this to */Food to start the the Food node, or you could change it to */*/Desserts to start at the Desserts node. Search the internet or see my XML book to learn more about XPath.

Next the Window.Resources section creates a HierarchicalDataTemplate to explain how data encountered by the WPF TreeView should be displayed. The template is named NodeTemplate.

The ItemsSource property tells where to find the next level of data. In this example if you are at an XML element, the value ./* means “look at the children of this element.”

Next the template includes a TextBlock named nodetext. It will use that to display the current element’s data.

The template then defines a data trigger that activates when the current XML node has type Element. This applies to XML nodes such as <Food> and <Bananas \>.

When it finds such a node, the trigger uses a setter that sets the Text property of the nodetext control to be the current XML node’s Name. That makes the TextBlock display the element’s name.

After the Window.Resources section, the code defines the program’s Grid control. It sets the control’s DataContext equal to the food_provider defined in the Window.Resources section.

The code then defines the program’s TreeView control. It sets the ItemTemplate property to the NodeTemplate that was previously defined. It sets the ItemsSource property to the data source that the control should use. The value {Binding} binds the control to the current DataContext, which was set in the Grid control.

The code finishes by creating two Button controls.

When the program starts, the TreeView loads its data from the DataContext and uses the template to figure out what to display and how.

Using an XmlDocument

When you click the Load Flowers button, the following code displays the data in the file flowers.xml.

// Use an XmlDocuument to load flowers.xml.
private void btnLoadFlowers_Click(object sender, RoutedEventArgs e)
{
    // Get the file's location.
    string filename = System.AppDomain.CurrentDomain.BaseDirectory;
    filename = System.IO.Path.GetFullPath(filename) + "flowers.xml";

    // Read the file into an XmlDocument.
    XmlDocument doc = new XmlDocument();
    doc.Load(filename);

    // Make an XmlDataProvider that uses the XmlDocument.
    XmlDataProvider provider = new XmlDataProvider();
    provider.Document = doc;
    provider.XPath = "./*";

    // Make the TreeView display the XmlDataProvider's data.
    trvItems.DataContext = provider;
}

This code gets the location where the program is executing and uses it to build the full path to the flowers.xml file. It then creates an XmlDocument and uses it to load the file.

Next the code creates an XmlDataProvider and sets its Document property to the XmlDocument object. It also sets the provider's XPath property to ./*. That tells it to look at the children of the document's root. In this example, the top data node is Flowers so that's the top-level node the TreeView displays.

Finally the code sets the TreeView control's DataContext property to the provider so it displays the provider's data.

Setting XmlDataProvider.Source

When you click the Load Cars button, the following code executes.

// Use an XmlDataProvider's Source property to load cars.xml.
private void btnLoadCars_Click(object sender, RoutedEventArgs e)
{
    // Get the file's location.
    string filename = System.AppDomain.CurrentDomain.BaseDirectory;
    filename = System.IO.Path.GetFullPath(filename) + "cars.xml";

    // Make an XmlDataProvider that uses the file.
    XmlDataProvider provider = new XmlDataProvider();
    provider.Source = new Uri(filename, UriKind.Absolute);
    provider.XPath = "./*";

    // Make the TreeView display the XmlDataProvider's data.
    trvItems.DataContext = provider;
}

This code gets the file name for its XML file much as the previous code did. It creates a new XmlDataProvider and sets its Source property to a new Uri object representing the file's path. It then sets the provider's XPath property and sets the WPF TreeView control's DataContext property as before.

Conclusion

In all three cases, the TreeView uses the same ItemTemplate to decide how to traverse and display the XML data.

This example assumes the data is stored in XML elements. Many of the examples you'll see on the internet assume the data is stored as property values inside elements. For example, instead of having a leaf node <Beans/>, they might have something like <Vegetable Name="Beans"/>. You would need to modify the template to handle that format.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in controls, wpf, XAML, XML and tagged , , , , , , , , , , , , . Bookmark the permalink.

3 Responses to Load a WPF TreeView control from an XML file in C#

  1. Dev says:

    Very nice. How to extend it to get values of XML tags and display it on treeview as well. For example:

    In Xml
    Available
    Out of Stock

    And in treeview something like:
    Beans: Available
    Peas: Out if Stock

Leave a Reply

Your email address will not be published. Required fields are marked *