CollectionViewSource.Фильтр не вызывается с несколькими резюме для одной и той же коллекции в Datagrid

#wpf #collectionviewsource

#wpf #collectionviewsource

Вопрос:

Я разделяю одну и ту же коллекцию элементов между несколькими моделями представления в DataGrid. Когда выбран один элемент в этом общем списке, мне нужно, чтобы он исчез из списка других элементов.

Здесь используются разные модели представления:

ParentViewModel.cs

 public class ParentViewModel
{
    private readonly ObservableCollection<SharedItemViewModel> _sharedItems;

    public ParentViewModel()
    {
        // Shared items
        _sharedItems = new ObservableCollection<SharedItemViewModel>
        {
            new SharedItemViewModel { Visible = true },
            new SharedItemViewModel { Visible = true }
        };
        
        this.Children = new ObservableCollection<ChildViewModel>
        {
            // Each child references the same list
            new ChildViewModel(_sharedItems),
            new ChildViewModel(_sharedItems)
        }
    }
    
    public ObservableCollection<ChildViewModel> Children { get; }
}
 

SharedItemViewModel.cs

 public class SharedItemViewModel
{
    private bool _visible;
    
    public bool Visible
    {
        get => _visible;
        set => this.UpdateProperty(ref _visible, value); // Raises the PropertyChanged event properly
    }
}
 

ChildViewModel.cs

 public class ChildViewModel
{
    private SharedItemViewModel _selectedItem;

    public ChildViewModel(ObservableCollection<SharedItemViewModel> sharedItems)
    {
        this.Items = sharedItems;
    }
    
    public ObservableCollection<SharedItemViewModel> Items { get; }
    
    public SharedItemViewModel SelectedItem
    {
        get => _selectedItem;
        set 
        {
            if(_selectedItem != value)
            {
                // Set the old item visible again
                if (_selectedItem != null)
                {
                    _selectedItem.Visible = true;
                }
                
                _selectedItem = value;
                
                // Set the new item not visible
                if (value != null)
                {
                    _selectedItem.Visible = false;
                }
                
                this.NotifyPropertyChanged();
            }
        }
    }
}
 

Для этого я пытаюсь использовать разные CollectionViewSource для каждой строки:

MyView.xaml

 <DataGrid 
    ItemsSource="{Binding Children}"
    AutoGenerateColumns="False">
    
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Item">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.Resources>
                            <CollectionViewSource 
                                                    x:Key="LocalItems"
                                                    Source="{Binding Items, Mode=OneWay}" 
                                                    Filter="CollectionViewSource_Filter"
                                                    IsLiveFilteringRequested="True">
                                <CollectionViewSource.LiveFilteringProperties>
                                    <sys:String>Visible</sys:String>
                                </CollectionViewSource.LiveFilteringProperties>
                            </CollectionViewSource>
                        </Grid.Resources>
                        
                        <ComboBox 
                                    ItemsSource="{Binding Source={StaticResource LocalItems}}"
                                    SelectedItem="{Binding SelectedItem}"/>

                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>
 

MyView.xaml.cs

 private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
    e.Accepted = [..]; // Some work here to do to decide whether the item should be visible
}
 

Когда я запускаю приложение, общие элементы хорошо привязаны к спискам со списком. Когда я выбираю один элемент в одном выпадающем списке, Visible свойство правильно задано, и PropertyChanged событие хорошо поднято.

Моя проблема в том, что я никогда (ни при загрузке, ни при изменении Visible свойства) не нажимал на Filter обработчик в моем коде (проверено с точкой останова).

Вы знаете, что не так с этим кодом?

Ответ №1:

Я не уверен, почему событие не вызывается, но вы можете либо напрямую отфильтровать Items исходную коллекцию, либо попытаться установить Filter свойство представления при Grid загрузке в CellTemplate :

 private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    Grid grid = (Grid)sender;
    CollectionViewSource cvs = grid.Resources["LocalItems"] as CollectionViewSource;
    if (cvs != null)
        cvs.View.Filter = (s) => true; //your filtering logic here...
}
 

XAML:

 <Grid Loaded="Grid_Loaded">
    <Grid.Resources>
        <CollectionViewSource x:Key="LocalItems">
        ...
 

Комментарии:

1. Событие filter вызывается этим решением, это прогресс ! Но я не могу выполнить свою логику фильтрации только с SharedItemViewModel помощью . Мне также нужно получить доступ к ChildViewModel (это не так просто, как моя Visible собственность в моем примере: s).

Ответ №2:

Я нашел обходной путь, но я не буду отмечать его как ответ, поскольку он немного «ломает» шаблон MVVM, добавляя зависимость WindowsBase в мою ViewModel.

Решение состоит в создании CollectionViewSource в ChildViewModel :

 public class ChildViewModel
{
    private SharedItemViewModel _selectedItem;

    public ChildViewModel(ObservableCollection<SharedItemViewModel> sharedItems)
    {
        var cvs = new CollectionViewSource();
        cvs.Source = sharedItems;
        cvs.IsLiveFilteringRequested = true;
        cvs.LiveFilteringProperties.Add(nameof(SharedItemViewModel.Visible));
        cvs.View.Filter = this.Filter;

        this.Items = cvs.View;
    }
    
    public ICollectionView Items { get; }
    
    public SharedItemViewModel SelectedItem {..}
    
    private bool Filter(object obj)
    {
        var vm = obj as SharedItemViewModel;
        
        return vm.Visible;
    }
}