Правильный способ использования CollectionViewSource в ViewModel

Я использовал перетаскивание для привязки объекта источника данных (модель БД) к DataGrid (в основном следуя этому примеру в привязка данных Entity Framework с помощью WPF.

все работает нормально с этой реализацией.

XAML

<Window.Resources>    
<CollectionViewSource x:Key="categoryViewSource"  
    d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource categoryViewSource}">
..

Код

private void Window_Loaded(object sender, RoutedEventArgs e)
{
   System.Windows.Data.CollectionViewSource categoryViewSource =
      ((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource")));

  _context.Categories.Load();
  categoryViewSource.Source = _context.Categories.Local;        
}

ViewModel

public MainWindow()
{
    InitializeComponent();
    this.DataContext = new MyViewModel();
}

однако, когда я пытаюсь использовать тот же код из ViewModel, он не работает (FindResource недоступна), кроме того, я не думаю, что это правильный подход (т. е. в использовании x:Key в MVVM).

Я был бы очень признателен за любую помощь, чтобы указать мне, что является правильным способом реализации CollectionViewSource и DataBinding С DataGrid.

3 ответов


у вас есть два варианта использовать CollectionViewSource правильно с MVVM -

  1. подвергнуть ObserVableCollection шт. (Categories в вашем случае) через ViewModel и создать CollectionViewSource в XAML, как это -

    <CollectionViewSource Source="{Binding Path=Categories}">
        <CollectionViewSource.SortDescriptions>
           <scm:SortDescription PropertyName="CategoryName" />
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
    

    scm:xmlns:scm="clr-namespace:System.ComponentModel;assembly=Wind‌​owsBase"

    посмотреть это - Filtering коллекции из XAML с помощью CollectionViewSource

  2. создать и предоставить ICollectionView непосредственно с ViewModel

    посмотреть этот - как перемещаться, группировать, сортировать и фильтровать данные в WPF

в следующем примере показано, как создать представление коллекции и свяжите его с ListBox

XAML:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ListBox ItemsSource={Binding Customers} />
</Window>

Просмотр Codebehind:

public class CustomerView
{
   public CustomerView()
   {
        DataContext = new CustomerViewModel();
   }
}

ViewModel:

public class CustomerViewModel
{
    private ICollectionView _customerView;

    public ICollectionView Customers
    {
        get { return _customerView; }
    }

    public CustomerViewModel()
    {
        IList<Customer> customers = GetCustomers();
        _customerView = CollectionViewSource.GetDefaultView(customers);
    }
}

я обнаружил, что удобно иметь CollectionViewSource в моей ViewModel и привязать ListBox (в моем случае) к CollectionViewSource.Просмотр при настройке CollectionViewSource.Источник-список, который я хочу использовать.

вот так:

ViewModel:

    public DesignTimeVM()  //I'm using this as a Design Time VM 
    {
        Items = new List<Foo>();
        Items.Add(new Foo() { FooProp= "1", FooPrep= 20.0 });
        Items.Add(new Foo() { FooProp= "2", FooPrep= 30.0 });

        FooViewSource = new CollectionViewSource();
        FooViewSource.Source = Items;

        SelectedFoo = Items.First();

        //More code as needed
    }

XAML:

<ListBox ItemsSource="{Binding FooViewSource.View}" SelectedItem="{Binding SelectedFoo}"/>

Это означает, что я могу делать аккуратные вещи в виртуальной машине по мере необходимости (от https://blogs.msdn.microsoft.com/matt/2008/08/28/collectionview-deferrefresh-my-new-best-friend/ ):

        using (FooViewSource.DeferRefresh())
        {
            //Remove an old Item
            //add New Item
            //sort list anew, etc. 
        }

Я полагаю, что это возможно при использовании ICollectionView object также, но демо-код в ссылке блога, кажется, некоторые вещи codebehind, ссылаясь на listbox напрямую, которого я пытаюсь избежать.

кстати, прежде чем вы спросите, вот как вы используете время разработки VM:модель представления времени разработки WPF


просто для справки, другой способ-использовать прикрепленное свойство на CollectionViewSource, который затем передает функции ViewModel (реализация интерфейса).

Это очень простая демонстрация только для фильтрации, для нее потребуется некоторая работа, например, вторая коллекция на виртуальной машине, но я думаю, что этого достаточно, чтобы показать общую технику.

если это лучше или хуже, чем другие методы для обсуждения, я просто хотел указать, что есть другой способ сделать это

определение арестованного имущества:

public static class CollectionViewSourceFilter
{
    public static IFilterCollectionViewSource GetFilterObject(CollectionViewSource obj)
    {
        return (IFilterCollectionViewSource)obj.GetValue(FilterObjectProperty);
    }

    public static void SetFilterObject(CollectionViewSource obj, IFilterCollectionViewSource value)
    {
        obj.SetValue(FilterObjectProperty, value);
    }

    public static void FilterObjectChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue is IFilterCollectionViewSource oldFilterObject
            && sender is CollectionViewSource oldCvs)
        {
            oldCvs.Filter -= oldFilterObject.Filter;
            oldFilterObject.FilterRefresh -= (s, e2) => oldCvs.View.Refresh();
        }

        if (e.NewValue is IFilterCollectionViewSource filterObject
            && sender is CollectionViewSource cvs)
        {
            cvs.Filter += filterObject.Filter;
            filterObject.FilterRefresh += (s,e2) => cvs.View.Refresh();
        }
    }

    public static readonly DependencyProperty FilterObjectProperty = DependencyProperty.RegisterAttached(
        "FilterObject",
        typeof(Interfaces.IFilterCollectionViewSource),
        typeof(CollectionViewSourceFilter),
        new PropertyMetadata(null,FilterObjectChanged)
    );
}

интерфейс:

public interface IFilterCollectionViewSource
{
    void Filter(object sender, FilterEventArgs e);
    event EventHandler FilterRefresh;
}

использование в xaml:

<CollectionViewSource
        x:Key="yourKey"
        Source="{Binding YourCollection}"
        classes:CollectionViewSourceFilter.FilterObject="{Binding}" />

и использование в ViewModel:

class YourViewModel : IFilterCollectionViewSource
{
    public event EventHandler FilterRefresh;

    private string _SearchTerm = string.Empty;
    public string SearchTerm
    {
        get { return _SearchTerm; }
        set {
            SetProperty(ref _SearchTerm, value);
            FilterRefresh?.Invoke(this, null);
        }
    }

    private ObservableCollection<YourItemType> _YourCollection = new ObservableCollection<YourItemType>();
    public ObservableCollection<YourItemType> YourCollection
    {
        get { return _YourCollection; }
        set { SetProperty(ref _YourCollection, value); }
    }

    public void Filter(object sender, FilterEventArgs e)
    {
        e.Accepted = (e.Item as YourItemType)?.YourProperty?.ToLower().Contains(SearchTerm.ToLower()) ?? true;
    }
}