views:

9467

answers:

2

I have an XML that needs to be databound to a WPF TreeView. Here the XML can have different structure. The TreeView should be databound generic enough to load any permutation of hierarchy. However an XAttribute on the nodes (called Title) should be databound to the TreeViewItem's header text and not the nodename.

XML to be bound:

<Wizard>
  <Section Title="Home">
    <Loop Title="Income Loop">
      <Page Title="Employer Income"/>
      <Page Title="Parttime Job Income"/>
      <Page Title="Self employment Income"/>
    </Loop>
  </Section>
  <Section Title="Deductions">
    <Loop Title="Deductions Loop">
      <Page Title="Travel spending"/>
      <Page Title="Charity spending"/>
      <Page Title="Dependents"/>
    </Loop>
  </Section>
</Wizard>

XAML:

<Window x:Class="Wpf.DataBinding.TreeViewer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Wpf.DataBinding"
    Title="TreeViewer" Height="300" Width="300">
    <Window.Resources>
     <HierarchicalDataTemplate ItemsSource="{Binding Path=Elements}" x:Key="TVTemplate">
      <TreeViewItem Header="{Binding Path=Name}"/>
     </HierarchicalDataTemplate>
    </Window.Resources>
    <StackPanel>
     <TreeView x:Name="_treeView" Style="{StaticResource TVallExpanded}"
       ItemsSource="{Binding Path=Root.Elements}"
       ItemTemplate="{StaticResource TVTemplate}" />
    </StackPanel>
</Window>

XAML's codebehind that loads XML to XDocument and binds it to TreeView

public partial class TreeViewer : Window
{
    public TreeViewer()
    {
        InitializeComponent();
        XDocument doc = XDocument.Parse(File.ReadAllText(@"C:\MyWizard.xml"));
        _treeView.DataContext = doc;
    }
}

So in the XAML markup we are binding Name to TreeViewItem's header.

<TreeViewItem Header="{Binding Path=Name}"/>

However, I want to bind it to Title attribute of Section, Loop and Page in the Xml above. I read that it's not possible to use XPath while binding XDocument. But there has to be a way to bind the Title attribute to TreeViewItem's Header text. I tried using @Title, .[@Title] etc. But none seemed to work.

This thread on MSDN Forums has a similar discussion.

Any pointers would be greatly helpful.

+1  A: 

I think all you need to do is create a HierarchicalDataTemplate for each node type in your XML, load your xml it into an XmlDataProvider, and then bind that to the TreeView. The TV works with the XDP to bind data, and somewhere along the line they figure out what HDTs you have defined and match their DataType to the names of the nodes in your XML. You might have some issues with your XPATHs changing with the different types of data, but keeping those flexible is another question.

For example, I have a little regex test app. It includes a help system which is essentially all the different regex parts listed in a tree: Categories and parts with descriptions, tooltips, and other stuff. The data about the parts is stored as an xml data source. Since its static, I just created a static resource with the application's resources:

<XmlDataProvider
    x:Key="rxPartData"
    XPath="RegexParts">
    <x:XData>
     <RegexParts
      xmlns="">
      <Category
       Name="Character class"
       ToolTip="Sets of characters used in matching">
       <RegexPart
        Regex="[%]"
        Hint="Positive character group"
        ToolTip="Matches any character in the specified group (replace % with one or more characters)" />
       <!-- yadda -->
      </Category>
     </RegexParts>
    </x:XData>
</XmlDataProvider>

Next, I created HierarchicalDataTemplates for each node type in the data (again, all of this is in the application's resources):

<!-- Category data template -->
<HierarchicalDataTemplate
    DataType="Category"
    ItemsSource="{Binding XPath=*}">
    <TextBlock
     Focusable="False"
     Text="{Binding XPath=@Name}"
     ToolTip="{StaticResource CategoryTooltip}"
     ToolTipService.InitialShowDelay="0"
     ToolTipService.ShowDuration="{x:Static sys:Int32.MaxValue}"
     ToolTipService.HasDropShadow="True" />
</HierarchicalDataTemplate>
<!-- RegexPart data template -->
<HierarchicalDataTemplate
    DataType="RegexPart"
    ItemsSource="{Binding XPath=*}">
    <WrapPanel
     Focusable="False"
     ToolTip="{StaticResource RegexPartTooltip}"
     ToolTipService.InitialShowDelay="0"
     ToolTipService.ShowDuration="{x:Static sys:Int32.MaxValue}"
     ToolTipService.HasDropShadow="True">
     <TextBlock
      Text="{Binding XPath=@Regex}" />
     <TextBlock
      Text=" - " />
     <TextBlock
      Text="{Binding XPath=@Hint}" />
    </WrapPanel>
</HierarchicalDataTemplate>

Lastly, I just bound the tree to the XmlDataProvider:

<TreeView
  Name="_regexParts"
  DockPanel.Dock="Top"
  SelectedItemChanged="RegexParts_SelectedItemChanged"
  ItemsSource="{Binding Source={StaticResource rxPartData}, XPath=/RegexParts/Category}"
  ToolTip="Click the + to expand a category; click a part to insert it">
</TreeView>

And that's all you have to do. The TreeView and the XmlDataProvider will take care of finding and using the correct HDT's for the correct nodes in the data. The hardest part of all this is figuring out your xpaths for binding. It can get a little tricky, as if your paths are incorrect, you'll end up getting nothing in the tree and there won't be any errors (there are ways to increase error reporting in databinding in WPF, but that's another question).

Will
Thanks Will. You've added some really good info. But I don't like XmlDataProvider's in the case I am working with. Although my sample loads up the XML from a file, in reality I get XDocument directly to be bound to TV. I found the solution though. Will post it as answer.
Vin
As you can see from my example, using an XmlDataProvider can greatly simplify your xaml by allowing you to break out all the templates for your tree into HDT's. You can get that XDocument into one in your codebehind, no problem. But if this works for you, more power to ya.
Will
The thing here is, My XDocument should be an object representation of the TreeView. If I use XmlDataProvider they get decoupled, don't you think?
Vin
+3  A: 

Hurrah !!! I figured out how to bind XAttribute. It is not intuitive and it's not easily imaginable. But here is how it can be done.

<TreeViewItem Header="{Binding Path=Attribute[Title].Value}"/>

It is hard to imagine that Title can directly be used in square braces.

More @ this MSDN link

Vin
I think, it should Attributes instead of Attribute... At least for me it doesn't work...
binco
What about doing `Binding XPath=@Title`?
Sarah Vessels