Building a Windows Phone 7 control from the ground up - Part one - the “Infinite ListBox”

Tuesday, August 31, 2010 by Georgi Atanasov | Comments 9

One of the coolest things in every new developer platform is the challenge to master the entire framework in a way that enables you to create high quality, greatly optimized controls that behave in exactly the same way a user would expect to. Yesterday Microsoft announced that more than 300 000 developers have already downloaded the WP7 Developer Tools Beta. Based entirely on Silverlight, phone development has never been easier and more fun than in the Windows Phone 7 environment.

With these series of blogs I want to reveal some tricky moments in developing a simple DatePicker control for WP7. The control should mimic the one described in the public Design Guidelines:

At first sight the control should be pretty easy to implement. We have a line of three buttons aligned vertically; when a button is clicked, we display a ListBox which allows a specific date component (Day, Month or Year) to be chosen. Digging deeper reveals several challenges:

  • The “Year” list should have some kind of data virtualization – obviously we cannot have the entire time line from 1 through 9999 created in-memory (at least not in the context of a mobile device).
  • The “Day” and “Month” lists should be “infinite” – that is to allow a logical range of items to be re-rolled infinitely (as seen from the second screen, the “December” item is preceding the “January” one).

The Solution:

Data Virtualization

The ListBox in WP7 uses the IList interface to communicate with its assigned data source. Only three methods from this interface are used, namely:

  • Indexer (this[index] in C#)
  • Count
  • IndexOf

So, if we provide a custom IList implementation that knows how to implement these three methods we can have our data virtualized. Let’s have a look in this pretty simple virtualized data source:

public class VirtualizedListSource<T> : IList
{
    private int virtualCount;
    public event EventHandler<VirtualizedDataItemEventArgs<T>> DataItemNeeded;
 
    public VirtualizedListSource(int virtualCount)
    {
        this.virtualCount = virtualCount;
    }
 
    /// <summary>
    /// Gets or sets the count of virtual items.
    /// </summary>
    public int VirtualCount
    {
        get
        {
            return this.virtualCount;
        }
        set
        {
            this.virtualCount = value;
        }
    }
 
    /// <summary>
    /// Gets the count of this data source.
    /// </summary>
    public int Count
    {
        get
        {
            return this.virtualCount;
        }
    }
 
    /// <summary>
    /// Gets the data item at the specified index.
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public object this[int index]
    {
        get
        {
            VirtualizedDataItem<T> item = this.GetItemAt(index);
            if (item != null)
            {
                item.Index = index;
                return item;
            }
 
            return default(T);
        }
        set
        {
            throw new NotImplementedException();
        }
    }
 
    public int IndexOf(object value)
    {
        VirtualizedDataItem<T> item = value as VirtualizedDataItem<T>;
        Debug.Assert(item != null, "VirtualizedDataItem expected.");
        if (item != null)
        {
            return item.Index;
        }
 
        return -1;
    }
 
    protected virtual VirtualizedDataItem<T> CreateDataItem(int index)
    {
        VirtualizedDataItem<T> item = null;
 
        EventHandler<VirtualizedDataItemEventArgs<T>> eh = this.DataItemNeeded;
        if (eh != null)
        {
            VirtualizedDataItemEventArgs<T> args = new VirtualizedDataItemEventArgs<T>(null);
            eh(this, args);
            item = args.Item;
        }
 
        if(item == null)
        {
            item = this.CreateDataItemInstance();
        }
 
        return item;
    }
 
    protected virtual VirtualizedDataItem<T> CreateDataItemInstance()
    {
        return new VirtualizedDataItem<T>(default(T));
    }
 
    protected virtual VirtualizedDataItem<T> GetItemAt(int index)
    {
        return this.CreateDataItem(index);
    }
}

I have intentionally skipped the rest IList implementation as all other methods are not needed and they are not the focus of this post. Whenever an item at a specified index is requested, we raise the DataItemNeeded event, and then check whether a user-defined item is supplied and if not we create a default item instance. Using the provided implementation we may easily create a data source for the “Year” ListBox:

public class YearsDataSource : VirtualizedListSource<DateTime>
{
    public YearsDataSource()
        : base(9999)
    {
    }
 
    protected override VirtualizedDataItem<DateTime> GetItemAt(int index)
    {
        //consider that lists are zero-based
        index++;
        return new VirtualizedDataItem<DateTime>(new DateTime(index, 1, 1));
    }
}

An important thing to note here is the fact that we need an ItemTemplate specified so that the ListBox uses UI virtualization. Otherwise the entire exercise will be useless.

Infinite ListBox

Now that we have our virtualized data we can come up with a very elegant and simple implementation of the “infinite ListBox” feature. The idea is that we can specify a fairly large virtual item count and a number of logical items (for example the “Month” ListBox will have 12). We can then override the GetItemAt method of our VirtualizedListSource class, translate the desired index within the range of logical items and return the logical item at that index. Here is the sample data source:

public class WheelDataSource<T> : VirtualizedListSource<T>
{
    private List<VirtualizedDataItem<T>> logicalItems;
 
    public WheelDataSource(IEnumerable<T> items)
    {
        this.logicalItems = new List<VirtualizedDataItem<T>>(items)
    }
 
    protected override VirtualizedDataItem<T> GetItemAt(int index)
    {
        return this.logicalItems[index % this.logicalItems.Count];
    }
}

 

Having let’s say 1 million virtual items and scrolling the ListBox to the middle will give the user the perception of an endless looping ListBox – he can scroll up and down by a total of 500 000 items in each direction.

I want to dig a bit deeper here. One may wonder why I am not using my own custom panel implementation that applies UI virtualization rather than playing with the data source of the ListBox. There are some limitations in the underlying framework that implicitly force you to use only primitive controls that are already present. One limitation is the sealed classes. For example you may not create your own custom ScrollViewer implementation by simply deriving from the base one as it is marked with the “sealed” keyword. So you have two options here: a) Use the built-in one and b) Create your own one entirely from scratch, deriving from ContentControl. While creating your own implementation always has its pros, it also has its cons, especially in the context of Windows Phone. Consider things like handling manipulation events, multi-touch, animations and scrolling as well as internal interfaces that update some other internal stuff which in turn updates more internal properties and calls internal methods. Put simply creating a custom data-source that exploits the built-in ListBox seemed the easiest and simplest solution (especially as a start-up :)).

