Modul corect de utilizare a CollectionViewSource în ViewModel (Programare, C#, Wpf, Mvvm, Datagrid, Collectionviewsource)

David S a intrebat.

Am folosit Drag and Drop pentru a lega obiectul Data Source (un model DB) de DataGrid (practic, urmând acest exemplu din Entity Framework Databinding cu WPF.

Totul funcționează bine cu această implementare.

XAML

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

Codul din spate

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

Cu toate acestea, atunci când încerc să folosesc același cod din cadrul ViewModel, nu funcționează (FindResource nu este disponibil), în plus, nu cred că aceasta este abordarea corectă (adică să folosesc x:Key în MVVM).

Aș aprecia foarte mult orice ajutor pentru a-mi indica care este modalitatea corectă de a implementa CollectionViewSource și DataBinding cu DataGrid.

3 răspunsuri
akjoshi

Aveți două opțiuni pentru a utiliza CollectionViewSource în mod corespunzător cu MVVM –

  1. Expuneți un ObservableCollection de elemente (Categories în cazul tău) prin intermediul ViewModel și creați CollectionViewSource în XAML astfel –

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

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

    a se vedea acest lucru – Filtering colecții din XAML utilizând CollectionViewSource

  2. Creați și expuneți un ICollectionView direct din colecția dvs. ViewModel

    a se vedea acest lucru – Cum să navigați, să grupați, să sortați și să filtrați date în WPF

Exemplul următor arată cum să creați o vizualizare de colecție și să o legați de un ListBox

Vizualizare XAML:

<Window 
    
    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. Dacă nu există o proprietate pe care să se facă sortarea? de exemplu, dacă există o proprietate ObservableCollection de șir sau int?

A. În acest caz, puteți utiliza pur și simplu . ca nume de proprietate:

<scm:SortDescription PropertyName="." />

Comentarii

  • FYI, scm: este aceasta xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" –  > Por user2023861.
  • Hie… Îmi pare rău că am întârziat atât de mult cu acest lucru, dar chiar și cu această soluție. cum declarăm apoi CollectionViewSource în ViewModel? –  > Por Oferta.
  • @Oferta Bună ziua, am adăugat codul relevant care arată cum să declar CollectionViewSource în ViewModel. –  > Por akjoshi.
  • Am încercat această soluție, ideea este într-adevăr grozavă, dar am probleme cu obținerea vizualizării designtime pentru a fi funcțională. Poate că ați putea arunca o privire la: stackoverflow.com/questions/38093415/… – –  > Por Jakub Pawlinski.
  • Ce se întâmplă dacă nu am nicio proprietate pe care să o sortez? De exemplu, dacă am observableCollection de șiruri de caractere, sau ints sau orice altceva? În loc de tipul de referință personalizat –  > Por John Demetriou.
gakera

Am constatat că este la îndemână să ai un tip de CollectionViewSource în ViewModel-ul meu și să leagă ListBox (în cazul meu) la CollectionViewSource.View în timp ce setează CollectionViewSource.Source să fie lista pe care vreau să o folosesc.

Astfel:

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

Acest lucru înseamnă că pot să fac lucruri îngrijite în VM, după cum este necesar (din 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. 
        }

Presupun că acest lucru este posibil atunci când se utilizează ICollectionView dar codul demonstrativ din linkul de pe blog pare a fi o chestie de codebehind, care se referă direct la listbox, ceea ce încerc să evit.

BTW înainte de a întreba, iată cum se utilizează un Design Time VM: Modelul de vizualizare WPF Design Time View Model

FastJack

Doar ca referință, o altă modalitate este de a utiliza o proprietate atașată pe CollectionViewSource care apoi direcționează funcțiile către ViewModel (Implementarea unei interfețe).

Aceasta este o demonstrație foarte elementară doar pentru filtrare, ar mai fi nevoie de ceva muncă pentru, de exemplu, o a doua colecție pe VM, dar cred că este suficientă pentru a arăta tehnica generală.

Dacă această metodă este mai bună sau mai rea decât celelalte metode este subiect de discuție, am vrut doar să subliniez că există o altă modalitate de a face acest lucru.

Definiția proprietății atașate:

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

Interfață:

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

utilizare în xaml:

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

și utilizarea în 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;
    }
}