Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use HierarchicalDataTemplate to display XML Elements and Attributes?

I'd like to display arbitrary XML in a TreeView, with expanding and collapsing nodes, showing both the element name and the set of attributes and their values. I think I can do this with HierarchicalDataTemplate .

I've seen the hints to use HierarchicalDataTemplate to display arbitrary XML elements, and text nodes, like this:

  <Window.Resources>
    <HierarchicalDataTemplate x:Key="NodeTemplate">
      <TextBlock x:Name="tbName" Text="?" />
      <HierarchicalDataTemplate.ItemsSource>
        <Binding XPath="child::node()" />
      </HierarchicalDataTemplate.ItemsSource>
      <HierarchicalDataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
          <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
          <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
        </DataTrigger>
      </HierarchicalDataTemplate.Triggers>
    </HierarchicalDataTemplate>
    <XmlDataProvider x:Key="xmlDataProvider">
    </XmlDataProvider>
  </Window.Resources>
  ....

  <TreeView Name="treeView1"
          ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=*}"
          ItemTemplate= "{StaticResource NodeTemplate}"/>

Which works great. It displays the element names and text for each element. But my XML uses attributes to carry information. The schema is complex and I don't have a formal definition of it, so for now I am treating it as arbitrary XML.

The simplest document looks like this:

<c4soap name="GetVersionInfo" seq="" result="1">
  <versions>
    <version name="Director" 
             version="2.1.0.126418" 
             buildtype="" 
             builddate="Jun  1 2011" buildtime="14:52:43" />
    <version name="MediaManager" 
             version="2.1.0.126418" 
             buildtype="" 
             builddate="Jun  1 2011" 
             buildtime="14:36:17" />
  </versions>
</c4soap>

Using the above HierarchicalDataTemplate definition, I get this for a display:

enter image description here

Not quite what I want. For each node I want to display both the element name and the set of attributes and their values.

I tried this:

  <Window.Resources>
    <HierarchicalDataTemplate x:Key="NodeTemplate">
      <WrapPanel
          Focusable="False">
        <TextBlock x:Name="tbName" Text="?" />
        <TextBlock x:Name="tbAttrs" Text="?" />
      </WrapPanel>
      <HierarchicalDataTemplate.ItemsSource>
        <Binding XPath="child::node()" />
      </HierarchicalDataTemplate.ItemsSource>
      <HierarchicalDataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
          <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
          <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
          <Setter TargetName="tbAttrs" Property="Text" Value="{Binding Path=Attributes}"/>
        </DataTrigger>
      </HierarchicalDataTemplate.Triggers>
    </HierarchicalDataTemplate>
    <XmlDataProvider x:Key="xmlDataProvider">
    </XmlDataProvider>
  </Window.Resources>

... which gets me kinda close, but the Value="{Binding Path=Attributes}" results in a display of "(Collection)" in the TreeView.

enter image description here

How can I simply display all the actual attribute names and values, in addition to the element name?

like image 296
Cheeso Avatar asked Oct 03 '11 13:10

Cheeso


2 Answers

I added an ItemsControl into the template, like this :

<Window.Resources>
  <SolidColorBrush x:Key="xmlValueBrush" Color="Blue" />
  <SolidColorBrush x:Key="xmAttributeBrush" Color="Red" />
  <SolidColorBrush x:Key="xmlTagBrush" Color="DarkMagenta" />
  <SolidColorBrush x:Key="xmlMarkBrush" Color="Blue" />
  <DataTemplate x:Key="attributeTemplate">
    <StackPanel Orientation="Horizontal"
                Margin="3,0,0,0"
                HorizontalAlignment="Center">
      <TextBlock Text="{Binding Path=Name}"
                 Foreground="{StaticResource xmAttributeBrush}"/>
      <TextBlock Text="=&quot;"
                 Foreground="{StaticResource xmlMarkBrush}"/>
      <TextBlock Text="{Binding Path=Value}"
                 Foreground="{StaticResource xmlValueBrush}"/>
      <TextBlock Text="&quot;"
                 Foreground="{StaticResource xmlMarkBrush}"/>
    </StackPanel>
  </DataTemplate>

  <HierarchicalDataTemplate x:Key="nodeTemplate">
    <StackPanel Orientation="Horizontal"
        Focusable="False">
      <TextBlock x:Name="tbName" Text="?" />
      <ItemsControl
          ItemTemplate="{StaticResource attributeTemplate}"
          ItemsSource="{Binding Path=Attributes}"
          HorizontalAlignment="Center">
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
      </ItemsControl>
    </StackPanel>
    <HierarchicalDataTemplate.ItemsSource>
      <Binding XPath="child::node()" />
    </HierarchicalDataTemplate.ItemsSource>
    <HierarchicalDataTemplate.Triggers>
      <DataTrigger Binding="{Binding Path=NodeType}" Value="Text">
        <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Value}"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding Path=NodeType}" Value="Element">
        <Setter TargetName="tbName" Property="Text" Value="{Binding Path=Name}"/>
      </DataTrigger>
    </HierarchicalDataTemplate.Triggers>
  </HierarchicalDataTemplate>
  <XmlDataProvider x:Key="xmlDataProvider">
  </XmlDataProvider>
</Window.Resources>

Now it displays element names and the set of attributes and their values, like this:

enter image description here

like image 99
Cheeso Avatar answered Sep 28 '22 05:09

Cheeso


you also can use a template selector for the different node types and use the XPath node()|@* to loop thru all types of nodes:

<TreeView
    x:Name="TreeView"
    ItemsSource="{Binding}"
    ItemTemplateSelector="{DynamicResource ResourceKey=NodeTemplateSelector}">
    <TreeView.Resources>
        <HierarchicalDataTemplate x:Key="TextTemplate">
            <Grid>
                <uixml:TextInputControl DataContext="{Binding}" />
            </Grid>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate x:Key="AttributeTemplate">
            <Grid>
                <uixml:AttributeInputControl DataContext="{Binding}" />
            </Grid>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate x:Key="NodeTemplate" >
            <TextBlock Text="{Binding Path=Name}" />
            <HierarchicalDataTemplate.ItemsSource>
                <Binding XPath="child::node()|@*" />
            </HierarchicalDataTemplate.ItemsSource>
        </HierarchicalDataTemplate>
        <ui:XmlTemplateSelector
            x:Key="NodeTemplateSelector"
            NodeTemplate="{StaticResource NodeTemplate}"
            TextTemplate="{StaticResource TextTemplate}"
            AttributeTemplate="{StaticResource AttributeTemplate}" />
    </TreeView.Resources>
</TreeView>

and :

public class XmlTemplateSelector:DataTemplateSelector{
    public DataTemplate NodeTemplate { get; set; }
    public DataTemplate TextTemplate { get; set; }
    public DataTemplate AttributeTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
        XmlNode node = (XmlNode)item;
        switch (node.NodeType) {
            case XmlNodeType.Attribute:
                return AttributeTemplate;
            case XmlNodeType.Element:
                return NodeTemplate;
            case XmlNodeType.Text:
                return TextTemplate;
        }
        throw new NotImplementedException(String.Format("not implemented for type {0}", node.NodeType));
    }
}
like image 38
Bram Avatar answered Sep 28 '22 03:09

Bram