tags:

views:

2133

answers:

2

The problem: We want to bind a HierarchicalDataTemplate’s ItemsSource property to a CollectionViewSource, to perform grouping and filtering.

The background: The original issue we were working on had to do with filtering a TreeView. Specifically, we found that using a CollectionViewSource to filter worked but caused the TreeView nodes to collapse. It's likely because the View’s Refresh function regenerates its list of objects which causes the TreeView to regenerate its nodes, causing the original nodes’ expansion states to be lost. We resolved this by writing a class that is similar to CollectionViewSource but preserves the View by editing the object list in place so that when it changes due to filtering, the associated TreeView nodes aren’t destroyed.

This has worked perfectly for us and we’d like to use it at deeper levels in our TreeView, bringing us back to our problem. Currently, we have a HierarchicalDataTemplate that looks like this:

<HierarchicalDataTemplate

    x:Key="tableTemplate"

    ItemsSource="{Binding Path=DataItems}"

    ItemTemplateSelector="{StaticResource tableGroupsTemplateSelector}"

    >

Instead, we want it to behave like this:

<HierarchicalDataTemplate

    x:Key="tableTemplate"

    ItemTemplateSelector="{StaticResource tableGroupsTemplateSelector}"

    >

    <HierarchicalDataTemplate.ItemsSource>

        <Binding>

            <Binding.Source>

                <CollectionViewSource

                    Source="{Binding Path=DataItems}"

                    />

            </Binding.Source>

        </Binding>

    </HierarchicalDataTemplate.ItemsSource>

Unfortunately, this approach doesn’t seem to work. From what we can tell, the binding within the CVS never fires; no binding errors are raised; we tried attaching a converter and setting a breakpoint but the breakpoint was never hit. We’ve also tried various other solutions, including: using RelativeSource, moving the CollectionViewSource into the template’s Resources, and incorporating TreeViewItem’s into the template. However, nothing has worked.

As an aside, I do realize that a ViewModel approach would enable filtering. However, I'm at a place in our development cycle where I can't make that type of change so I'm looking for alternatives, like the CollectionViewSource approach.

Any help you can give would be appreciated.

Thanks,

-Craig

+2  A: 

In my applications I have found that the simplest way to achieve tree view filtering is by binding the visibility to a property on the listed objects.

This approach may not work for you but if you would like to try it here is an example of what I have done.

In the Tree View Resources you add a Style Trigger. you can bind the trigger to any property on the objects that your displaying, and you can add in a value converter if you want to add some logic to inspect the item and decide if it should be displayed or not.

<TreeView.Resources>
   <Style TargetType="TreeViewItem">
         <Style.Triggers>
               <DataTrigger Binding="{Binding Path=Display}" Value="False">
                    <Setter Property="Visibility"  Value="Collapsed"/>
               </DataTrigger>
         </Style.Triggers>
   </Style>
</TreeView.Resources>

The Trigger will Collapse the treeView item if the "Display" property is false.

To avoid adding a "Display" property to your objects, you can do the following:

 public class PositionVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {

            int positions = (int) value;
            if (positions > 0)
                return true;
            return false;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new System.NotSupportedException();
        }
    }

And in the TreeView Style binding add the value converter:

<TreeView.Resources>
   <Style TargetType="TreeViewItem">
         <Style.Triggers>
               <DataTrigger Value="False">
                    <Binding Path="Positions">
                        <Binding.Converter>
                            <local:PositionVisibilityConverter/>
                        </Binding.Converter>
                    </Binding>
                    <Setter Property="Visibility"  Value="Collapsed"/>
               </DataTrigger>
         </Style.Triggers>
   </Style>
</TreeView.Resources>

You dont need to specify the path if you want to inspect the whole object.

This method seemed to work best for me, give it a try.

Kelly
A: 

Hi Kellls,

This looks like a good solution for most cases. One thing does come up here, though. Do you have a trick for easily removing the Expander glyph for node that have all their children collapsed?

Thanks, -Scott

Hi Scott,Good point. In my case I collapse the parent if all the children are filtered, so this is not an issue for me.If that is not the behavior your looking for, I believe you could customize the control template and use a similar data trigger technique to hide the Expander glyph.
Kelly