I spent a long time searching the internet to the solution for this problem, and basically ended up stumbling on the solution.
What you want to do is bind your listbox to an ICollectionView. Then make sure that you DON'T have IsSynchronizedWithCurrentItem set to false.
Bad, won't work
IsSynchronizedWithCurrentItem="False"
This is the Silverlight default, don't waste your time typing it out
IsSynchronizedWithCurrentItem="{x:Null}"
This will throw an error at runtime, and I believe is the same as {x:null} anyways
IsSynchronizedWithCurrentItem="True"
ICollectionView has a method called MoveCurrentToFirst. That name seems a bit ambiguous, but it does actually move the CurrentItem pointer to the first item (I initally thought it reordered the collection by moving whatever item you had selected to the first item position). The IsSynchronizedWithCurrentItem property allows Listbox (or any control implementing Selector) to work magic with ICollectionViews. In your code you can call ICollectioView.CurrentItem instead of whatever you bound Listbox.SelectedItem to get the currently selected item.
Here is how I make my ICollectionView available to my view (I'm using MVVM) :
public System.ComponentModel.ICollectionView NonModifierPricesView
{
get
{
if (_NonModifierPricesView == null)
{
_NonModifierPricesView = AutoRefreshCollectionViewSourceFactory.Create(x => ((MenuItemPrice)x).PriceType == MenuItemPrice.PriceTypes.NonModifier);
_NonModifierPricesView.Source = Prices;
_NonModifierPricesView.ApplyFilter(x => ((MenuItemPrice)x).DTO.Active == true);
}
ICollectionView v = _NonModifierPricesView.View;
v.MoveCurrentToFirst();
return v;
}
}
Now, as you want to bind to an observable collection, you cannot use the default CollectionViewSource as it is not aware of updates to the source collection. You probably noticed that I am using a custom CVS implementation called AutoRefreshCollectionViewSource. If memory serves, I found the code online and modified it for my own uses. I've added extra functionality for filtering, so could probably clean these classes up even more.
Here is my version of the code :
AutoRefreshCollectionViewSource.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Data;
public class AutoRefreshCollectionViewSource : System.Windows.Data.CollectionViewSource
{
// A delegate for launching a refresh of the view at a different priority.
private delegate void NoArgDelegate();
private Predicate<object> MyFilter; // this is the filter we put on when we do ApplyFilter
private Predicate<object> BaseFilter; // this is the filter that is applied always
public AutoRefreshCollectionViewSource(Predicate<object> _baseFilter) : base()
{
BaseFilter = _baseFilter;
if (BaseFilter == null)
BaseFilter = x => true;
}
/// <summary>
/// A collection containing all objects whose event handlers have been
/// subscribed to.
/// </summary>
private List<INotifyPropertyChanged> colSubscribedItems = new List<INotifyPropertyChanged>();
// We must override the OnSourceChanged event so that we can subscribe
// to the objects in the new collection (and unsubscribe from the old items).
protected override void OnSourceChanged(object oldSource, object newSource)
{
// Unsubscribe from the old source.
if (oldSource != null)
SubscribeSourceEvents(oldSource, true);
// Subscribe to the new source.
if (newSource != null)
SubscribeSourceEvents(newSource, false);
base.OnSourceChanged(oldSource, newSource);
}
/// <summary>
/// Adds or Removes EventHandlers to each item in the source collection as well as the
/// collection itself (if supported).
/// </summary>
/// <param name="source">The collection to (un)subscribe to and whose objects should be (un)subscribed.</param>
/// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
private void SubscribeSourceEvents(object source, bool remove)
{
// Make sure the source is not nothing.
// This may occur when setting up or tearing down this object.
if (source != null)
if (source is INotifyCollectionChanged)
// We are (un)subscribing to a specialized collection, it supports the INotifyCollectionChanged event.
// (Un)subscribe to the event.
if (remove)
((INotifyCollectionChanged)source).CollectionChanged -= Handle_INotifyCollectionChanged;
else
((INotifyCollectionChanged)source).CollectionChanged += Handle_INotifyCollectionChanged;
if (remove)
// We are unsubscribing so unsubscribe from each object in the collection.
UnsubscribeAllItemEvents();
else
// We are subscribing so subscribe to each object in the collection.
SubscribeItemsEvents((IEnumerable)source, false);
}
/// <summary>
/// Unsubscribes the NotifyPropertyChanged events from all objects
/// that have been subscribed to.
/// </summary>
private void UnsubscribeAllItemEvents()
{
while (colSubscribedItems.Count > 0)
SubscribeItemEvents(colSubscribedItems[0], true);
}
/// <summary>
/// Subscribes or unsubscribes to the NotifyPropertyChanged event of all items
/// in the supplied IEnumerable.
/// </summary>
/// <param name="items">The IEnumerable containing the items to (un)subscribe to/from.</param>
/// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
private void SubscribeItemsEvents(IEnumerable items, bool remove)
{
foreach (object item in items)
SubscribeItemEvents(item, remove);
}
/// <summary>
/// Subscribes or unsubscribes to the NotifyPropertyChanged event if the supplied
/// object supports it.
/// </summary>
/// <param name="item">The object to (un)subscribe to/from.</param>
/// <param name="remove">Whether or not to subscribe or unsubscribe.</param>
private void SubscribeItemEvents(object item, bool remove)
{
if (item is INotifyPropertyChanged)
// We only subscribe of the object supports INotifyPropertyChanged.
if (remove)
{
// Unsubscribe.
((INotifyPropertyChanged)item).PropertyChanged -= Item_PropertyChanged;
colSubscribedItems.Remove((INotifyPropertyChanged)item);
}
else
{
// Subscribe.
((INotifyPropertyChanged)item).PropertyChanged += Item_PropertyChanged;
colSubscribedItems.Add((INotifyPropertyChanged)item);
}
}
/// <summary>
/// Handles a property changed event from an item that supports INotifyPropertyChanged.
/// </summary>
/// <param name="sender">The object that raised the event.</param>
/// <param name="e">The event arguments associated with the event.</param>
/// <remarks></remarks>
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// By default, we do not need to refresh.
bool refresh = false;
if (e.PropertyName == "Active" || e.PropertyName == "DTO.Active")
refresh = true;
if (refresh)
// Call the refresh.
// Notice that the dispatcher will make the call to Refresh the view. If the dispatcher is not used,
// there is a possibility for a StackOverFlow to result.
this.Dispatcher.BeginInvoke(new NoArgDelegate(this.View.Refresh), null);
}
/// <summary>
/// Handles the INotifyCollectionChanged event if the subscribed source supports it.
/// </summary>
private void Handle_INotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SubscribeItemsEvents(e.NewItems, false);
break;
case NotifyCollectionChangedAction.Remove:
SubscribeItemsEvents(e.OldItems, true);
break;
case NotifyCollectionChangedAction.Replace:
SubscribeItemsEvents(e.OldItems, true);
SubscribeItemsEvents(e.NewItems, false);
break;
case NotifyCollectionChangedAction.Reset:
UnsubscribeAllItemEvents();
SubscribeItemsEvents((IEnumerable)sender, false);
break;
}
}
public void ApplyFilter(Predicate<object> f)
{
if (f != null)
MyFilter = f;
this.View.Filter = x => MyFilter(x) && BaseFilter(x);
this.View.Refresh();
}
public void RemoveFilter()
{
this.View.Filter = BaseFilter;
this.View.Refresh();
}
}
AutoRefreshCollectionViewSourceFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class AutoRefreshCollectionViewSourceFactory
{
private static List<AutoRefreshCollectionViewSource> Collections;
public static AutoRefreshCollectionViewSource Create()
{
if (Collections == null)
Collections = new List<AutoRefreshCollectionViewSource>();
AutoRefreshCollectionViewSource cvs = new AutoRefreshCollectionViewSource(null);
Collections.Add(cvs);
return cvs;
}
public static AutoRefreshCollectionViewSource Create(Predicate<object> p)
{
if (Collections == null)
Collections = new List<AutoRefreshCollectionViewSource>();
AutoRefreshCollectionViewSource cvs = new AutoRefreshCollectionViewSource(p);
Collections.Add(cvs);
return cvs;
}
public static void ApplyFilterOnCollections()
{
foreach (AutoRefreshCollectionViewSource cvs in Collections)
cvs.ApplyFilter(null);
}
public static void RemoveFilterFromCollections()
{
foreach (AutoRefreshCollectionViewSource cvs in Collections)
cvs.RemoveFilter();
}
public static void CleanUp()
{
Collections = null;
}
}