J'ai utilisé un glisser-déposer pour lier un objet de source de données (un modèle de base de données) à DataGrid
(en suivant essentiellement cet exemple dans Entité Framework Liaison de données avec WPF .
Tout fonctionne bien avec cette implémentation.
<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();
}
Cependant, lorsque j'essaie d'utiliser le même code dans ViewModel, cela ne fonctionne pas (FindResource
n'est pas disponible). De plus, je ne pense pas que ce soit la bonne approche (c'est-à-dire d'utiliser x:Key
dans MVVM).
J'apprécierais vraiment toute aide pour m'indiquer quelle est la bonne façon de mettre en œuvre CollectionViewSource
et DataBinding
avec DataGrid
.
Vous avez deux options pour utiliser CollectionViewSource
correctement avec MVVM -
Exposez une ObservableCollection
éléments (Categories
dans votre cas) via votre ViewModel
et créez CollectionViewSource
en 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=WindowsBase"
voir ceci - Filtering
collections de XAML en utilisant CollectionViewSource
Créez et exposez une ICollectionView
directement à partir de votre ViewModel
see this - Comment naviguer, grouper, trier et filtrer des données dans WPF
L'exemple suivant montre comment créer une vue de collection et que Le lier à une ListBox
Voir XAML:
<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>
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); } }
J'ai trouvé qu'il était pratique d'avoir une CollectionViewSource
dans mon ViewModel et de lier la ListBox
(dans mon cas) au CollectionViewSource.View
tout en définissant le CollectionViewSource.Source
comme étant la liste que je veux utiliser.
Ainsi:
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 intéressantes dans la VM si nécessaire (à partir de https://blogs.msdn.Microsoft.com/matt/2008/08/28/collectionview-deferrefresh-my-new-best- ami/ ):
using (FooViewSource.DeferRefresh())
{
//Remove an old Item
//add New Item
//sort list anew, etc.
}
Je suppose que cela est également possible avec l’utilisation de l’objet ICollectionView
, mais le code de démonstration dans le lien du blog semble être quelque chose de codebehind, renvoyant directement à la zone de liste, ce que j’essaie d’éviter.
BTW avant de demander, voici comment utiliser un ordinateur virtuel au moment du design: Modèle de vue au moment du design de WPF
À titre de référence, une autre méthode consiste à utiliser une propriété attachée sur CollectionViewSource, qui dirige ensuite les fonctions vers ViewModel (implémentation d’une interface).
Ceci est une démonstration très basique juste pour le filtrage, il faudrait du travail pour, par exemple. une deuxième collection sur le VM mais je pense que cela suffit pour montrer la technique générale.
Si cela est meilleur ou pire que les autres méthodes est en discussion, je voulais juste souligner, qu'il y a une autre façon de faire
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)
);
}
Interface:
public interface IFilterCollectionViewSource
{
void Filter(object sender, FilterEventArgs e);
event EventHandler FilterRefresh;
}
utilisation en xaml:
<CollectionViewSource
x:Key="yourKey"
Source="{Binding YourCollection}"
classes:CollectionViewSourceFilter.FilterObject="{Binding}" />
et utilisation dans le 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;
}
}