Как я могу обеспечить динамическую фильтрацию для коллекции объектов? - PullRequest
1 голос
/ 25 августа 2010

У меня есть класс ShipmentsCollection, который наследует ObservableCollection, который содержит объекты отгрузки (сущности).Это отображается в списке в моем пользовательском контроле ShipmentsView.Мое намерение состоит в том, чтобы позволить пользователю ввести текстовое поле над списком и отфильтровать список с элементами, содержащими эту строку, а также отфильтровать на основе нескольких опций на основе флажков и переключателей (состояние доставки и направление заказа).

Я пробовал это несколькими способами, но ни один из них не выглядит очень элегантным или действительно функциональным.Вот что я попробовал:

  • Поместите ShipmentsCollection в CollectionViewSource и отфильтруйте через предикат.Не удалось найти хороший способ сделать фильтр автоматическим обновлением на основе пользовательской типизации или изменения параметра.
  • Рефакторинг как класс, который наследует collectionViewSource и пытался объявить непосредственно в XAML, но получил следующую ошибку: "Указанныйименованное соединение либо не найдено в конфигурации, не предназначено для использования с поставщиком EntityClient, либо недействительно ".Пробовал исправить, но не смог найти работающее решение.
  • Рефакторинг для наследования от CollectionView, реализовал логику фильтра, в обработчике событий в codebehind.Все еще пытаюсь выяснить, как я могу передать строку фильтра в обработчик событий, не называя элемент управления текстового поля filtertext.

У кого-то есть хорошие идеи относительно реализации этой функциональности в шаблоне проектирования MVVM.Я ожидаю, что в списке будет не более 200 объектов, поэтому это не будет огромная операция фильтрации.

Кори

Ответы [ 2 ]

3 голосов
/ 11 августа 2016

Я знаю, что этот вопрос закрыт и старый.но для кого-то вроде меня, ищущего динамическую фильтрацию, можно обратиться к следующей ссылке

https://github.com/lokeshlal/WPFDynamicFilters

. В приведенном выше примере создаются фильтры для каждой сущности на основе атрибута, определенного в свойстве модели сущности.

