Animated Filtering of ListBoxItems

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.

16 Responses to Animated Filtering of ListBoxItems

  1. Josh, perhaps you could apply the filter after you fade the items? That way you will use the filter and still have the nice fade out for the items that are filtered. Perhaps returning items to the normal state after you apply the filter might be necessary depending on how you implement things…

  2. SerialSeb says:

    Josh,

    have you considered simply implementing your own ICollectionView, and rather than do a NotifyCollectionChanged.CollectionChanged from the refresh with a Reset, do a diff and notify with NewItems and OldItems? It seems to me that would actually work wonders.

    You could probably also use an attached property to hook up on any ItemsControl and that would inject a fake collection in between the original ItemsControl and its ItemsSource, detect the changes, do a diff, then get the list of all the itemscontainers, fade them out manually, and finally propagate the change afterwards?

    I think the second solution is much more general and much more in a composition spirit. Am not a big fan of control inheritance 🙂

  3. Josh Smith says:

    Denis,

    The problem with using the Filter property *at all* is that it raises the CollectionChanged event and sets e.Action=Refresh. That causes all of the ListBoxItems to be thrown away and recreated, so I’d end up performing two rounds of hiding the filtered out items (once before the Reset, once after).

    In addition to that, I’d still be doing control-level filtering. I’d rather filter on the data source so that all bound controls honor the filter automatically (and fade their items).

    Thanks for the feedback, though.

    Josh

  4. Josh Smith says:

    SerialSeb,

    I had been toying with the idea of subclassing ListCollectionView and somehow updating the bound controls in a “gentle” manner when the filter changed, but your approach sounds much better! That might just do the trick…

    I like your second solution too, but it seems like the kind of code that would be wraught with bugs. If someone puts a shotgun to my head and tells me to write it, then I’ll go that route. Otherwise I’d rather try something simpler! 🙂

    Thanks,
    Josh

  5. Nick says:

    “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.”

    I don’t know if I agree with this. Maybe there should be a filter feature on both the control collection and the data collection. There might be times when you want your collection to be filtered in one place an not another. I can think of a few off the top of my head but a real world example might be…Times Reader. If you’re searching for a specific article you don’t want all the other articles/sections to disappear in the menu at the top of the app. I only mention this because I had this exact conversation with somebody earlier today. Creepy!

  6. Nick says:

    Follow up:

    I do agree though that there are lots of times you want the filter done on the data collection level.

  7. Josh Smith says:

    Nick,

    I stand by my original statement. If you are in a situation where you want two controls to be bound to the same data source, but have one show a filtered view and one not, you should bind to two CollectionView objects. The control which needs to display a filtered view would have a filter added to its CollectionView. The control which does not need to show a filtered view would then not be affected by the filtration.

    Josh

  8. Nick says:

    My bad. I had originally misread, “This is especially important if more than one list control was bound to the same CollectionView” as “bound to the same DataCollection”. Big difference in my brain. I see what you’re really saying and I concur…or agree…or humbly throw myself to the ground when faced with such wisdom. Pick one.

  9. Have you considered solving the problem at the Panel level?

    You could build a Panel that notices when children are removed, and have it do the work. The worse of two hacks here would be to keep the thing hanging around as a visual child until the animation completes even when it’s still in the Children collection. The lesser of two hacks would be to grab a VisualBrush of the thing so you can let it leave the tree but still paint with it.

    However, it’s probably easier not to use Filter at all. Have a view where all the items are nominally present, but use a trigger in a data template to hide/unhide things when they are filtered.

  10. John Stewien says:

    What came to my mind was something along the lines of a ListBoxItem template that fades in and out, and where all the ListBoxItems are dynamically bound to a converter for the ListBoxItem to be visible or not.

  11. Josh Smith says:

    Ian,

    I like your idea of using a Panel to provide the fading effect, but I agree that to make it work would inevitably involve some hackery. I’m starting to get the feeling that the WPF architects didn’t build the platform in such a way that this effect is easy to achieve. I’m going to investigate the Panel route more.

    I don’t like the DataTemplate approach because that’s not a great means of creating reusable code, imo. The ListBox should provide the fading effect, not the template for the items, so that any ItemTemplate can be used in concert with the fading effect.

    Thanks,
    Josh

  12. Josh Smith says:

    John,

    Thanks for the feedback. I would prefer to keep the animation logic in the ListBox (or perhaps ListBoxItem), not in the DataTemplate used to render an item. Putting reusable logic in a DataTemplate smells funny to me, because DataTemplates are very often not reusable “as-is.”

    I can see the merit of your approach, but want to create something more general and reusable. The quest continues… 🙂

    Thanks,
    Josh

  13. Martin Cook says:

    Hey Josh,

    Just wondered if you got any further with this project or if it was scrapped?

    Thanks,
    Martin

  14. Josh Smith says:

    Martin,

    I never took this idea any further.

    Josh

  15. sirte2002 says:

    Hi! I have a problem, I don’t know how to make a fade in/out effect to a window. Can you tell me or sent a cod example? thanks!

  16. sirte2002 says:

    excuse me 😛 I forget the effect I don’t know how to do is in WPF