如何过滤CollectionContainer

时间:2018-10-04 14:48:04

标签: c# collectionviewsource compositecollection

在结合使用 CompositeCollection 的情况下执行 ICollectionView 的过滤时遇到问题。

该图描述了我要实现的目标: Window with filter text box, items collection and "Add" button

要求:

  • “添加”按钮必须是 WrapPanel
  • 的一部分
  • 应通过 ICollectionView.View.Filter
  • 执行过滤

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;
    }
}

问题在于,view = CollectionViewSource.GetDefaultView(MyCollection);是与资源中定义的 CollectionViewSource 关联的视图,而不是 CollectionContainer的视图。因此刷新了错误的视图,并且根本没有刷新显示的视图。

我可以通过扩展 CollectionContainer 并连接到CollectionChanged事件来实现所需的行为:

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;
        }
    }
}

并将其公开给后面的代码:

...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();

问题: 有没有一种方法可以实现我所需的行为而又不将控件暴露在代码背后? 可以将MVVM绑定到 CollectionContainer 的视图吗?

在此先感谢您,并感谢您发表冗长的帖子。

1 个答案:

答案 0 :(得分:0)

我找到了解决问题的绝妙方法。感谢在此答案中引用的关于Thomas Levesque's .NET blog的文章:CollectionContainer doesn't bind my Collection我可以简单地执行绑定,并且{ ICollectionView是正确的选择。

BindingProxy.cs(版权归Thomas Levesque所有)

CollectionViewSource.GetDefaultView(MyCollection);

更新的XAML:

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));
}