相关文章推荐
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I used Drag and Drop to bind Data Source object (a DB model) to DataGrid (basically following this example in Entity Framework Databinding with WPF .

Everything works fine with this implementation.

<Window.Resources>    
<CollectionViewSource x:Key="categoryViewSource"  
    d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource categoryViewSource}">

Code Behind

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();

However, when I try to use the same code from within ViewModel, it doesn‘t work (FindResource is not available), besides, I don’t think this is the right approach (i.e. to use x:Key in MVVM).

I would really appreciate any help to point me what is the right way to implement CollectionViewSource and DataBinding with DataGrid.

  • Expose an ObservableCollection of items (Categories in your case) through your ViewModel and create CollectionViewSource in XAML like this -

    <CollectionViewSource Source="{Binding Path=Categories}">
        <CollectionViewSource.SortDescriptions>
           <scm:SortDescription PropertyName="CategoryName" />
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
    

    scm: xmlns:scm="clr-namespace:System.ComponentModel;assembly=Wind‌​owsBase"

    see this - Filtering collections from XAML using CollectionViewSource

  • Create and Expose an ICollectionView directly from your ViewModel

    see this - How to Navigate, Group, Sort and Filter Data in WPF

  • Following example shows how to create a collection view and bind it to a ListBox

    View 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>
    

    View Codebehind:

    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 );
    

    Update:

    Q. If there is no property to sort on? e.g. if there is an ObservableCollection of string or int?

    A. In that case you can Simply use . as the property name:

    <scm:SortDescription PropertyName="." />
                    Hie.. Sorry to be so late with this but even with this solution. how do we then declare the CollectionViewSource in the ViewModel?
    – Offer
                    Nov 13, 2014 at 12:47
                    @Offer Hi, I have added the relevant code showing how to declare CollectionViewSource in the ViewModel.
    – akjoshi
                    Nov 13, 2014 at 14:48
                    I have tried this solution, the idea is really great, but I have troubles with getting designtime view to be functional. Maybe you could take a look at: stackoverflow.com/questions/38093415/…
    – Jakub Pawlinski
                    Jul 1, 2016 at 13:35
                    What if I have no property to sort on? e.g. if I have observableCollection of strings, or ints or whatever? Instead of custom reference type
    – John Demetriou
                    Apr 2, 2019 at 11:25
    

    I found that it is handy to have a CollectionViewSource in my ViewModel and bind the ListBox (in my case) to the CollectionViewSource.View while setting the CollectionViewSource.Source to be the list I want to use.

    Like so:

    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}"/>
    

    This means I can do neat stuff in the VM as needed (from 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. 
    

    I suppose this is possible when using the ICollectionView object also, but the demo code in the blog link seems to be some codebehind stuff, refering the listbox directly, which I'm trying to avoid.

    BTW before you ask, here's how you use a Design Time VM: WPF Design Time View Model

    Just for reference, another way is to use an attached property on the CollectionViewSource which then pipes the functions to the ViewModel (Implementing an Interface).

    This is a very basic Demonstration just for filtering, it would need some work for e.g. a second Collection on the VM but i think it's enough to show the general technique.

    If this is better or worse than the other methods is up for discussion, i just wanted to point out, that there's another way of doing this

    Definition of attached Property:

    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)
    

    Interface:

    public interface IFilterCollectionViewSource
        void Filter(object sender, FilterEventArgs e);
        event EventHandler FilterRefresh;
    

    usage in xaml:

    <CollectionViewSource
            x:Key="yourKey"
            Source="{Binding YourCollection}"
            classes:CollectionViewSourceFilter.FilterObject="{Binding}" />
    

    and usage in the 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;
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.

     
    推荐文章