Graeme_Grant
При работе с WPF проще сначала сделать данные с помощью Привязка данных (WPF)[^]. Таким образом, представление и данные разделяются, давая окно в данные. Преимущество заключается в том, что вы разделяете данные, а затем можете выбрать, как эти данные будут восприниматься пользователем.
Джон отчасти прав, тот самый Шаблон проектирования MVVM[^] лучше поможет отделить данные от представления. Это решение будет использовать MVVM, однако существует гораздо более эффективный метод WPF, встроенный в .Net Framework, чтобы делать то, что вы хотите.
При работе с коллекциями данных, а фильтрация, сортировка и т. д. используются на коллекциях в памяти, лучшим методом WPF является использование Классу collectionviewsource[^] и привязать к результирующему представлению ваши данные.
Ниже приведен пример того, как использовать CollectionViewSource
с DataGrid
и ComboBox
чтобы выбрать тип фильтра.
Во-первых, нам нужно реализовать обертки для INotifyPropertyChanged Интерфейс[^] используется для передачи изменений в привязке данных.
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue)) && field.Equals(newValue)) return;
field = newValue;
RaisePropertyChanged(propertyName);
}
public void RaisePropertyChanged (string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode
=> (bool) DesignerProperties.IsInDesignModeProperty
.GetMetadata(typeof(DependencyObject))
.DefaultValue;
}
Далее нам нужен класс для хранения данных экземпляра для каждого элемента в нашей коллекции:
public class PersonModel : ObservableBase
{
private string name;
public string Name
{
get => name;
set => Set(ref name, value);
}
private decimal paid;
public decimal Paid
{
get => paid;
set => Set(ref paid, value);
}
private decimal owing;
public decimal Owing
{
get => owing;
set => Set(ref owing, value);
}
}
Теперь мы можем создать нашу модель представления, используемую для привязки данных к представлению:
class MainViewModel : ViewModelBase
{
public MainViewModel() => Mock();
private Random rand = new Random();
public ObservableCollection<PersonModel> People { get; }
= new ObservableCollection<PersonModel>();
private CollectionViewSource CSV { get; set; }
public ICollectionView PeopleView { get; private set; }
public List<string> PaymentStates { get; }
= new List<string> {"All", "Not Paid", "Partially Paid", "Paid in Full"};
private string selectedState;
public string SelectedState
{
get => selectedState;
set
{
Set(ref selectedState, value);
RefreshFilter();
}
}
private void Mock()
{
for (int i = 0; i < 500; i++)
{
var owed = rand.Next(100, 500);
var paidState = rand.Next(1, 100);
People.Add(new PersonModel
{
Name = $"Person {i}",
Owing = owed,
Paid = paidState > 0 && paidState < 33
? 0
: paidState > 32 && paidState < 66
? rand.Next(5, owed / 10) * 10
: owed
});
}
selectedState = PaymentStates[0];
CSV = new CollectionViewSource {Source = People};
PeopleView = CSV.View;
RefreshFilter();
}
private void RefreshFilter()
{
Func<PersonModel, bool> filterType;
switch (PaymentStates.IndexOf(selectedState))
{
case 1: // not paid
filterType = x => x.Paid == 0;
break;
case 2: // partially paid
filterType = x => x.Paid > 0 && x.Paid < x.Owing;
break;
case 3: // paid in full
filterType = x => x.Paid >= x.Owing;
break;
default:
filterType = _ => true;
break;
}
PeopleView.Filter = item => filterType(item as PersonModel);
// update based on filter
PeopleView.Refresh();
}
}
Теперь мы можем наконец реализовать нашу точку зрения:
<Window x:Class="FilteredDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FilteredDataGrid"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="Raw Data"/>
<DataGrid Grid.Row="1"
ItemsSource="{Binding People}"/>
<StackPanel Grid.Column="1"
Orientation="Horizontal">
<Label Content="Filter By:"/>
<ComboBox ItemsSource="{Binding PaymentStates}"
SelectedItem="{Binding SelectedState}"
Width="100" Margin="10 0 0 0"/>
</StackPanel>
<DataGrid Grid.Row="1" Grid.Column="1"
x:Name="FilterResults"
ItemsSource="{Binding PeopleView}"/>
</Grid>
</Window>
Изменение выделения в поле
Combobox
изменится примененный фильтр и результаты
DataGrid
повторит примененный фильтр.
Примечание: в
RefreshFilter()
из
MainViewModel
Я использую локальные функции для предварительного определения фильтра, а не помещаю полный фильтр в
PeopleView.Filter
Как и любой циклический код, поместите как можно меньше внутри цикла, чтобы максимизировать производительность.
Здесь должно быть достаточно информации, чтобы применить ее к вашему собственному проекту для реализации того типа фильтрации, которого вы хотите достичь.