Например:

  1. Определить атрибут для фильтров

    public class FilterAttribute : Attribute
    {
        public FilterAttribute() { }
        public string FilterLabel { get; set; }
        public object FilterValue { get; set; }
        public string FilterKey { get; set; }
        public Type FilterDataType { get; set; }
        public bool IsDropDown { get; set; }
        public string DropDownList { get; set; }
        public List<object> ObjectDropDownList { get; set; }
    }
    
  2. Применить указанный выше атрибут в свойствах модели

    public class GridModel
    {
        [Filter(FilterLabel = "Id",
            FilterKey = "Id",
            IsDropDown = false,
            FilterDataType = typeof(int))]
        public int Id { get; set; }
        [Filter(FilterLabel = "Name",
            FilterKey = "Name",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        public string Name { get; set; }
        [Filter(FilterLabel = "Country",
            FilterKey = "Country",
            IsDropDown = true,
            FilterDataType = typeof(int),
            DropDownList = "Country")]
        public string Country { get; set; }
        [Filter(FilterLabel = "Address",
            FilterKey = "Address",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        public string Address { get; set; }
    }
    
  3. Определите модель, которая будет привязана к раскрывающемуся типу

    public class Country
    {
        public int Id { get; set; } // id will be used for value
        public string Name { get; set; } // Name will be used for display value
    }
    
  4. ViewModel фактического вида

    public class FilterViewModel
    {
        public ICommand CheckFiltersCommand { get; set; }
        public FilterViewModel()
        {
            CheckFiltersCommand = new DelegateCommand(GetFilters);
            GridSource = new List<GridModel>();
            GridSource.Add(new GridModel() { Id = 1, Name = "Name1", Country = "Denmark" });
            GridSource.Add(new GridModel() { Id = 2, Name = "Name2", Country = "India" });
            GridSource.Add(new GridModel() { Id = 3, Name = "Name3", Country = "Australia" });
            GridSource.Add(new GridModel() { Id = 4, Name = "Name4", Country = "India" });
            GridSource.Add(new GridModel() { Id = 5, Name = "Name5", Country = "Australia" });
            GridSource.Add(new GridModel() { Id = 6, Name = "Name6", Country = "Hongkong" });
    
            FilterControlViewModel = new FilterControlViewModel();
            FilterControlViewModel.FilterDetails = new List<FilterAttribute>();
    
            foreach (var property in typeof(GridModel).GetProperties())
            {
                if (property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).Any())
                {
                    var attribute = (FilterAttribute)property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).First();
                    FilterControlViewModel.FilterDetails.Add(attribute);
                }
            }
        }
    
        private void GetFilters()
        {
            FilterCollection = new Dictionary<string, object>();
            foreach (var filter in FilterControlViewModel.FilterDetails)
            {
                if (filter.IsDropDown)
                {
                    if (filter.FilterValue != null)
                        FilterCollection.Add(filter.FilterKey, filter.FilterValue.GetType().GetProperty("Id").GetValue(filter.FilterValue));
                }
                else
                {
                    FilterCollection.Add(filter.FilterKey, filter.FilterValue);
                }
            }
    
            MessageBox.Show(string.Join(", ", FilterCollection.Select(m => m.Key + ":" + Convert.ToString(m.Value)).ToArray()));
        }
    
        public List<GridModel> GridSource { get; set; }
    
        public Dictionary<string, object> FilterCollection { get; set; }
    
        public FilterControlViewModel FilterControlViewModel { get; set; }
    }
    

В вышеприведенном представлении свойство 'FilterControlViewModel' будет перебирать все свойства модели и собирать информацию фильтра свойств.Это же свойство будет назначено пользовательскому элементу управления, как описано в файле xaml ниже

    <Window x:Class="WPFDynamicFilters.GridWithFilters"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFDynamicFilters"
            mc:Ignorable="d"
            Title="gridwithfilters" Height="481.239" Width="858.171">
        <Grid>
            <Grid HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" x:Name="FilterGrid" Height="209" Width="830">
                <Border BorderThickness="1" BorderBrush="Gold"/>
                <local:Filter DataContext="{Binding FilterControlViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10" />
            </Grid>
            <DataGrid x:Name="DataGrid" ItemsSource="{Binding GridSource}" HorizontalAlignment="Left" Margin="10,294,0,0" VerticalAlignment="Top" Height="146" Width="830"/>
            <Button x:Name="button" Content="Check Filters" HorizontalAlignment="Left" Margin="10,245,0,0" VerticalAlignment="Top" Width="110" Command="{Binding CheckFiltersCommand}"/>
        </Grid>
    </Window>

Элемент управления фильтра будет принимать все атрибуты и отображать элемент управления с помощью itemscontrol

    <UserControl x:Class="WPFDynamicFilters.Filter"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WPFDynamicFilters"
                 mc:Ignorable="d" 
                 d:DesignHeight="40" 
                 >
        <UserControl.Resources>
            <DataTemplate x:Key="TStringTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="TxtFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TIntegerTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="IntFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TDropDownTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <ComboBox 
                    SelectedItem="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          SelectedIndex="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          ItemsSource="{Binding ObjectDropDownList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          DisplayMemberPath="Name"
                                 RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" />
                </StackPanel>
            </DataTemplate>
            <local:FilterTemplateSelector x:Key="FilterTemplateSelector" 
                    StringTemplate="{StaticResource TStringTemplate}"
                    IntegerTemplate="{StaticResource TIntegerTemplate}"
                    DropDownTemplate="{StaticResource TDropDownTemplate}"
                    />
        </UserControl.Resources>
        <Grid>
            <ItemsControl ItemsSource="{Binding FilterDetails}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                            <ContentControl 
                                Content="{Binding}" 
                                HorizontalAlignment="Left" 
                                ContentTemplateSelector="{StaticResource FilterTemplateSelector}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </UserControl>

Наконец, определитеселектор шаблонов

public class FilterTemplateSelector : DataTemplateSelector
    {
        public DataTemplate StringTemplate { get; set; }
        public DataTemplate IntegerTemplate { get; set; }
        public DataTemplate DropDownTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item,
                     DependencyObject container)
        {
            var filter = (item as FilterAttribute);
            if (filter == null) return StringTemplate;

            if (!filter.IsDropDown)
            {
                switch (filter.FilterDataType.Name.ToLower())
                {
                    case "int32":
                    case "int64":
                        return IntegerTemplate;
                    case "string":
                        return StringTemplate;
                }
            }
            else
            {
                // display drop down
                switch (filter.DropDownList)
                {
                    case "Country":
                        filter.ObjectDropDownList = GetDropDown.GetCountries().ToList<object>();
                        break;
                }
                return DropDownTemplate;
            }

            return StringTemplate;
        }
    }
3 голосов
/ 25 августа 2010

Ваш первый вариант - тот, который я бы предложил. Чтобы заставить автофильтр работать на основе набора текста, я бы сделал что-то вроде свойства SearchString в моей ViewModel, привязал к нему текст текстового поля и установил UpdateSourceTrigger в привязке к PropertyChanged, чтобы он вызывал событие SearchString PropertyChanged каждый раз. время ввода ключа вместо ожидания, пока поле не потеряет фокус.

XAML:

<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" />

ViewModel: При установленном выше свойстве PropertyChanged метод «Set» вызывается каждый раз, когда вводится ключ, а не только когда текстовое поле теряет фокус.

private string _searchString;
public string SearchString
{
    get { return _searchString; }
    set
    {
        if (_searchString != value)
        {
            _searchString = value;
            OnPropertyChanged("SearchString");
        }
    }
}
...