Правильный способ использования 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
.
Ответы
Ответ 1
У вас есть два варианта правильного использования CollectionViewSource
с MVVM -
Откройте 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=WindowsBase"
посмотрите это - Filtering
коллекции из XAML с использованием CollectionViewSource
Создайте и выставьте 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;
}
}