0
votes

I'm having troubles doing filtering on a WPF TreeView.

Here is my TreeView XAML:

<TreeView x:Name="ResourcesTreeView" ItemsSource="{Binding Path=FilteredResources}">
<TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type models:HierarchicalResource}" ItemsSource="{Binding ContainedResources}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding ResourceIdentifier.ResourceType, Converter={StaticResource ResourceTypeToIconConverter}}" 
                Stretch="Uniform" Height="23" VerticalAlignment="Center"/>
            <TextBlock Text="{Binding Path=Name}" Style="{StaticResource ResourceName}" VerticalAlignment="Center"/>
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.Resources>
<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectedItemChanged">
        <i:InvokeCommandAction Command="{Binding OnSelectedResourceChangedCommand}"
        CommandParameter="{Binding ElementName=ResourcesTreeView, Path=SelectedItem}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Here is the HierarchicalResource class:

public class HierarchicalResource
{
    public HierarchicalResource(ResourceIdentifier resourceIdentifier, List<HierarchicalResource> containedResources, string name)
    {
        this.ResourceIdentifier = resourceIdentifier;
        this.ContainedResources = containedResources;
        this.Name = name;
    }

    public ResourceIdentifier ResourceIdentifier { get; }

    public List<HierarchicalResource> ContainedResources { get; set; }

    public string Name { get; }
}

This is how Im initializing the CollectionViewProperty:

this.FilteredResources = CollectionViewSource.GetDefaultView(resourcesTree);

the resourcesTree is a list that contain one root element, that contain several children in its ContainedResources property. It works an looks great. Only problem is that when trying to filter it is not working. When I am trying to use this.FilteredResources.Filter = obj => { ... } it is happening only once on the root node. How can I really make the Filter delegate to be executed on each of the existing tree nodes??

Thanks, Guy

1
Each list in the hierarchy will need to be filteredAndy
Hi Andy, can you give me some mote details? How can I apply the filter o each of the lists?bermanguy
Personally, i would write a framework element converter that took a predicate as a property. Then use that on the itemssource of any template.Andy
sorry but this is too high level for me to understand you solution. I will really appreciate if you can elaborate a little bit more, maybe with a little example :)bermanguy

1 Answers

1
votes

This isn't going to fit into a bunch of comments so I'll try and explain in an answer. (I don't have the time to write a complete sample.)

Start from basics so you can definitely understand what I'm on about. The stuff in a treeview is composed of treeviewitems.
A treeviewitem is a headered items control. That's why the text you see is a "Header" property. In a hierarchical template you have an itemssource binding.
This is the itemssource for the itemscontrol which is INSIDE the parent treeviewitem.
And this is the cause of your issue.
What you're filtering is just that top level if treeviewitems.
You're not filtering the itemscontrols within them.
Or the itemscontrols within each of them... and so on.

There are two routes to go with this.

You can filter in the view.
or
You can filter in the viewmodel.

Let's talk about the ViewModel first.
You bind to a list.
When you do that, there is a collectionview automatically generated.
You could get a reference to that and apply a filter to that. Almost as if they designed this so you can easily bind but put in a bit more work and you can sort + filter.

You should google up on this a bit, but loosely speaking this is like:

var view1 = CollectionViewSource.GetDefaultView(source);

You might have to cast to a collectionview or listcollectionview. These have filtering with predicates. Like this:
https://social.technet.microsoft.com/wiki/contents/articles/26673.wpf-collectionview-tips.aspx#Filtering
There's a sample associated with that article and you could bind to an explicit collectionview based on your list.

Alternatively.
You could do this in the view.
I suggest you take a look at this but go with the viewmodel filtering.

This is more difficult but would mean your filter could go in XAML. The way I'd do this is with a converter, but one which is also a frameworkelement. Like this http://drwpf.com/blog/category/value-converters/

This can also have a property (or properties) on it which the converter can use.
Here's a divisor converter:

public class DivideByConverter : MarkupExtension, IValueConverter
{
    public double Divisor { get; set; }

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double bound = System.Convert.ToDouble(value);
        return bound / Divisor;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

Used:

<ScaleTransform ScaleX="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType=Button}
    , Converter={local:DivideByConverter Divisor=480}
    }" 

Why do you need a property?
You want some way to hand over a predicate so the converter can filter your list for you.
In WPF can you filter a CollectionViewSource without code behind?

You could even have a list of predicates.
The technique of adding a property to a converter is quite useful.