Bonne façon d'utiliser CollectionViewSource dans ViewModel

j'ai utilisé le Drag and Drop pour lier L'objet source de données (un modèle de base de données) à DataGrid (essentiellement en suivant cet exemple dans Entity Framework Databinding avec WPF .

Tout fonctionne très bien avec cette implémentation.

XAML

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

Code Derrière

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

Toutefois, quand j'essaie d'utiliser le même code à L'intérieur de ViewModel, cela ne fonctionne pas ( FindResource n'est pas disponible), d'ailleurs, je ne pense pas que ce soit la bonne approche (i.e. d'utiliser x:Key dans MVVM).

j'apprécierais vraiment toute aide pour me montrer quelle est la bonne façon de mettre en œuvre CollectionViewSource et DataBinding avec DataGrid .

30
demandé sur ΩmegaMan 2014-01-02 21:45:21

3 réponses

vous avez deux options pour utiliser CollectionViewSource correctement avec MVVM -

  1. exposez un ObserVableCollection d'articles ( Categories dans votre cas) à travers votre ViewModel et créez CollectionViewSource dans xaml comme ceci -

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

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

    voir cette - Filtering collections de XAML utilisant Collection Viewsource

  2. Créez et exposez un ICollectionView directement à partir de votre ViewModel

    voir ce - comment naviguer, grouper, trier et filtrer les données dans WPF

l'exemple suivant montre comment créer une vue collection et liez - le à un ListBox

XAML:

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

Voir Codebehind:

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);
    }
}
44
répondu akjoshi 2018-01-16 17:12:25

j'ai trouvé qu'il est pratique d'avoir une CollectionViewSource dans mon ViewModel et de lier la liste de diffusion (dans mon cas) à la CollectionViewSource.Affichage lors de la mise en place de la CollectionViewSource.De la Source à la liste que je veux utiliser.

Comme suit:

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

cela signifie que je peux faire des choses soignées dans la VM au besoin (de 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. 
        }

je suppose que c'est possible en utilisant l'objet ICollectionView aussi, mais le code de démonstration dans le lien de blog semble être quelque chose codebehind, se référant directement à la boîte de liste, que j'essaie d'éviter.

BTW avant de vous demander, voici comment vous utilisez un Design Time VM: WPF Design Time View Model

6
répondu gakera 2017-09-13 16:05:36

juste pour référence, une autre façon est d'utiliser une propriété attachée sur la CollectionViewSource qui achemine ensuite les fonctions vers le ViewModel (implémentation D'une Interface).

C'est une démonstration très basique juste pour le filtrage, il faudrait un peu de travail pour par exemple une deuxième Collection sur la VM mais je pense que c'est suffisant pour montrer la technique générale.

si c'est mieux ou pire que les autres méthodes est à discuter, je voulais juste faites remarquer qu'il y a une autre façon de faire cela

Définition de la Propriété attachée:

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

de l'Interface:

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

usage dans xaml:

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

et utilisation dans le modèle de vue:

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
répondu FastJack 2018-03-07 12:57:59