The LongListSelector control is an awesome thing, but using it without LINQ I found a tricky business. Here is what I learned.
[Updated 11/20 to grossly simplify the code]
I am pretty new to Silverlight and to DataBinding, and I was happy when I started a few months ago that I got the a VS Wizard-generated ListBox UI working with an asynchronous web service. However for large lists a simple ListBox isn't usable, so I tried using the LongListSelector (LLS) from the recently released Silverlight Toolkit: I wanted an alphabetic "jump list". I read the sample code, it seemed ok, then I tried adding it to my code. It all went horribly wrong due to my lack of experience in these areas, so then I tried rolling my own version of a jump list, and for asynchronous data that didn't work out well either. Then I found this great article by WindowsPhoneGeek and the fog began to clear.
I took the Group class from that article, I changed my data source from ObservableCollection<MyType> to ObservableCollection<Group<MyType>>, updated the XAML and tried it with a hard-coded list of items to start with. Unlike the Toolkit sample code, and unlike the same code in the article, I could not use LINQ to build my data source as it is asynchronous. Weirdly my code died while adding items to my collection, with this callstack:
Microsoft.Phone.Controls.Toolkit.dll!Microsoft.Phone.Controls.LongListSelector.OnRootCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + 0x182 bytes System.Windows.dll!System.Collections.ObjectModel.ObservableCollection<SampleApp.Group<SampleApp.ItemViewModel>>.OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + 0x1c bytes System.Windows.dll!System.Collections.ObjectModel.ObservableCollection<SampleApp.Group<SampleApp.ItemViewModel>>.InsertItem(int index, SampleApp.Group<SampleApp.ItemViewModel> item) + 0x37 bytes mscorlib.dll!System.Collections.ObjectModel.Collection<SampleApp.Group<SampleApp.ItemViewModel>>.Add(SampleApp.Group<SampleApp.ItemViewModel> item) + 0x28 bytes
and none of that code is mine. This stumped me entirely, until someone on a list at work determined that the data source must also inherit from IList. Not IList<T> but the old-school IList interface. I added that to the Group class, implemented the bare miniumum of methods, and now my test code worked! I rushed to hook it up to my asynchronous population code, and that resulted in an entirely empty list. No crashes, but no data visible at all. Debugging revealed that the LLS was NOT hooked up to the change notification of the collection, which is why as things were added, nothing in the list updated.
This turned out to be because the data source also has to be a member of INotificationCollectionChanged. Once I generated those notifications, the list was populated asynchronously and the LLS worked fabulously. (LINQ really does perform magic, when you realize how much code you have to write when you don't use it). You can see the original code I wrote at the end, which was in the first version of this post. However someone who actually understands this stuff (thanks John!) pointed out to me that I had basically re-implemented ObservableCollection, so the updated code looks like this:
public class Group<T> : ObservableCollection<T>
{
public string Title
get;
set;
}
public bool HasItems
get
return Count != 0;
private set
public Group(string name)
this.Title = name;
For reference here is my original version of the Group class. You'll notice that many methods throw NotImplemented exceptions, I only implemented them as they were hit during development. You may find additional work is required for your scenario (I only need to Clear and Add to my collection), but this should get you up and running.
public class Group<T> : IEnumerable<T>, IList, INotifyCollectionChanged
this.Items = new List<T>();
public Group(string name, IEnumerable<T> items)
this.Items = new List<T>(items);
public override bool Equals(object obj)
Group<T> that = obj as Group<T>;
return (that != null) && (this.Title.Equals(that.Title));
public IList<T> Items
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
return this.Items.GetEnumerator();
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
#region IList Members
public int Add(object value)
if (value is T)
this.Items.Add((T)value);
int index = this.Items.Count - 1;
NotifyCollectionChanged_Add((T)value, index);
return index;
else
throw new InvalidOperationException("Adding wrong type");
public void Clear()
this.Items.Clear();
this.NotifyCollectionChanged_Reset();
public bool Contains(object value)
throw new NotImplementedException();
public int IndexOf(object value)
public void Insert(int index, object value)
public bool IsFixedSize
get { return false; }
public bool IsReadOnly
public void Remove(object value)
public void RemoveAt(int index)
public object this[int index]
return Items[index];
set
public void CopyTo(Array array, int index)
public int Count
get { return Items.Count; }
public bool IsSynchronized
public object SyncRoot
get { return this; }
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void NotifyCollectionChanged_Reset()
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (null != handler)
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
private void NotifyCollectionChanged_Add(T item, int index)
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));