【问题标题】:Proper way to use CollectionViewSource in ViewModel在 ViewModel 中使用 CollectionViewSource 的正确方法
【发布时间】: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 的正确方法。

【问题讨论】:

    标签: c# wpf mvvm datagrid collectionviewsource


    【解决方案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 一起正确使用 -

      1. 通过您的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

      2. 直接从您的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。
      • 我已经尝试过这个解决方案,这个想法真的很棒,但是我无法让设计时视图正常工作。也许你可以看看:stackoverflow.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

      【讨论】:

        猜你喜欢
        • 2015-03-15
        • 2023-03-17
        • 2021-03-07
        • 2023-04-11
        • 2014-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-22
        相关资源
        最近更新 更多