Подтвердить что ты не робот

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

Я использовал Drag and Drop, чтобы связать объект Data Source (модель DB) с DataGrid (в основном следуя этому примеру в Entity Framework Databinding with 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.

4b9b3361

Ответ 1

У вас есть два варианта правильного использования 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"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    x:Class="CustomerView">
    <ListBox ItemsSource={Binding Customers} />
</Window>

Просмотреть код позади:

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

ViewModel:

public class CustomerViewModel
{
    private readonly ICollectionView customerView;

    public ICollectionView Customers
    {
        get { return customerView; }
    }

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

Обновление:

В. Если нет свойства для сортировки? например если есть строка ObservableCollection или int?

О. В этом случае вы можете просто использовать . в качестве имени свойства:

<scm:SortDescription PropertyName="." />

Ответ 2

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

Вот так:

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, но демонстрационный код в ссылке на блог, похоже, представляет собой некоторый ICollectionView кода, напрямую ссылающийся на список, которого я стараюсь избегать.

Кстати, прежде чем спросить, вот как вы используете виртуальную машину Design Time: модель представления времени WPF

Ответ 3

Просто для справки, другим способом является использование присоединенного свойства в 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;
    }
}