The LongListSelector is great for long lists in Windows Phone 7, but getting your data into the right format can be tricky and confusing (certainly took me a while to work out what to do!). Especially if you want to use ‘pure’ binding to an ObservableCollection.
The answer that I found easiest to understand and follow was to use ‘nested Observable collections’. This also ensure that things are kept up-to-date in the UI without additional coding.
To help me understand what was need I broke it down into some simple stages. I am sure it can be done with a single LINQ statement.
Let say you have a list of contacts that you would like to display with the typical A-Z short cuts as shown here. This includes showing group titles that contain no child records too!
Create the basic object – Contacts
We’ll start with the simple Contact class that has two 3 properties. One is derived to create the key value. This can be any value, but here it’s the first letter of the Name value in lower case. If your data is changing then you should implement the INotifyPropertyChanged interface. I have left it out here for simplicity.
namespace PhoneTester1.Model { public class Contact { public string Name { get; set; } public string JobTitle { get; set; } private string _NameKey; /// <summary> /// First letter of the Name. Used for grouping in the long list selector /// </summary> public string NameKey { get { //If not set then if (_NameKey == null) { //get the first letter of the Name char key = char.ToLower(this.Name[0]); if (key < 'a' || key > 'z') { key = '#'; } _NameKey = key.ToString(); } return _NameKey; } } } }
Create the Group Observable Collection
Then we’ll create a Group class. I have called it GroupOC for Grouping Observable Collections.
using System.Collections.ObjectModel; namespace PhoneTester1.Model { public class GroupedOC<T>:ObservableCollection<T> { /// <summary> /// The Group Title /// </summary> public string Title { get; set; } /// <summary> /// Constructor ensure that a Group Title is included /// </summary> /// <param name="name">string to be used as the Group Title</param> public GroupedOC(string name) { this.Title = name; } /// <summary> /// Returns true if the group has a count more than zero /// </summary> public bool HasItems { get { return (Count != 0); } private set { } } } }
Create the Helper method to Group the Contacts
I created a static method called CreateGroupedOC in a static Collection Helper class called CollectionHelper. This can then be called on to group everything up.
using System.Collections.ObjectModel; using System.Linq; using PhoneTester1.Model; namespace PhoneTester1.Functions { public static class CollectionHelpers { /// <summary> /// Groups a passed Contacts ObservableCollection /// </summary> /// <param name="InitialContactsList">Unordered collection of Contacts</param> /// <returns>Grouped Observable Collection of Contacts suitable for the LongListSelector</returns> public static ObservableCollection<GroupedOC<Contact>> CreateGroupedOC(ObservableCollection<Contact> InitialContactsList) { //Initialise the Grouped OC to populate and return ObservableCollection<GroupedOC<Contact>> GroupedContacts = new ObservableCollection<GroupedOC<Contact>>(); //first sort our contacts collection into a temp List using LINQ var SortedList = (from con in InitialContactsList orderby con.Name select con).ToList(); //Now enumerate throw the alphabet creating empty groups objects //This ensure that the whole alphabet exists even if we never populate them string Alpha = "#abcdefghijklmnopqrstuvwxyz"; foreach (char c in Alpha) { //Create GroupedOC for given letter GroupedOC<Contact> thisGOC = new GroupedOC<Contact>(c.ToString()); //Create a temp list with the appropriate Contacts that have this NameKey var SubsetOfCons = (from con in SortedList where con.NameKey == c.ToString() select con).ToList<Contact>(); //Populate the GroupedOC foreach (Contact csm in SubsetOfCons) { thisGOC.Add(csm); } //Add this GroupedOC to the observable collection that is being returned // and the LongListSelector can be bound to. GroupedContacts.Add(thisGOC); } return GroupedContacts; } } }
Tie it together in your ViewModel
In my projects I am usually populating a collection of objects either from a database or web service. Here we’ll start with a collection of Contacts that is populated in a random order. This ObservableCollection we would pass to the method shown above (CreateGroupedOC ) to group it and return a grouped ObservableCollection.. This can then be referenced in your ViewModel and bound to the LongListSelector control.
I also use the very handy MVVM Light Toolkit produced by the very excellent Laurent Bugnion of GalaSoft. This you can find at http://mvvmlight.codeplex.com/ and some info about it at http://www.galasoft.ch/mvvm/getstarted/. I strongly recommend using it.
I have included my whole ViewModel below. Hopefully the comments make it self explanatory!
using System; using System.Collections.ObjectModel; using System.Text; using GalaSoft.MvvmLight; using PhoneTester1.Functions; using PhoneTester1.Model; namespace PhoneTester1.ViewModel { /// <summary> /// This class contains properties that the main View can data bind to. /// You can also use Blend to data bind with the tool's support. /// This code is provided as is by Hard Medium Soft Ltd. /// see us at http://www.hardmediumsoft.com /// </summary> public class MainViewModel : ViewModelBase { public string ApplicationTitle { get { return "Nicholas Rogoff for Hard Medium Soft Ltd."; } } public string PageName { get { return "Tester App"; } } /// <summary> /// The <see cref="GroupedContacts" /> property's name. /// </summary> public const string GroupedContactsPropertyName = "GroupedContacts"; private ObservableCollection<GroupedOC<Contact>> _GroupedContacts = new ObservableCollection<GroupedOC<Contact>>(); /// <summary> /// Gets the GroupedContacts property. /// /// Changes to that property's value raise the PropertyChanged event. /// This property's value is broadcasted by the Messenger's default instance when it changes. /// </summary> public ObservableCollection<GroupedOC<Contact>> GroupedContacts { get { return _GroupedContacts; } set { if (_GroupedContacts == value) { return; } var oldValue = _GroupedContacts; _GroupedContacts = value; // Update bindings, no broadcast RaisePropertyChanged(GroupedContactsPropertyName); } } /// <summary> /// Initializes a new instance of the MainViewModel class. /// </summary> public MainViewModel() { //Populate some Contacts with random strings ObservableCollection<Contact> tempContactsOC = new ObservableCollection<Contact>(); for (int i = 0; i < 50; i++) { Contact tempContact=new Contact(){Name = RandomString(12,false,i) + " - " + i, JobTitle=RandomString(18,false,i)}; tempContactsOC.Add(tempContact); } //Create our Nested ObservableCollection GroupedContacts = CollectionHelpers.CreateGroupedOC(tempContactsOC); } #region Random String generator /// <summary> /// Generates a random string with the given length /// </summary> /// <param name="size">Size of the string</param> /// <param name="lowerCase">If true, generate lowercase string</param> /// <returns>Random string</returns> private string RandomString(int size, bool lowerCase, int seed) { StringBuilder builder = new StringBuilder(); Random random = new Random((int)DateTime.Now.Ticks * seed); char ch; for (int i = 0; i < size; i++) { ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); builder.Append(ch); } if (lowerCase) return builder.ToString().ToLower(); return builder.ToString(); } #endregion } }
If you think you can make this simpler or better please let me know. Thanks.
Thank you very much for this article. I need to create a LongListSelector with binding to an Observeable collection and not to LINQ much like you have done so that I can use binding to update the data in the LongListSelector. However, I am still a little bit confused about how this relates to the XAML. Could you add the XAML for this to your article?
Thank you,
Richard
I will put up the XAML side in another blog shortly for you. Hopefully that will complete the picture.
Nicholas
i agree with Richard – it’s a very useful article but for anyone starting out it’s difficult to know how to wire it up to the LongListSelector in the xaml. Also, I wish you hadn’t user the MVVM lightweight framework as that means, to use your code, i have to change my whole design to implement that, or else work out what it does in certain methods…. which I guess won’t hurt me to do anyway.
But anyway, I shouldn’t complain as this is still very useful – so thanks again.
I will be posting a blog with the XAML soon to clarify things. If your not using MVVM Light then I strongly suggest it as it’s a real time saver.
Nicholas
And one more question, can this stuff be used when a new Contanct is to add, and without additional code?
Yes. Because everything is based on Observable collections, if you just add a record to a collection, in code behind in the normal way, this will bubble up to the UI automatically. If the binding is two-way in the XAML then it should also be possible to do it on the UI side too, but I have not tried this or thought about it long…so would be interesting to test.
Can you post the XAML for this example?
Can you please post the rest of your code or perhaps the project? I’m struggling to get this working.
Thank you for your post! Please post a xaml part of code. Thanks in advance.
I edited the helper to my needs, I really don’t need the the entire alphabet so I rewrote some of the lines to instead use the Category name.
The problem was, if I edited an item on my listbox, it does not automatically update the item selected.
INotifyPropertyChanged is implemented and all but can’t seem to work.
ok goin back to say, I was repopulating my listbox after I made an edit. But am not sure why it’s not reflecting my changes on the listbox. But anyway, it worked when I iterate my way into the collection and update the values of my objects.
so just to share my update on your helper
public static ObservableCollection<GroupedOC> CreateGroupedOC(ObservableCollection InitialContactsList)
{
//Initialise the Grouped OC to populate and return
ObservableCollection<GroupedOC> GroupedContacts = new ObservableCollection<GroupedOC>();
List cats = new List();
foreach (Model_Passwords pass in InitialContactsList)
{
if (!cats.Contains(pass.CategoryName))
{
cats.Add(pass.CategoryName);
}
}
foreach (var cat in cats)
{
////Create GroupedOC for given category name
GroupedOC thisGOC = new GroupedOC(cat);
////Create a temp list with the appropriate vault items that have this category name
var SubsetOfCons = (from con in InitialContactsList
where con.CategoryName == cat
select con).ToList();
////Populate the GroupedOC
foreach (Model_Passwords csm in SubsetOfCons)
{
thisGOC.Add(csm);
}
//Add this GroupedOC to the observable collection that is being returned
// and the LongListSelector can be bound to.
GroupedContacts.Add(thisGOC);
}
return GroupedContacts;
}
This has helped me so much. Thank you very much!
Great sample code, well explained. Thank you.
How do you Add/Delete an item from the Observable<GroupedOC> GroupedContacts?