在ViewModel中正确使用CollectionViewSource的方法

61

我使用拖放功能将数据源对象(一个数据库模型)绑定到 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不可用)。此外,我认为这并不是正确的方法(即在MVVM中使用x:Key)。

我非常感谢任何帮助,指导我如何正确地实现CollectionViewSourceDataBindingDataGrid

3个回答

90

使用MVVM正确使用CollectionViewSource有两种选择 -

  1. 通过你的ViewModel公开一个项目(Categories在这种情况下)的ObservableCollection,并在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"

    参见此处 - 如何使用CollectionViewSource从XAML过滤集合

  2. 直接从ViewModel创建和公开一个ICollectionView

    参见此处 - 在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>

查看 Codebehind:

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

答:在这种情况下,您可以简单地使用“.”作为属性名称:

<scm:SortDescription PropertyName="." />

12
FYI,“scm:”意思是“xmlns: scm =” clr-namespace:System.ComponentModel;assembly=WindowsBase “”。 - user2023861
1
@Offer 你好,我已经添加了相关代码,展示了如何在ViewModel中声明CollectionViewSource。 - akjoshi
@JimBalter 我想你是指xaml代码中的x:Class属性。为了简洁起见,我可能已经将其删除了。 - akjoshi
@JimBalter 抱歉,这只是一个例子,而不是一份全面的教程,它解决了 OP 的问题,那时候这更加可取。但是请随意更新,这就是 SO 的美妙之处。 - akjoshi
1
如果我没有可以排序的属性怎么办?例如,如果我有一个字符串、整数或其他类型的 observableCollection 呢?除了自定义引用类型之外 - John Demetriou
显示剩余5条评论

15
我发现在我的ViewModel中拥有一个CollectionViewSource,并将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}"/>

这意味着我可以根据需要在 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 对象也是可能的,但博客链接中的演示代码似乎是一些后端代码,直接引用列表框,而我正试图避免这种情况。

顺便说一下,在你问之前,这是如何使用设计时 VM 的:WPF 设计时视图模型


4
仅供参考,另一种方法是在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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接