在 ViewModel 中使用 CollectionViewSource 的正确方法
Posted
技术标签:
【中文标题】在 ViewModel 中使用 CollectionViewSource 的正确方法【英文标题】:Proper way to use CollectionViewSource in ViewModel 【发布时间】:2014-01-20 05:39:25 【问题描述】:我使用拖放将数据源对象(数据库模型)绑定到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;
视图模型
public MainWindow()
InitializeComponent();
this.DataContext = new MyViewModel();
但是,当我尝试在 ViewModel 中使用相同的代码时,它不起作用(FindResource
不可用),此外,我认为这不是正确的方法(即使用 x:Key
在 MVVM 中)。
我非常感谢任何帮助指出我使用 DataGrid
实施 CollectionViewSource
和 DataBinding
的正确方法。
【问题讨论】:
【参考方案1】:仅供参考,另一种方法是使用CollectionViewSource
上的附加属性,然后将函数通过管道传输到ViewModel
(实现接口)。
这是一个非常基本的演示,仅用于过滤,它需要一些工作,例如VM 上的第二个集合,但我认为它足以展示一般技术。
如果这比其他方法更好或更差有待讨论,我只想指出,还有另一种方法可以做到这一点
附加属性的定义:
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;
【讨论】:
【参考方案2】:您有两个选项可以将CollectionViewSource
与 MVVM 一起正确使用 -
通过您的ViewModel
公开一个ObservableCollection
项目(在您的情况下为Categories
)并像这样在XAML 中创建CollectionViewSource
-
<CollectionViewSource Source="Binding Path=Categories">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="CategoryName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
scm:xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
看到这个 - Filtering
collections from XAML using CollectionViewSource
直接从您的ViewModel
创建和公开ICollectionView
看到这个 - How to Navigate, Group, Sort and Filter Data in 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();
视图模型:
public class CustomerViewModel
private readonly ICollectionView customerView;
public ICollectionView Customers
get return customerView;
public CustomerViewModel()
IList<Customer> customers = GetCustomers();
customerView = CollectionViewSource.GetDefaultView( customers );
更新:
问。如果没有要排序的属性?例如是否存在字符串或整数的ObservableCollection
?
A.在这种情况下,您可以简单地使用 . 作为属性名称:
<scm:SortDescription PropertyName="." />
【讨论】:
仅供参考,scm:
是这个xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
嗨.. 很抱歉这么晚了,但即使有这个解决方案。那么我们如何在 ViewModel 中声明 CollectionViewSource?
@Offer 您好,我已经添加了相关代码,展示了如何在 ViewModel 中声明 CollectionViewSource。
我已经尝试过这个解决方案,这个想法真的很棒,但是我无法让设计时视图正常工作。也许你可以看看:***.com/questions/38093415/…
如果我没有要排序的属性怎么办?例如如果我有 observableCollection 字符串、整数或其他什么?而不是自定义引用类型【参考方案3】:
我发现在我的 ViewModel 中有一个 CollectionViewSource
并将 ListBox
(在我的情况下)绑定到 CollectionViewSource.View
同时将 CollectionViewSource.Source
设置为我想要使用的列表很方便。
像这样:
视图模型:
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"/>
这意味着我可以根据需要在 VM 中做一些整洁的事情(来自 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
对象时也是可能的,但博客链接中的演示代码似乎是一些代码隐藏的东西,直接引用列表框,我试图避免。
顺便说一句,在你问之前,这里是你如何使用设计时虚拟机:WPF Design Time View Model
【讨论】:
以上是关于在 ViewModel 中使用 CollectionViewSource 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章
使用 RelayCommand 从 ViewModel 中清除条目文本