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...

spiderman
  • 1,565
  • 2
  • 16
  • 37

3 Answers3

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

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Master Stroke
  • 5,108
  • 2
  • 26
  • 57
  • 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: – spiderman Jan 24 '13 at 09:52
  • 3
    programmers born to do complicated things :-) – Master Stroke Jan 24 '13 at 09:53
  • 1
    This was, in fact, the answer. – Shaun Wilson Feb 23 '14 at 14:29
  • 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.GetDefaultView](https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewsource.getdefaultview?view=netframework-4.8#remarks) – royalTS Apr 08 '22 at 08:02
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...

Grant Winney
  • 65,241
  • 13
  • 115
  • 165
spiderman
  • 1,565
  • 2
  • 16
  • 37
  • 14
    Don't you just hate when somebody down votes without leaving a comment? – Warlord 099 Mar 25 '15 at 14:58
  • Maybe they received this error - `CollectionViewType property can only be set during Initialization.` – turkinator Aug 17 '16 at 02:38
  • @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. – Yatin Aug 03 '17 at 04:26
  • 3
    @Yatin 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. – Warlord 099 Aug 07 '17 at 17:04
  • 1
    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. – Unknown Coder May 17 '19 at 09:16
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>
Andrii
  • 1,081
  • 1
  • 11
  • 24