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

Фильтрация и группировка коллекций в WPF

Очень часто, в приложениях приходится фильтровать отображаемые списки. Чуть реже, но тоже достаточно часто их приходится группировать. Каждый раз изобретаются велосипеды. Сегодня я хочу показать стандартный способ фильтрации и группировки эементов в коллекциях. Само собой, пример будет на WPF.
Для демонстрации воспользуемся вот таким классом:

public class Item
{
    public string Name { get; set; }

    public string Type { get; set; }

    static private List<Item> _demo;

    public static List<Item> Demo
    {
        get
        {
            if (_demo == null)
            {
                _demo = new List<Item>();
                _demo.Add(new Item() { Name = "Плоский мир", Type = "Настольная игра" });
                _demo.Add(new Item() { Name = "Коньки", Type = "Спортивные товары" });
                _demo.Add(new Item() { Name = "Воланчик", Type = "Спортивные товары" });
                _demo.Add(new Item() { Name = "Геополитика", Type = "Настольная игра" });
                _demo.Add(new Item() { Name = "Футбольный мяч", Type = "Спортивные товары" });
                _demo.Add(new Item() { Name = "Каркасон", Type = "Настольная игра" });
                _demo.Add(new Item() { Name = "Город синей луны", Type = "Настольная игра" });
            }
            return _demo;
        }
    }
} 
Приведенный класс содержит два свойства, одно для показа, второе для группировки, ну и статическое свойство для генерации демонстрационной коллекции.
Для того, чтобы получить все возможности фильтрации, группировки (есть еще сортировка, но т.к. в DataGrid она может определяться пользователем, то здесь ее рассматривать не буду), нам придется воспользоваться классом CollectionViewSource. Данный класс позволяет через свойство GroupDescription задавать группировки, а через свойство Filter - предикат, который будет определеять, показывать элемент коллекции или нет.
Делаем разметку формы:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <TextBox x:Name="tbFilter" Margin="5" TextChanged="tbFilter_TextChanged" />
    <ListView x:Name="lvMain" Grid.Row="1" Margin="5">
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" Foreground="Blue" />
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Обратите внимание, что способ отображения группировки задается при поможи DataTemplate, в котором мы можем определить любое отображение для группировки. Ну и настройка CollectionViewSource может выглядеть вот так:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        ICollectionView view = CollectionViewSource.GetDefaultView(Item.Demo);
        view.Filter = str => (str as Item).Name.ToLower().Contains(tbFilter.Text.ToLower());
        view.GroupDescriptions.Add(new PropertyGroupDescription("Type"));
        lvMain.ItemsSource = view;
    }

    private void tbFilter_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (lvMain.ItemsSource is ICollectionView)
        {
            (lvMain.ItemsSource as ICollectionView).Refresh();
        }
    }
}

Обратите внимание, на то, что группировку мы ведем указав имя свойства элемента коллекции, а всю остальную работу за нас сделает CollectionViewSource. Для фильтрации же, достаточно задать предикат, который на вход получает элемент коллекции, и возвращает true, если элемент коллекции необходимо отображать, иначе false. Последний метод необходим, чтобы после имзенения текста в TextBox происходилаперепроверка всех элементов методом помещенным в Filter. Ну и вот так это работает:
На этом все. 

Комментариев нет:

Отправить комментарий