CollectionViewSource, how to filter data?
Asked Answered
D

3

11

I am binding a ComboBox to Entities but I want the data filtered.

Up to now I have tried two ways:

  • "simple" one: Apply the filter directly to the ObjectSet throught LINQ to Entities
  • setting a filtering event handler as described on msdn

I am satisfied by the first approach, above all because the query generated to the database contains a WHERE clause, so not all the whole data have to be retrieved from the remote db....

However, the #2 approach is by far more flexible, if at runtime i'd like to change the filtering applied... I have followed the example on msdn, but I get an exception, why?

So, my questions are:
1. Which approach is better
2. Why I get the exception?

Here is my code:

 private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        //Do not load your data at design time.
        if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
        {
            //Load your data here and assign the result to the CollectionViewSource.
            System.Windows.Data.CollectionViewSource myCollectionViewSource =
                (System.Windows.Data.CollectionViewSource)
                this.Resources["tSCHEDEViewSource"];

            // If I use this I get the data filtered on startup, but is it the right mode?
            //myCollectionViewSource.Source = _context.TSCHEDE.Where(s => s.KLINEA == kLinea && s.FCANC == "T").OrderBy(s => s.DSCHEDA).OrderByDescending(s => s.DSTORICO);

            // Instead If I apply my custom filtering logic
            myCollectionViewSource.Filter += new FilterEventHandler(filterSource);

            myCollectionViewSource.Source = _context.TSCHEDE; // ... Here i get an exception: 
            // 'System.Windows.Data.BindingListCollectionView' view does not support filtering. ???
        }
    }


    private void filterSource(object sender, FilterEventArgs e)
    {
        TSCHEDE scheda = e.Item as TSCHEDE;
        if (scheda != null)
        {
            if (scheda.KLINEA == 990)
            {
                e.Accepted = true;
            }
            else
            {
                e.Accepted = false;
            }
        }
    }

EDIT: I have tried implementing the Filter property on the View rather than setting the EventHandler:

myCollectionView = (BindingListCollectionView)myCollectionViewSource.View;
myCollectionView.Filter = new Predicate<object>(Contains);

public bool Contains(object de)
    {
        TSCHEDE scheda = de as TSCHEDE;
        return (scheda.KLINEA == 990);
    }

And now I get the not so useful exception:

