Reputation: 41
I have a problem to perform filtering of ICollectionView in combination with usage of CompositeCollection.
The illustration describes what I want to achieve: Window with filter text box, items collection and "Add" button
Requirements:
DebugWindow.xaml:
<StackPanel>
<TextBox Text="{Binding TextBoxText, UpdateSourceTrigger=PropertyChanged}" Margin="5" BorderBrush="Black"/>
<ItemsControl BorderBrush="Gray">
<!-- Resources -->
<ItemsControl.Resources>
<CollectionViewSource x:Key="ColVSKey"
Source="{Binding MyCollection}"/>
</ItemsControl.Resources>
<!-- Items Source -->
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource ColVSKey}}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
<!-- Item Template -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Background="Gray"
Margin="10"
Padding="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- Items Panel -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
DebugWindow.xaml.cs:
public partial class DebugWindow : Window, INotifyPropertyChanged
{
private string _textBoxText = "";
public ObservableCollection<string> MyCollection { get; set; }
public Predicate<object> FilterFunction { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private ICollectionView view;
public string TextBoxText
{
get
{
return _textBoxText;
}
set
{
if (value == _textBoxText)
return;
_textBoxText = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TextBoxText)));
if (view != null)
view.Refresh();
}
}
public DebugWindow()
{
InitializeComponent();
MyCollection = new ObservableCollection<string>() { "one", "two", "Three", "four", "five", "six", "seven", "Eight" };
FilterFunction = new Predicate<object>((o) => Filter(o));
view = CollectionViewSource.GetDefaultView(MyCollection);
if (view != null)
view.Filter = new Predicate<object>((o) => Filter(o));
this.DataContext = this;
}
public bool Filter(object v)
{
string s = (string)v;
bool ret = false;
if (s.IndexOf(TextBoxText) != -1)
ret = true;
return ret;
}
}
The problem is, that the view = CollectionViewSource.GetDefaultView(MyCollection);
is the View associated with the CollectionViewSource defined within Resources and not the View of the CollectionContainer. So the wrong View is refreshed and the displayed View is not refreshed at all.
I was able to achieve desired behaviour with extending the CollectionContainer, hooking up to CollectionChanged event:
public class MyCollectionContainer : CollectionContainer
{
private ICollectionView _view;
public ICollectionView View
{
get
{
return _view;
}
}
public MyCollectionContainer()
{
this.CollectionChanged += MyCollectionContainer_CollectionChanged;
}
private void MyCollectionContainer_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_view == null && Collection != null && MyFilter != null)
{
_view = CollectionViewSource.GetDefaultView(Collection);
_view.Filter += MyFilter;
}
}
}
and exposing it to the code-behind:
...in XAML...
<CompositeCollection>
<local:MyCollectionContainer x:Name="MyCollectionContainer" Collection="{Binding Source={StaticResource ColVSKey}}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
...in constructor...
MyCollectionContainer.MyFilter = new Predicate<object>((o) => Filter(o));
...in TextBoxText property set...
if(MyCollectionContainer.View!=null)
MyCollectionContainer.View.Refresh();
Questions: Is there a way how to achieve the behaviour I require without exposing the control to code-behind? Is it possible to bind a MVVM to View of the CollectionContainer?
Thanks in advance and sorry for long post.
Upvotes: 1
Views: 443
Reputation: 41
I have found an elegant solution to my problem. Thanks to article on Thomas Levesque's .NET blog which was referenced in this answer: CollectionContainer doesn't bind my Collection I can simply perform binding and the ICollectionView
obtained by
CollectionViewSource.GetDefaultView(MyCollection);
is the right one to be refreshed.
BindingProxy.cs (credit goes to Thomas Levesque)
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object MyData
{
get { return (object)GetValue(MyDataProperty); }
set { SetValue(MyDataProperty, value); }
}
public static readonly DependencyProperty MyDataProperty =
DependencyProperty.Register("MyData", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
The updated XAML:
<ItemsControl>
<ItemsControl.Resources>
<local:BindingProxy x:Key="proxy" MyData="{Binding Path=MyCollection}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource proxy}, Path=MyData}"/>
<Button Content="Add another one" Margin="5"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
...
</ItemsControl>
Upvotes: 1