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.


About the Author

Georgi Atanasov


Related Posts

Comments