воскресенье, 25 ноября 2012 г.

Класс с поддержкой фильтрации и сортировки в Windows 8 приложениях

Для меня оказалось значительным сюрпризом, что CollectionViewSource в WPF и CollectionViewSource в Windows 8 приложениях, работают по разному. На форуме MSDN, уже дважды возникал про это вопрос (здесь и здесь). Ну и простит меня LXGDARK пример я напишу на C#, но думаю у него проблем с этим не возникнет. Итак, под катом пример класса, который принимает коллекцию и позволяет ее фильтровать и сортировать.

Для начала, включим в наш класс функционал по уведомлению внешнего мира, о изменении свойств. Т.к. в Windows 8 приложениях обделили не только CollectionViewSource, но и DependencyProperty, отняв у него замечательный метод RegisterReadOnly. То я буду реализовывать класс с поддержкой INotifyPropertyChanged (зачем он нужен, можно почитать здесь).
Ладно, начинаем:

public class AlCollectionViewSource : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string p_propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(p_propertyName));
        }
    }
}
Теперь добавим поле для хранения коллекции и свойство, через которое будут читать отфильтрованную и отсортированную коллекцию, кстати, не забываем про конструктор:

IEnumerable _source;

public IEnumerable View
{
    get
    {
        return _source;
    }
}

public AlCollectionViewSource(IEnumerable p_source)
{
    _source = p_source;
    OnPropertyChanged("View");
}
Ок, теперь, если мы передадим в класс коллекцию, мы сможем ее получить, но нам нужен немного больший функционал. Давайте добавим фильтрацию. Для этого, достаточно добавить свойство и немного поправить свойство View:

private Func<bool> _filter = null;

public Func<bool> Filter
{
    get
    {
        return _filter;
    }
    set
    {
        if (_filter != value)
        {
            _filter = value;
            OnPropertyChanged("View");
        }
    }
}

public IEnumerable View
{
    get
    {
        IEnumerable result = _source;
        Func<bool> filter = Filter;
        if (filter != null)
        {
            result = result.Where(item => Filter(item));
        }
        return result;
    }
}
Ок, осталось добавить сортировку. Аналогично, нам понадобится свойство и правка свойства View. В этот раз она будет чуть посложнее. Если у нас есть имя свойства по которому сортировать, то мы извлекаем его значение через Reflection и сортируем по нему, если вместо имени поля у нас пустая строка, то сортируем по самим объектам, ну а если null, то возвращаем в том же порядке, в каком значения были в исходной коллекции.
private string _orderPropertyName = null;

public string OrderPropertyName
{
    get
    {
        return _orderPropertyName;
    }
    set
    {
        if (_orderPropertyName != value)
        {
            _orderPropertyName = value;
            OnPropertyChanged("View");
        }
    }
}

public IEnumerable View
{
    get
    {
        IEnumerable result = _source;
        Func<bool> filter = Filter;
        if (filter != null)
        {
            result = result.Where(item => filter(item));
        }
        if (!string.IsNullOrWhiteSpace(OrderPropertyName))
        {
            result = result.OrderBy(
                item =>
                {
                    Type type = item.GetType();
                    PropertyInfo property = type.GetTypeInfo().GetDeclaredProperty(OrderPropertyName);
                    return property.GetValue(item);
                });
        }
        else
        {
            if (OrderPropertyName == "")
            {
                result = result.OrderBy(item => item);
            }
        }
        return result;
    }
}
Ну и давайте небольшой пример, в стиле MVVM.
Для примера, введем вот такой вспомогательный класс:

public class Person
{
    public string LastName { get; set; }

    public static List<Person> GetPersonCollection()
    {
        List<Person> result = new List<Person>();
        result.Add(new Person { LastName = "Сидоров" });
        result.Add(new Person { LastName = "Федоров" });
        result.Add(new Person { LastName = "Иванов" });
        result.Add(new Person { LastName = "Петров" });
        return result;
    }
}
Ну и ViewModel:

public class MyViewModel
{
    public ICommand SearchCommand { get; private set; }

    public ICommand OrderCommand { get; private set; }

    public AlCollectionViewSource<Person> Items { get; private set; }

    public string FilterString { get; set; }

    public MyViewModel()
    {
        Items = new AlCollectionViewSource<Person>(Person.GetPersonCollection());
        SearchCommand = new MyCommand(() => Items.Filter = item => item.LastName.ToLower().Contains(FilterString.ToLower()));
        OrderCommand = new MyCommand(() => Items.OrderPropertyName = "LastName");
    }
}

Разметка окна:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding Path=FilterString,Mode=TwoWay}" Width="150" Margin="5" />
        <Button Content="Фильтр!" Command="{Binding SearchCommand}" />
        <Button Content="Сортировка!" Command="{Binding OrderCommand}" />
    </StackPanel>
    <ListView x:Name="lvMain" Grid.Row="1" DataContext="{Binding Items}" ItemsSource="{Binding View}" DisplayMemberPath="LastName" />
</Grid>
Ну и картинки, как это работает. Так приложение выглядит после запуска:
Так, после набора в TextBox-е строки "ро" и нажатия на "Фильтр!":
Ну и после нажатия на кнопку "Сортировка!":
Вот как то так.
 

4 комментария:

  1. Алексей. А готовый проект для изучения можете сбросить?

    ОтветитьУдалить
    Ответы
    1. Кто лентяй и не хочет сам, с нуля написать? А?
      Вот, если что, ссылка:
      https://skydrive.live.com/redir?resid=9F0ADE63C4A0A8B9!197&authkey=!AB2SRQeflwfZ1oY

      Удалить
    2. Просто запутался. Где какой класс, какой код к основному окну, какой к вспомогательным классам. Неделю уже перелопачиваю всякие варианты. Модель MVVM не знаю поэтому и трудности.

      Удалить