I have a simple objective: to apply a filter to a ListBox’s data source and have the filtered out items “fade away.” I also want items that are filtered back into the ListBox to “fade into view.” I thought it would be a straightforward affair. I was very wrong. This blog post is a big one, so go grab a snifter of Laphroaig 10 Year Cask Strength and put on some J.S. Bach harpsichord music (or whatever keeps you afloat) and then we’ll get started…
Before I go any further, if you aren’t interested in reading about all the issues that exist around implementing this functionality, you can download the source code here: Animated ListBoxItem Filtering (change the file extension from .DOC to .ZIP). Keep in mind, I have not yet found what I consider to be a satisfactory way to implement this.
It was very easy to throw together some dumb code that did the trick, in a Window’s code-behind. Unfortunately that is a terrible make-shift solution, because it is not at all reusable (unless the prospect of copy-and-pasting the same chunk of code into many Window code-behinds doesn’t make you nauseous – in which case I hope I never have to work with you). It would be much better to put that fading animation logic into a ListBox subclass, so that it’s a breeze to make use of it later on. That’s where the trouble begins…
WPF has support for filtering a collection of items which are displayed, via the CollectionView.Filter property. Filter is a public property of type Predicate<object> which allows you to give a Yea or Nay regarding which data objects should be displayed in the UI.
You might be thinking that the Filter property would make my task a snap. That’s what I thought when I first started looking into this issue. It turns out that using the Filter property is actually detrimental for implementing this feature. Here’s the deal…
When you set the Filter on a CollectionView (in my case, a ListCollectionView), a bunch of things happen. The basic gist is that the CollectionView experiences a severe earthquake, sends shockwaves to any control bound to its list of items, and, when it’s all done shaking and quaking, you get notified that all Hell broke loose. More specifically, the CollectionView verifies its list of items, sends a message to my ListBox indicating that the whole list was reset, and that causes the ListBox to create all new ListBoxItems for the “new” list of data.
Notice that at no point in time does the CollectionView alert the outside world that the list is about to be reset, before it happens. Also, the ListBox does not have any suitable method/property to override which alerts you before its items are about to be thrown away and recreated (at least none that I could find). The ListBox has a bunch of virtual methods that let you know after the items have changed (such as OnItemsChanged) but that doesn’t help us when we need to animate an item just before it is removed. As far as I can tell, there is no event or virtual method which can be used on either the CollectionView or ListBox which gives you a “heads up” about that.
My make-shift solution (which is available to download toward the top of this article) does not at all use the Filter property of CollectionView. Instead, when it’s time to apply a filter I manually iterate over the ListBox.Items property, get each ListBoxItem, and then fade it away if necessary. This sucks for a variety of reasons:
1) The data filtering should be done at the data source level, not the control level. This is especially important if more than one list control was bound to the same CollectionView, since they should all only show the “filtered in” items. By filtering the items on the control level, you would have to manually filter each bound control whenever the filter changes.
2) The “filtered out” items are still in the ListBox’s Items collection. This is weird because the items are not visible in the ListBox.
3) As mentioned previously, the logic for filtering the items is a Window’s code-behind. This really stinks because it is not at all easy to cleanly reuse elsewhere.
4) Other reasons I have not thought of at the moment…
To be honest with you, I don’t know what the ideal solution to this problem is. I’ve thought about it a lot, and have not yet come up with a really good way to solve this problem. If the WPF team were to add some event which let you know that the list of items is about to change, I don’t know if it would help at all. Since animations are asynchronous, by the time those animations got started the ListBoxItems being animated would be thrown away and new ones created.
The only solution that I’m halfway happy about is putting this logic into a ListBox subclass and exposing three new properties: Filter, ItemsFilteredIn, and ItemsFilteredOut. That approach still suffers from having the filtering logic not on the data source level, but at least it would be encapsulated. If I end up implementing that control, I’ll post an article about it on the CodeProject.
If you have any good ideas/suggestions about how to implement this in a better way, please let me know.