Как отфильтровать CollectionContainer - PullRequest
0 голосов
/ 04 октября 2018

У меня проблема с выполнением фильтрации ICollectionView в сочетании с использованием CompositeCollection .

На рисунке показано, чего я хочу достичь: Окнос текстовым полем фильтра, коллекцией предметов и кнопкой «Добавить»

Требования:

  • Кнопка «Добавить» должна быть частью 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 , определенным в Resources , а не представлением 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 голосов
/ 05 октября 2018

Я нашел элегантное решение моей проблемы.Благодаря статье в блоге Томаса Левеска .NET , на которую ссылается этот ответ: CollectionContainer не связывает мою коллекцию Я могу просто выполнить связывание, а ICollectionView получен CollectionViewSource.GetDefaultView(MyCollection);правильный выбор для обновления.

BindingProxy.cs (кредит достается Томасу Левеску)

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