Using LongListSelector without LINQ
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;
}
}
Original Code
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
{
public Group(string name)
{
this.Title = name;
this.Items = new List<T>();
}
public Group(string name, IEnumerable<T> items)
{
this.Title = name;
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 string Title
{
get;
set;
}
public IList<T> Items
{
get;
set;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
#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)
{
throw new NotImplementedException();
}
public void Insert(int index, object value)
{
throw new NotImplementedException();
}
public bool IsFixedSize
{
get { return false; }
}
public bool IsReadOnly
{
get { return false; }
}
public void Remove(object value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public object this[int index]
{
get
{
return Items[index];
}
set
{
throw new NotImplementedException();
}
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public int Count
{
get { return Items.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { return this; }
}
#endregion
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)
{
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (null != handler)
{
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
}
}
Comments
Anonymous
November 26, 2010
So how do you add something to your observable collection? Given this ObservableCollection<Group<MyType>> I presume you have nested lists?Anonymous
December 07, 2010
CK: I do, I have a Group for each letter of the alphabet, I build the list of groups to start with, then I add items to each group as I enumerate them.Anonymous
December 21, 2010
Hi, and if I have a function that populate a data source (ObservableCollection object) async?? Is, the best practice, implement the CollectionChanged event of the list and then for each item added to that list "reassigning" that to the right group?? Thanks.Anonymous
December 27, 2010
Could you post a complete code sample with the hardcoded initialization of the data and the different classes needed ... thxAnonymous
January 11, 2011
Joris: I'll see if I can extract enough real source to be useful to post.Anonymous
January 17, 2011
Mones, I can't recommend a "best practice" for async population. I have a version that works, but I'm not comfortable enough with XAML/SL to post it publicly, sorry.Anonymous
February 21, 2011
The comment has been removedAnonymous
December 01, 2013
where can i found sample code. can u give link or send me to this mail please it's urgent . mail id : mail2mallig@gmail.com thanks