System.NotSupportedException: Specified method is not supported. at System.Windows.Data.CollectionView.set_Filter(Predicate`1 value)

EDIT

XAML code:

<UserControl.Resources>
    <CollectionViewSource x:Key="tSCHEDEViewSource" d:DesignSource="{d:DesignInstance my:TSCHEDE, CreateList=True}"  >
    </CollectionViewSource>
    <DataTemplate x:Key="SchedaTemplate">
        <StackPanel Orientation="Horizontal" >
            <TextBlock Text="{Binding Path=KSCHEDA}" Width="60"></TextBlock>
            <TextBlock Text="{Binding Path=DArticolo}" Width="200"></TextBlock>
            <TextBlock Text=" - " Width="40"></TextBlock>
            <TextBlock Text="{Binding Path=DSTORICO}" Width="150"></TextBlock>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>
<Grid Background="PapayaWhip" DataContext="{StaticResource tSCHEDEViewSource}" DataContextChanged="StartHere" Name="rootGrid">
    <ComboBox ItemTemplate="{StaticResource SchedaTemplate}" Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="23,129,0,0" Name="tSCHEDEComboBox1" SelectedValuePath="KSCHEDA" VerticalAlignment="Top" Width="393">
        <ComboBox.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel />
            </ItemsPanelTemplate>
        </ComboBox.ItemsPanel>
    </ComboBox>
</Grid>

Now I am thinking the problem is in XAML Binding, not in code behind...

Deangelo answered 24/1, 2013 at 9:8 Comment(0)
D
7

Al last I have found a solution, as posted also in this question to explicitly declare the type of the Collection:

CollectionViewType="ListCollectionView"

So in XAML added the Collection type:

<CollectionViewSource x:Key="tSCHEDEViewSource" d:DesignSource="{d:DesignInstance my:TSCHEDE,  CreateList=True}" CollectionViewType="ListCollectionView">
    </CollectionViewSource>

And in code now the Event Handler works:

myCollectionViewSource.Filter += new FilterEventHandler(filterSource);

The only regret is that I did not understand why, for something apparently so simple, I have to force it "by hand" in XAML ??? To me this seems like an hack, and also very error prone...

Deangelo answered 24/1, 2013 at 11:29 Comment(5)
Don't you just hate when somebody down votes without leaving a comment?Homologize
Maybe they received this error - CollectionViewType property can only be set during Initialization.Bosun
@Warlord099 It's weird. We never complain about upvotes...just a thought. But yeah, we're almost sure it was done because the comment/post helped the person.Superfluity
@Superfluity It really isn't all that weird. If an answer is already correct/helpful then there might not be anything else that would provide benefit by being added. However if an answer is NOT correct/helpful then there is always a benefit in explaining why. Either way, this is now the accepted answer despite having multiple downvotes over two years ago when I made my initial comment.Homologize
If you get that error "CollectionViewType property can only be set during Initialization.", just set the CollectionViewType as the first attribute in XAML, before any other attribute. I know this answer is old, but maybe this can be helpul to someone.Aspirator
V
11

Check this

1) CollectionView Filtering

Filtering requires a delegate (Predicate) based on which the filter will occur. The Predicate takes in the item an based on the value true or false it returns, it selects or unselect an element.

this.Source.Filter = item => {
    ViewItem vitem = item as ViewItem;
    return vItem != null && vitem.Name.Contains("A");
};

2) FIltering the data Dynamically

Vierno answered 24/1, 2013 at 9:38 Comment(4)
I have followed you first link, but after a bit the things get a little too complicated... Above all, what i get from myCollectionViewSource.View is a ICollectionView, and it doesn't seem to support Sorting of Filtering (the properties CanSort,CanFilter are false) In the XAML I have: <CollectionViewSource x:Key="tSCHEDEViewSource" d:DesignSource="{d:DesignInstance my:TSCHEDE, CreateList=True}" />Deangelo
programmers born to do complicated things :-)Vierno
This was, in fact, the answer.Gambell
As addition to the first link, which is really helpful, WPF always bind to a view rather than the collection, which is the default view. And, since the default view is shared by all bindings, the filtering or sorting can be done on the view and is automatically displayed. As explained here: CollectionViewSource.GetDefaultViewFledgy
D
7

Al last I have found a solution, as posted also in this question to explicitly declare the type of the Collection:

CollectionViewType="ListCollectionView"

So in XAML added the Collection type:

<CollectionViewSource x:Key="tSCHEDEViewSource" d:DesignSource="{d:DesignInstance my:TSCHEDE,  CreateList=True}" CollectionViewType="ListCollectionView">
    </CollectionViewSource>

And in code now the Event Handler works:

myCollectionViewSource.Filter += new FilterEventHandler(filterSource);

The only regret is that I did not understand why, for something apparently so simple, I have to force it "by hand" in XAML ??? To me this seems like an hack, and also very error prone...

Deangelo answered 24/1, 2013 at 11:29 Comment(5)
Don't you just hate when somebody down votes without leaving a comment?Homologize
Maybe they received this error - CollectionViewType property can only be set during Initialization.Bosun
@Warlord099 It's weird. We never complain about upvotes...just a thought. But yeah, we're almost sure it was done because the comment/post helped the person.Superfluity
@Superfluity It really isn't all that weird. If an answer is already correct/helpful then there might not be anything else that would provide benefit by being added. However if an answer is NOT correct/helpful then there is always a benefit in explaining why. Either way, this is now the accepted answer despite having multiple downvotes over two years ago when I made my initial comment.Homologize
If you get that error "CollectionViewType property can only be set during Initialization.", just set the CollectionViewType as the first attribute in XAML, before any other attribute. I know this answer is old, but maybe this can be helpul to someone.Aspirator
H
0
<TextBox x:Name="FilterTextBox">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="TextChanged">
            <b:CallMethodAction
                MethodName="Refresh"
                TargetObject="{Binding View, BindsDirectlyToSource=True, Source={StaticResource tSCHEDEViewSource}}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
</TextBox>
Hoxha answered 14/4, 2020 at 18:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.