Well, that’s all for now. In the next post I will cover creating a DateListBox which has a SelectedDate property and may be used to select a Date component – Day, Month or Year. Stay tuned.

9 Comments

  • ManniAT 31 Aug 2010
    Hi Georgi,
    thank you for this nice post.
    I guess this is part of your work on the upcoming "telerik wp7 controls" :)

    Just kidding - but on the other hand, it would fit perfect in your (telerik's) product suite!
    Maybe (I hope so) we'll see something for WP7 in the (near) future!

    Manfred
  • volumerates 01 Sep 2010
    Thank you for this nice post.It's so cool.
  • CoDr 03 Sep 2010
    Do you have a copy of the design guidelines that had those in it? I saw them yesterday, but when I looked at that pdf today, those images of the date and time controls seem to be no longer there.
  • Georgi 03 Sep 2010
    Hi guys,

    Thank you for your comments. I really hope that the post was an interesting reading.

    @CoDr: You can get the whole package, including templates by following this link:

    http://go.microsoft.com/fwlink/?LinkId=196225
  • mark 18 Oct 2010
    I've heard great things about the Windows 7 phone. I hear it beats Android in some ways! Is there a way that a business phone system will be developed that will make VoIP a real possibility?

    Just wondering ;)

    Mark, CA
  • David C. 18 Oct 2010
    Yes Mark, there is! There is a business phone system I like best! That's a good question, and I ask the same thing all the time.

    Cheers!
  • Filipe 16 Feb 2011
    Can we add images to InfiniteListBox passing Uri as parameter?
  • neoncomp 14 Nov 2011
    Good article.... Thanks
  • robin 29 Feb 2012
      Hereis the sample data source: VirtualizedListSource class, translate the desired index within therange of logical items and return the logical item at that index. <a href="http://www.candmdomesticappliances.co.uk/">Cooker Spares</a>

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)