it-swarm.com.de

Richtige Verwendung von CollectionViewSource in ViewModel

Ich habe Drag & Drop verwendet, um das Datenquellenobjekt (ein DB-Modell) an DataGrid zu binden (im Wesentlichen nach diesem Beispiel in Entity Framework Databinding mit WPF

Alles funktioniert gut mit dieser Implementierung.

XAML

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

Code hinter

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

Wenn ich jedoch versuche, denselben Code in ViewModel zu verwenden, funktioniert er nicht (FindResource ist nicht verfügbar). Außerdem glaube ich nicht, dass dies der richtige Ansatz ist (d. H., x:Key in MVVM).

Ich würde mich sehr freuen, wenn Sie mir zeigen würden, wie Sie CollectionViewSource und DataBinding mit DataGrid richtig implementieren können.

37
David S

Sie haben zwei Möglichkeiten, CollectionViewSource korrekt mit MVVM zu verwenden.

  1. Stellen Sie eine ObservableCollection von Elementen (Categories in Ihrem Fall) durch Ihre ViewModel bereit und erstellen Sie CollectionViewSource in XAML folgendermaßen

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

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

    siehe diese - Filtering Sammlungen aus XAML mit CollectionViewSource

  2. Erstellen und Verfügbarmachen einer ICollectionView direkt von Ihrer ViewModel

    siehe dazu - Navigieren, Gruppieren, Sortieren und Filtern von Daten in WPF

Das folgende Beispiel zeigt, wie Sie eine Sammlungsansicht erstellen und An eine ListBox binden.

XAML anzeigen:

<Window 
    x:Class="CustomerView"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml">
    <ListBox ItemsSource={Binding Customers} />
</Window>

Codebehind anzeigen:

public class CustomerView
{
   public CustomerView()
   {
        DataContext = new CustomerViewModel();
   }
}

ViewModel:

public class CustomerViewModel
{
    private ICollectionView _customerView;

    public ICollectionView Customers
    {
        get { return _customerView; }
    }

    public CustomerViewModel()
    {
        IList<Customer> customers = GetCustomers();
        _customerView = CollectionViewSource.GetDefaultView(customers);
    }
}
50
akjoshi

Ich habe festgestellt, dass es praktisch ist, eine CollectionViewSource in meinem ViewModel zu haben und die ListBox (in meinem Fall) an den CollectionViewSource.View zu binden, während Sie CollectionViewSource.Source als Liste festlegen, die ich verwenden möchte. 

So wie:

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

Dies bedeutet, dass ich in der VM nach Bedarf ordentliche Sachen erstellen kann (von https://blogs.msdn.Microsoft.com/matt/2008/08/28/collectionview-deferrefresh-my-new-best- Freund/ ):

        using (FooViewSource.DeferRefresh())
        {
            //Remove an old Item
            //add New Item
            //sort list anew, etc. 
        }

Ich nehme an, dass dies auch möglich ist, wenn das ICollectionView-Objekt verwendet wird, aber der Demo-Code im Blog-Link scheint etwas Codebehind-Zeug zu sein, das direkt auf das Listenfeld verweist, was ich zu vermeiden versuche. 

Übrigens, bevor Sie fragen, wie Sie eine Design Time VM verwenden: WPF Design Time View Model

6
gakera

Eine andere Möglichkeit ist die Verwendung einer angefügten Eigenschaft in der CollectionViewSource, die dann die Funktionen an das ViewModel (Implementing a Interface) leitet.

Dies ist eine sehr einfache Demonstration, die nur zum Filtern dient. eine zweite Sammlung auf der VM, aber ich denke, es reicht aus, um die allgemeine Technik zu zeigen.

Wenn dies besser oder schlechter ist als die anderen Methoden, die zur Diskussion stehen, möchte ich nur darauf hinweisen, dass es einen anderen Weg gibt, dies zu tun

Definition der angefügten Eigenschaft:

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

Schnittstelle:

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

verwendung in Xaml:

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

und Verwendung im 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;
    }
}
0
FastJack