Abhi
Abhi

Reputation:

Binding XML data to WPF treeview Control

I have spend a lot of time trying to figure out how to bind data in my XML file to the TreeView control but I do not know where to start. I even tried going through Two-way binding of Xml data to the WPF TreeView and Josh Smith's code sample on codeproject, but still can't understand how to begin!!!

I have XML in in a file "C:\SPDependencies.xml" (I can change the format if required)!!!:

  <node type="SPDependencies" Name="SPDependencies">
        <node type="StoredProc" Name="SP1">
                <node type="OperationType" Name="Type1">
                        <node type="TableName" Name="Table1"/>
                        <node type="TableName" Name="Table2"/>
                </node>
                <node type="OperationType" Name="Type2">
                         <node type="TableName" Name="Table1"/>
                        <node type="TableName" Name="Table2"/>
                </node>
                 .....
        </node>
        <node type="StoredProc" Name="SP2">
              <node type="OperationType" Name="Type1">
              ...
              ...
        </node>
</node>

I need to display this in the Treeview control in the following format:

<SP1>
   <Type1>
      <Table1>
      <Table2>
      <Table3>
   <Type2>
      <Table1>
      <Table2>
      <Table3>
<SP2>
    <Type1>
........

Thanks, Abhi.

Upvotes: 3

Views: 17486

Answers (3)

I've figured out how to do this without stepping on TreeView.DataContext. Binding is easy. Codebehind is nearly as easy, but with one little gotcha.

You get nothing if you bind or assign XmlDataProvider to ItemsSource. It's not an IEnumerable (though it is INotifyPropertyChanged) and there's no implicit conversion. What you want is XmlDataProvider.Data, which is declared as Object, but I'm seeing a runtime type of XmlDataCollection (which inherits from ReadOnlyObservableCollection<XmlNode>).

MVVM

Binding Data is easy. I don't know if it's pure MVVM to put XmlDataProvider in your viewmodel, maybe not.

Viewmodel:

public XmlDataProvider ViewModelXMLDataProp { ... }

XAML

<TreeView
    ItemsSource="{Binding ViewModelXMLDataProp.Data}"
    ...
    />

Done -- that is, unless you need to use the XPath property of the Binding. If you do, you have to use the DataContext kludge. You can't set both Path and XPath on the same binding.

The XPath property of the XmlDataProvider does the same thing. If you can work with that, you're good.

You'd think Binding.Source would work, because that works when your XmlDataProvider is a static resource. When Binding.Source is a DataSourceProvider and Path is unspecified, Path defaults to Data:

<TreeView
    ItemsSource="{Binding Source={StaticResource MyXmlDataProviderResource}}"
    ...
    />

...but that works because you're giving it a static resource. The following actually binds to the string "ViewModelXMLDataProp" rather than looking to the DataContext for a property by that name. That's no good.

<TreeView
    ItemsSource="{Binding Source=ViewModelXMLDataProp}"
    ...
    />

Maybe you could write a MarkupExtension that'd make that work, but there's no need.

Codebehind

You should learn and use MVVM, but things happen for a lot of reasons and you didn't come here for a sermon.

Codebehind is a little trickier. TreeView.ItemsSource requires nothing more than that the object you give it must implement System.Collections.IEnumerable, so cast provider.Data to System.Collections.IEnumerable and don't worry about what the exact runtime type is.

Now here's the gotcha: XmlDataProvider.Data is populated asynchronously.

protected void LoadXML(String path)
{
    var provider = 
        new XmlDataProvider()
        {
            Source = new Uri(path, UriKind.Absolute),
            XPath = "./*"
        };

    //  FAIL: provider.Data is still null
    treeView.ItemsSource = (IEnumerable)provider.Data;
}

I found this to be an issue even when I create an XmlDocument, call XmlDocument.Load(), and assign the document to XmlDataProvider.Document. A Binding will still be hanging around when the Data property finally gets set, and it'll update ItemsSource then. But an assignment to ItemsSource in your code behind file does no such thing.

Contrary to ubiquitous folk belief at Stack Overflow, in the following code there is no binding happening, and in no sense has anything been bound:

//  NOT A BINDING
treeView.ItemsSource = someRandomCollectionOfStuff;

If nobody's creating an instance of System.Windows.Data.Binding or x:Bind, it's not a binding. This distinction matters: "Use the current value of x" is not the same concept as "indefinitely update with future values of y.x every time y raises PropertyChanged".

You could programmatically create a Binding or even handle PropertyChanged, but they went ahead and gave you a much simpler option. Just handle the XmlDataProvider.DataChanged event.

protected void LoadXML(String path)
{
    var provider = 
        new XmlDataProvider()
        {
            Source = new Uri(path, UriKind.Absolute),
            XPath = "./*"
        };

    provider.DataChanged += (s,e) 
        => treeView.ItemsSource = (IEnumerable)provider.Data;
}

And that does it. You could even keep that provider around, load new XML in it, and have the DataChanged event keep the treeview current. Seems like a waste of effort though.

Upvotes: 1

Denys Wessels
Denys Wessels

Reputation: 17049

Given the following xml file:

<node type="SPDependencies" Name="SPDependencies">
  <node type="StoredProc" Name="SP1">
    <node type="OperationType" Name="Type1">
      <node type="TableName" Name="Table1"/>
    </node>
    <node type="OperationType" Name="Type2">
      <node type="TableName" Name="Table1"/>
    </node>
  </node>
  <node type="StoredProc" Name="SP2">
    <node type="OperationType" Name="Type1">
    </node>
  </node>
</node>

View:

<Window x:Class="Tree.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Tree"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <HierarchicalDataTemplate x:Key="template">
            <TextBlock Text="{Binding XPath=@Name}" />
            <HierarchicalDataTemplate.ItemsSource>
                <Binding XPath="node" />
            </HierarchicalDataTemplate.ItemsSource>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid DataContext="{Binding Path=XmlData}">
        <TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource template}">
        </TreeView>
    </Grid>
</Window>

View model:

public class ViewModel
{
    public XmlDataProvider XmlData { get; set; }

    public ViewModel()
    {
        XmlData = new XmlDataProvider();
        XmlData.Source = new Uri(@"C:\input.xml");
        XmlData.XPath = "node";
    }
}

Output:

tree view output

If you only want to show the nodes below the root, simply change the XPath to:

XmlData.XPath = "/node/node";

Upvotes: 5

Crippeoblade
Crippeoblade

Reputation: 4065

Heres the tree:

<Window.Resources>
    <HierarchicalDataTemplate DataType="node"
                              ItemsSource="{Binding XPath=node}">
        <TextBox Width="Auto"
                 Text="{Binding XPath=@Name, UpdateSourceTrigger=PropertyChanged}" />
    </HierarchicalDataTemplate>

    <XmlDataProvider
        x:Key="xmlDataProvider"
        XPath="node" Source="C:\Data.XML">
    </XmlDataProvider>
</Window.Resources>
<Grid>
    <StackPanel>
        <Button Click="Button_Click">Save</Button>
            <TreeView
                Width="Auto"
                Height="Auto"
                Name="treeview"
                ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=.}"/>
     </StackPanel>
</Grid>

I've added a simple button to save changes. So for your Button_Click method in code behind:

XmlDataProvider dataProvider = this.FindResource("xmlDataProvider") as XmlDataProvider;
dataProvider.Document.Save(dataProvider.Source.LocalPath);

See here for an article about data binding and WPF.

Upvotes: 3

Related Questions