c# - filtrar - La forma correcta de usar CollectionViewSource en ViewModel
filtrar datagridview c# (3)
Utilicé Arrastrar y soltar para vincular el objeto de origen de datos (un modelo de DB) a DataGrid
(básicamente siguiendo este ejemplo en Entity Framework Databinding with WPF .
Todo funciona bien con esta implementación.
XAML
<Window.Resources>
<CollectionViewSource x:Key="categoryViewSource"
d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource categoryViewSource}">
..
Código detrás
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();
}
Sin embargo, cuando trato de usar el mismo código desde ViewModel, no funciona ( FindResource
no está disponible), además, no creo que este sea el enfoque correcto (es decir, usar x:Key
en MVVM).
Realmente agradecería cualquier ayuda para señalarme cuál es la forma correcta de implementar CollectionViewSource
y DataBinding
con DataGrid
.
Descubrí que es útil tener un CollectionViewSource en mi ViewModel y enlazar el ListBox (en mi caso) con el CollectionViewSource.View mientras configuro el CollectionViewSource.Source para que sea la lista que quiero usar.
Al igual que:
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}"/>
Esto significa que puedo hacer cosas ordenadas en la máquina virtual según sea necesario (desde 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.
}
Supongo que esto también es posible cuando se utiliza el objeto ICollectionView
, pero el código de demostración en el enlace del blog parece ser un código subyacente, que hace referencia al cuadro de lista directamente, y estoy intentando evitarlo.
Por cierto, antes de preguntar, así es cómo se usa una máquina virtual de tiempo de diseño: WPF Design Time View Model
Solo como referencia, otra forma es usar una propiedad adjunta en CollectionViewSource que luego canaliza las funciones al ViewModel (Implementando una interfaz).
Esta es una Demostración muy básica solo para filtrar, necesitaría algún trabajo para, por ejemplo, una segunda Colección en la VM, pero creo que es suficiente para mostrar la técnica general.
Si esto es mejor o peor que los otros métodos es tema de discusión, solo quería señalar, que hay otra forma de hacerlo
Definición de propiedad adjunta:
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)
);
}
Interfaz:
public interface IFilterCollectionViewSource
{
void Filter(object sender, FilterEventArgs e);
event EventHandler FilterRefresh;
}
uso en xaml:
<CollectionViewSource
x:Key="yourKey"
Source="{Binding YourCollection}"
classes:CollectionViewSourceFilter.FilterObject="{Binding}" />
y uso en 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;
}
}
Tiene dos opciones para usar CollectionViewSource
correctamente con MVVM:
Exponga una
ObserVableCollection
de elementos (Categories
en su caso) a través de suViewModel
y creeCollectionViewSource
en xaml como este -<CollectionViewSource Source="{Binding Path=Categories}"> <CollectionViewSource.SortDescriptions> <scm:SortDescription PropertyName="CategoryName" /> </CollectionViewSource.SortDescriptions> </CollectionViewSource>
scm:
xmlns:scm="clr-namespace:System.ComponentModel;assembly=Wind​owsBase"
ver esto -
Filtering
colecciones de XAML usando CollectionViewSourceCrear y exponer un
ICollectionView
directamente desde suViewModel
ver esto - Cómo navegar, agrupar, ordenar y filtrar datos en WPF
El siguiente ejemplo muestra cómo crear una vista de colección y vincularla a 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>
Ver 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); } }