在 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 实施 CollectionViewSourceDataBinding 的正确方法。

【问题讨论】:

【参考方案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=Wind‌​owsBase"

    看到这个 - 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 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在 ViewModel (MVVM) 中使用视图方法

使用 RelayCommand 从 ViewModel 中清除条目文本

ViewModel 在导航导航中没有被清除,并且 viewmodel 中的实时数据保持活动状态

如何在编辑操作中使用 ViewModel?

我应该如何在两个片段中使用 ViewModel?

使用 viewmodel 在视图中显示图像