views:

491

answers:

3

Here's the problem. I'm binding a TreeView with a few different types of objects. Each object is a node, and SOME objects have a property called IsNodeExpanded, and of course, some others don't. Here's my style:

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
</Style>

Now, the problem is when binding the items that DON'T have this property, we get this error in the output:

System.Windows.Data Error: 39 : BindingExpression path error: 'IsNodeExpanded' property not found on 'object' ''CompensationChannel' (HashCode=56992474)'. BindingExpression:Path=IsNodeExpanded; DataItem='CompensationChannel' (HashCode=56992474); target element is 'TreeViewItem' (Name=''); target property is 'IsExpanded' (type 'Boolean')

Of course we get it a ton of times. So I'm trying to come up with a way to switch the style of the TreeViewItem based on the DataType it holds. Any idea on how to do this?

Some info: I can't do it manually for each item because I'm not creating them in XAML, they are created dynamically from a data source.

EDIT: I found this answer but it didn't work for me.

A: 

Would using a FallbackValue on the binding work for you? This would apply if the binding fails...

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay, FallbackValue=False}" />
</Style>
IanR
Thanks for the suggestion. It didn't work though; it only changed from "error" to "warning": System.Windows.Data Warning: 39 blah blah
Carlo
A: 

UPDATE

<TreeView.Resources>

    ... following is only for one type of data
    <HierarchicalDataTemplate 
      DataType="{x:Type local:RegionViewModel}" 
      ItemsSource="{Binding Children}"
      >

      ... define your style
      <HierarchicalDataTemplate.ItemContainerStyle>
           <Style TargetType="{x:Type TreeViewItem}" 
                  ... following line is necessary
                  BasedOn="{StaticResource {x:Type TreeViewItem}}">
                ..... your binding stuff....
           </Style>
      </HierarchicalDataTemplate.ItemContainerStyle>

      <StackPanel Orientation="Horizontal">
        <Image Width="16" Height="16" 
           Margin="3,0" Source="Images\Region.png" />
        <TextBlock Text="{Binding RegionName}" />
      </StackPanel>
    </HierarchicalDataTemplate>
...
</TreeView.Resources>

ALTERNATIVE WAY

Instead of Switching Styles, you should use HierarchicalDataTemplate and DataTemplate in order to style your TreeViewItem, they will work similarly unless you want to change certain inherited framework properties.

You can define different "DataTemplate" and "HeirarchicalDataTemplate" based on different types of object that are bound to for Item Template of TreeView.

And that is why these templates are designed to completely seperate your UI logic and code behind, using Selector etc or any such coding, you will introduce UI dependency more on your code behind, which WPF is not intended for.

Here is the link, TreeView DataBinding

And see how you can define item templates in resources,

<TreeView.Resources>
    <HierarchicalDataTemplate 
      DataType="{x:Type local:RegionViewModel}" 
      ItemsSource="{Binding Children}"
      >
      <StackPanel Orientation="Horizontal">
        <Image Width="16" Height="16" 
           Margin="3,0" Source="Images\Region.png" />
        <TextBlock Text="{Binding RegionName}" />
      </StackPanel>
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate 
      DataType="{x:Type local:StateViewModel}" 
      ItemsSource="{Binding Children}"
      >
      <StackPanel Orientation="Horizontal">
        <Image Width="16" Height="16" 
          Margin="3,0" Source="Images\State.png" />
        <TextBlock Text="{Binding StateName}" />
      </StackPanel>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type local:CityViewModel}">
      <StackPanel Orientation="Horizontal">
        <Image Width="16" Height="16" 
           Margin="3,0" Source="Images\City.png" />
        <TextBlock Text="{Binding CityName}" />
      </StackPanel>
    </DataTemplate>
  </TreeView.Resources>
Akash Kava
This doesn't really answer the question - he wants to _switch_ between styles.
codekaizen
Well let the requester decide whether this answer is acceptable to him or not, when HierarchicalDataTemplate is designed to do what he is exactly looking for, its a better and perfect alternative instead of using Selector, Selectors are complicated to write as you have to write more in codebehind to make it work which violates kind of complete design/code seperation. Anyway this is an alternative, it is not a wrong answer.
Akash Kava
Yeah, I have a DataTemplate or HierarchicalDataTemplate for multiple types of objects, I have like 5 different objects in the tree, 1 main one which nests 2, those 2 nest other 3, so the tree is sort of complicated. I still don't see how your answer solves my problem with the IsNodeExpanded property. Maybe I'm missing something?
Carlo
HierarchicalDataTemplate has property called "ItemContainerStyle" which will only work for you specific type, so the TreeViewItem style can be specified on selective HierarchicalDataTemplate for selective type only.
Akash Kava
Ohh I see it now. I haven't been able to test any of these since I've been busy on other parts of the project. I'll get back to you guys as soon I get the result I'm looking for. Thanks a lot for your time and help!
Carlo
+1  A: 

Try using the TreeView.ItemContainerStyleSelector property with a custom StyleSelector class which changes the style depending if the bound object has that property or not.

public class TreeItemStyleSelector : StyleSelector
{
    public Style HasExpandedItemStyle { get; set; }
    public Style NoExpandedItemStyle { get; set; }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        // Choose your test
        bool hasExpandedProperty = item.GetType().GetProperty("IsExpanded") != null;

        return hasExpandedProperty
                   ? HasExpandedItemStyle
                   : NoExpandedItemStyle;
    }
}

In the XAML Resources:

<Style x:Key="IsExpandedStyle" TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
</Style>

<Style x:Key="NoExpandedStyle" TargetType="{x:Type TreeViewItem}">
</Style>

<x:TreeViewItemStyleSelector x:Key="TreeViewItemStyleSelector"
                             HasExpandedItemStyle="{StaticResource IsExpandedStyle}"
                             NoExpandedItemStyle="{StaticResource NoExpandedStyle}" />

In the XAML:

<TreeView ItemsSource="{Binding ...}"
          ItemContainerStyleSelector="{StaticResource TreeItemStyleSelector}">
codekaizen
I actually like this idea... I'll give it a shot tomorrow. Thanks!
Carlo
+1 - this is the perfect use case for style selectors.
JerKimball