Everything in Windows Phone 7 is Content and Motion. Both of these key Metro parts are combined with a blazing fast performance and unmatched responsiveness throughout the entire OS. Same must be true for all custom applications – no room for performance compromises. And that is why one of the major focuses of the entire development process of RadControls for WP7 is performance. We are even ready to delay some features for a next release for the sake of being feather-light and as fast as possible.

As I explained in this blog post, from the very beginning we tried to reuse the existing ListBox that comes with the Windows Phone 7 Developer Tools runtime. While the control is perfect for its general purpose – namely to display a certain amount of items within a scrollable box, it should be greatly tweaked to meet the requirements for the WP7 Date/Time picker (as specified in the official guidelines). The major difficulty we met was with the endless looping of a logical range of items within the list. Although we managed to find out a clever and reasonable solution through our Infinite ListBox, the performance was far away from perfect, not even satisfactory. So, we started all over from scratch and RadLoopingPanel was born.

One of the heaviest operations within the Silverlight framework is adding new element(s) in the visual tree. Styles, Templates and Bindings bring huge overhead so a UI virtualization is a must. While the VirtualizingStackPanel is a nice general-purpose virtualization panel it is heavy to customize and its scrolling behavior is based on Measure/Arrange passes rather than a simple render transformation – in this case TranslateTransform. The idea behind RadLoopingPanel’s scrolling is very simple – we have only the bare minimum of visual items created and upon scroll operation we use translate transformations to arrange them in an appropriate way.

In the above screen we can have 2 completely visible items on the screen, so RadLoopingPanel will create a total of 4 visual items – 2 + 1 on top + 1 at bottom 

Since RadLoopingPanel is not a Control, it does not receive manipulation events. Instead it exposes several callbacks such as BeginScroll(), Scroll(double offset) and EndScroll(double velocity) that allow its hosting Control instance to communicate with the panel upon user gestures. It is up to the hosting Control to decide how gestures are treated and what manipulation is actually mapped to a scroll operation. Since it is a “Looping” panel, we can have positive or negative vertical offset. Positive offset is generated when the panel is manipulated downwards and negative one is generated when scrolling upwards. Upon offset change the panel re-evaluates the vertical translate of each item and updates it if needed. Since TranslateTransform changes are managed by the Compositor thread, the achieved User Experience upon scrolling is extremely smooth and slick, without any overhead to the UI thread.

A more interesting part than the visual scrolling is how a logical range of items is displayed by this limited number of visual elements. There are actually two “Wheels” within the panel – one is the visual wheel and the other is the logical one. Typically a logical wheel is larger (in some cases smaller) than the visual one so we need an algorithm that merges these two parts together. The idea is pretty simple – based on the currently accumulated vertical offset (being it positive or negative) we calculate the first top realized logical index. Once we know it we simply increment it downwards for each visual item present. The panel itself is responsible for these two general operations – to perform visual scrolling and to calculate the logical index of each visual item. The mapping of actual data for each item is responsibility of the hosting Control. The panel itself simply raises a callback UpdateVisualItem(VisualItem item, int logicalIndex) and the control itself may display whatever data is needed.

Here is how the UpdateLogicalIndexes method looks like:

private void UpdateLogicalIndexes(bool force)
{
    this.UpdateFirstRealizedLogicalIndex();
 
    int itemCount = this.visualIndexChain.Count;
    int currentLogicalIndex = this.topLogicalIndex;
 
    for (int i = 0; i < itemCount; i++)
    {
        LoopingListItem item = this.Children[this.visualIndexChain[i]] as LoopingListItem;
        if (this.owner != null)
        {
            this.owner.UpdateVisualItem(item, currentLogicalIndex, force);
        }
 
        currentLogicalIndex++;
        if (currentLogicalIndex == this.logicalCount)
        {
            currentLogicalIndex = 0;
        }
    }
}

We store an array of indexes that map the sequence of items indexes as we see it on the screen with the index of each item within the Children collection. In the above screen if we scroll one item downwards, the visualIndexChain array will have the {3, 0, 1, 2} values, which means that this.Children[3] item is translated on the top of the visual wheel.

And here is how we calculate the top realized logical index:

private void UpdateFirstRealizedLogicalIndex()
{
    double logicalWheelHeight = this.logicalCount * this.itemHeight;
    double normalizedOffset = this.visualOffset % logicalWheelHeight;
    int hiddenItems = (int)(Math.Abs(normalizedOffset) / this.itemHeight);
 
    if (normalizedOffset <= 0)
    {
        this.topLogicalIndex = hiddenItems;
    }
    else
    {
        this.topLogicalIndex = this.logicalCount - hiddenItems - 1;
    }
}

The good part is that we can very easily extend the panel with some restrictions like IsLoopingEnabled, MaxVerticalOffset and MinVerticalOffset so that we can get a blazing fast general purpose scrolling panel. Its only limitation is that all visual items should be of equal height. In order to support arbitrary item height, the implementation should be further extended, which however is far from being hard to do. 

Based on RadLoopingPanel, our Date/Time selectors and pickers offer unmatched performance and User Experience. Each operation within the date selector, where we have three wheels, is asynchronous hence you may scroll all of the three wheels simultaneously without any performance hit. In fact we are so fast that one cannot tell which selector is the native WP7 implementation that comes with the OS and which one is Telerik’s.

 

The next interesting part of our “Looping” controls is the RadLoopingListBox, which is actually the Control instance that hosts the RadLoopingPanel. It is responsible for generating the scroll events upon user manipulation and for the data update of each visual item. There are lots of optimizations within the control as well as some tricky parts but I will tell about them in my next article. Stay tuned.


About the Author

Georgi Atanasov


Related Posts

Comments

Comments are disabled in preview mode.