views:

973

answers:

4

Hello,

I am having some serious trouble creating a WPF TreeView with an Object databinding.

The application is config file editor. I have defined an Object structure which can be serialized to the correct XML format.

The problem I am having is formatting the object instance in the TreeView showing the correct hierarchy. The TreeView will only render the Channel node, and nothing else.

public class Objects
{
    public List<Channel> Channels { get; set; }
}

public class Channel 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public Reader Reader { get; set; }
    public Filters Filters { get; set; }
    public Router Router { get; set; }
    public Persister Persister { get; set; }
}

public class Filters : ArrayList
{
    public string StopOnFailure { get; set; }
}

public class Reader
{
    public string Id { get; set; }
    public string Name { get; set; }
}

All the child classes of Channel contain properties Id and Name. The Filters class is a collection of other types with the same property definition.

Here is the XAML

 <Window.Resources>
    <ObjectDataProvider x:Key="data"/>
    <DataTemplate DataType="{x:Type ConfigurationEditor:Channel}">
        <WrapPanel>
            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
            <TextBlock Text=" [" />
            <TextBlock  Text="{Binding Path=Id}" />
            <TextBlock Text="]" />
        </WrapPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <TreeView Margin="12,12,12,96" Name="treeView1" ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
    </TreeView>
</Grid>

The code behind to create the data instance

Objects config;
var serializer = new XmlSerializer(typeof(Objects));
using (var stream = new FileStream(@"C:\test.xml", FileMode.Open))
{
    config = (Objects)serializer.Deserialize(stream);
}

var dp = (ObjectDataProvider)FindResource("data");
dp.ObjectInstance = config;

I've looked at countless examples but I still can figure out what I am doing wrong. Thanks for the help.

Karl

Edit @Andy

Updated Code:

<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Objects}" ItemsSource="{Binding Path=Channels}"/>
<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Channel}" ItemsSource="Binding Path=Reader}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>

<DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
    <TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>

No change to the TreeView. With this change I still have only the Channel listed, and nothing else.

+1  A: 

Expanding on @Gimalay's answer, the problem is that the TreeView doesn't know where to get the data for any child nodes. You inform the TreeView by using a HierarchialDataTemplate, rather than a DataTemplate:

<HierarchialDataTemplate DataType="{x:Type ConfigurationEditor:Channel}"
    ItemsSource="...">
    <WrapPanel>
        <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
        <TextBlock Text=" [" />
        <TextBlock  Text="{Binding Path=Id}" />
        <TextBlock Text="]" />
    </WrapPanel>
</HierarchialDataTemplate>

The main difference between the two is the ItemsSource attribute. This is a binding expression that returns a collection of objects to use as child nodes.

The problem is that you have a few properties to get children from, not just one. You either need to combine them all into one property, or add another property that returns all of the child nodes.

Finally, you'll need to define a DataTemplate for each child item type, so that the TreeView knows how to display them (you can use a HierarchialDataTemplate for the children as well, if they in turn have child nodes).

Andy
The `Objects` class has a single property with a collection of `Channel` objects. So i created a `HierarchialDataTemplate` using that type and property. I can then display the `Channel` nodes by adding another template, however the leaf nodes are still not displayed
Karl
Could you post the updated code? That might help to debug the issue.
Andy
+1  A: 

I don't think you need this template, as Objects never come in the picture in your TreeView.

<HierarchicalDataTemplate
    DataType="{x:Type ConfigurationEditor:Objects}" 
    ItemsSource="{Binding Path=Channels}"/>

You have set this in XAML, which shows the Channels.. perfect!

<TreeView 
    Margin="12,12,12,96" Name="treeView1" 
    ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
</TreeView>

Now you want the Reader to be shown as well. But one can only specify objects of type IEnumerable as an ItemsSource, so the template below is incorrect which specifies "Reader" as the ItemsSource.

<HierarchicalDataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" 
    ItemsSource="{Binding Path=Reader}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>

So if you want reader name to be shown as well, use the template as shown below.

<DataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" >
    <StackPanel>
        <TextBlock Text="{Binding Path=Name}"/>
        <TextBlock Text="{Binding Path=Reader.Name}"/>
    <StackPanel>
</DataTemplate>
Trainee4Life
Thanks for your comment, it was helpful, but not the effect I am trying to achieve. Ultimately I want to have the `Channel` Object replicated as a tree. Meaning all the properties of the `Channel` appear as child-nodes in the TreeView.In the example you gave, the `Reader` name appears at the same level as the `Channel`. From the comment you made below, it seems what I am trying to do may not be possible.
Karl
+1  A: 

Just for the sake of it, hierarchical data templates are used when the items source actually has a hierarchy within itself. That is, the objects themselves have some hierarchy, with parent objects keeping a list of child objects.

As an example, may be the each Channel has a property SubChannels like below.

public class Channel 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public ObservableCollection<Channel> SubChannels { get; }
}

Then we could have used a template like this.

<HierarchicalDataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" 
    ItemsSource="{Binding Path=SubChannels}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>

Now the object structure could be multilevel deep with each subchannel again having subchannels.

Also, do note that in the example I just used the same type, Channel, to create a hierarchy of subchannels. We could have used another type, say each Channel having a list of readers.

Trainee4Life
A: 

Okay, After much trial an error I was able to get this to work as I wanted. Here is how i did it.

In my Channel object I added a new property that was a collection of all the other properties which I wanted to display in the TreeView

public ArrayList Components
    {
        get { return new ArrayList { Reader, Filters, Router, Persister  }; } 
        set
        {
            ArrayList list = value;
            if(list != null)
            {
                foreach (var item in list)
                {
                    if(item is Reader)
                    {
                        Reader = (Reader)item;
                    }
                    else if(item is Router)
                    {
                        Router = (Router) item;
                    }
                    else if(item is Persister)
                    {
                        Persister = (Persister) item;
                    }
                    else if(item is Filters)
                    {
                        Filters = (Filters) item;
                    }
                }
            }
        }
    }

Then my XAML is as follows to display the treeview.

    <HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Channel}" ItemsSource="{Binding Path=Components}">
        <WrapPanel>
            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
            <TextBlock Text=" [" />
            <TextBlock  Text="{Binding Path=Id}" />
            <TextBlock Text="]" />
        </WrapPanel>
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Filters}" ItemsSource="{Binding Path=.}">
        <TextBlock Text="Filters"/>
        <HierarchicalDataTemplate.ItemTemplate>
            <DataTemplate >
                <TextBlock Text="{Binding Path=Name}"/>
            </DataTemplate>
        </HierarchicalDataTemplate.ItemTemplate>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type ConfigurationEditor:Router}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type ConfigurationEditor:Persister}">
        <TextBlock Text="{Binding Path=Name}"/>
    </DataTemplate>

Thanks Andy for putting me on the right track. Karl

Karl