Tuesday, April 17, 2007

ListBoxItem index or enumirating of ListBox Items over filtered data source

One of my clients asked me today, how it possible to put the index of ListBox Item near the value, while data source of it might be filtered. How to get the index from inside data source all of you, I believe, knows, but how to ask out host (it might be any ItemsCountrol) about what the real index of the binded item, while some of items are hidden.

So, in order to do it, we have to be able to "ask" items control about the real position of it's items. How to do it? Simple, ItemsControl has really useful static method, named ItemsControlFromItemsContainer and the control's ItemContainerGenerator has method named IndexFromContainer. This means, that if we have an item and the item control, we can figure it's index. But first of all, how to get into Item, while using templates? I explained it earlier when spoke about Autoexplainable listbox items. You should use FindAncestor type of Relative binding to get an ancestor of the template.


<DataTemplate x:Key="myItem">
        <TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}"/>

Now, let's think a bit, who can add not data relevant information to our DataTemplate? Who invoked each time the item do something with binding? The right answer is converter. Let's create it.


class MyConverter:IValueConverter
        #region IValueConverter Members
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            ListBoxItem item = value as ListBoxItem;
            ListBox view = ItemsControl.ItemsControlFromItemContainer(item) as ListBox;
            int index = view.ItemContainerGenerator.IndexFromContainer(item);
            return index.ToString()+" - "+item.Content.ToString();

So, we done, the only thing I want to explain here is how to filter data for those, who do not know. ICollectionView - the interface, that implements the collection has Filter property, that receives strange class named Predicate<object>. This one will do your work. So let's first create a simple strings collection and set it as data context for our application


List<string> numbers = new List<string>();
            for (int i = 0; i < 10; i++)
                numbers.Add("Item " + i);
            myPanel.DataContext = numbers;

Next let's add filter property while check box (see screenshot) is checked. Steps? Get collection and filter it :)


void onChecked(object sender, RoutedEventArgs e)
            ICollectionView view = CollectionViewSource.GetDefaultView(myPanel.DataContext) as ListCollectionView;
            view.Filter = new Predicate<object>(FilterOdds);

On filter let's remove all odd numbers from our collection. Like this:


bool FilterOdds(object item)
            return int.Parse(item.ToString().Remove(0, 5)) % 2 == 1;

Done, remove the filter on Unchecked event and we done


void onUnchecked(object sender, RoutedEventArgs e)
            ICollectionView view = CollectionViewSource.GetDefaultView(myPanel.DataContext) as ListCollectionView;
            view.Filter = null;

That's all, folks.

Source code for this article

No comments: