首页 > 解决方案 > 如何过滤 CollectionContainer

问题描述

我在结合使用CompositeCollection执行ICollectionView过滤时遇到问题。

插图描述了我想要实现的目标: 带有过滤器文本框、项目集合和“添加”按钮的窗口

要求:

调试窗口.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;
    }
}

问题是,这是与Resources中定义的CollectionViewSourceview = CollectionViewSource.GetDefaultView(MyCollection);相关联的View,而不是CollectionContainer的 View 。所以刷新了错误的View,显示的View根本不刷新。

我能够通过扩展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的视图?

在此先感谢,很抱歉发了很长的帖子。

标签: c#collectionviewsourcecompositecollection

解决方案


我找到了一个优雅的解决方案来解决我的问题。感谢Thomas Levesque 的 .NET 博客上的文章,该文章在此答案中被引用:CollectionContainer doesn't bind my Collection我可以简单地执行绑定,并且ICollectionView获得的 CollectionViewSource.GetDefaultView(MyCollection);是正确的要刷新的。

BindingProxy.cs(感谢 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));
}

更新的 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>

推荐阅读