You can go straight at source to explore this yourself from my skydrive BingImageSearch

 This kind of application is pretty common:

Read on to see how I attempted to make this list scroll faster.

I started experimenting with list scrolling for images fetched from Bing, the ImageSearch Apis; Bing Image Search Sample.

Key things to note in the sample

  • Web requests were made using   HttpWebRequest and is the prefered(performant) way of making requests as opposed to WebClient.  When a request is made via WebClient, even though on the background thread, the response will be returned on the UI thread. This is a bug in the platform which we will address over time. As we know from the performance document, leaving the UI thread for input and drawing is the most efficient approach. Httpwebrequest does exactly that. Requests made via httpwebrequest return on the background thread as expected.
  • The Webrequests are made in the background thread and UI thread is marshalled only when items are ready to be drawn, this is done using                           

                             Deployment.Current.Dispatcher.BeginInvoke(() =>

                             {

                                 // copy the list of bingimages over

                                 this.Items.Add(myItem);                                

 

                             });

  • Add items to the UI thread in batches every some miliseconds (example add 2 items every 20ms). This keeps the UI thread free for input from the user, avoiding stutters and delays, giving the perception of performance. This is much better than adding all 20 items in one go, drawing 20 items straight on the UI thread will keep it busy and wont take in the input from user, example the flick gesture for scroll.

             So this logic was added                          

    int sleepcounter = 2;

                         int itemcounter = 0;

                         foreach (ItemViewModel item in bingImageList)

                         {

                             itemcounter++;

                             if (itemcounter % sleepcounter == 0)

                             {

                                 System.Threading.Thread.Sleep(20);

                             }

                             //add a dummy item

                             var myItem = item;

 

                             // copy the data over

                             Deployment.Current.Dispatcher.BeginInvoke(() =>

                             {

                                 // copy the list of bingimages over

                                 this.Items.Add(myItem);

                                

 

                             });

 

                         }

 

  • Then, I had the Image source bound to the URL. This was causing delay in the load, and scroll wasnt as smooth because of image coming in at their own pace. It was recommended to download these images and make it part of the item before adding item to the list. So I added the preload image logic LoadThumbNailImage() again using HttpWebRequest

             HttpWebRequest downloadthumbnailrequest = (HttpWebRequest)thumbnailState.AsyncRequest; 

            // end the async request

            thumbnailState.AsyncResponse = (HttpWebResponse)downloadthumbnailrequest.EndGetResponse(asyncResult);                     

           

            Stream imageStream = thumbnailState.AsyncResponse.GetResponseStream();

            byte[] b = new byte[this.ThumbNailSize];

            imageStream.Read(b, 0, this.ThumbNailSize);

            imageStream.Close();

 

            MemoryStream ms = new MemoryStream(b);

 

            Deployment.Current.Dispatcher.BeginInvoke(() =>

            {

                BitmapImage bmp = new BitmapImage();

                bmp.SetSource(ms);

                // copy the list of bingimages over

                this.ThumbNailImage = bmp;

            });

             

  • Then I learnt about the memory management APIs, so I decided to use those as my cut off method, when app's memory usage is more than 0.33 of device memory, I bail out and dont make any more calls to bing image search. You should use these to keep track of your device memory usage. Use this in combination with FrameRateCounter for texture memory if you want to deep dive. Run it a few times to ensure Garbage Collection has been kicked, or enforce GC.

            long deviceTotalMemory = (long)DeviceExtendedProperties.GetValue("DeviceTotalMemory");

            long applicationCurrentMemoryUsage = (long)DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage");

            long applicationPeakMemoryUsage = (long)DeviceExtendedProperties.GetValue("ApplicationPeakMemoryUsage");

            if((((PAGECOUNT*this._currentoffset)+PAGECOUNT)>this.TotalCount)||(applicationCurrentMemoryUsage>(deviceTotalMemory*0.33)) )

            {

                return false;

            }

   

 NOTE: Using device memory APIs when submitting to MarketPlace is not a good practice, you might be flagged. Use it only for diagnostic purposes.      

 

  • Then I added the logic to find scroll, detect end of scroll and make more network calls,         

 

        private ScrollViewer FindScrollViewerRecursive(UIElement element)

        {           

            int childCount = VisualTreeHelper.GetChildrenCount(element);

            for (int i = 0; i < childCount; i++)

            {

                if (VisualTreeHelper.GetChild(element, i) as ScrollViewer != null)

                {

                    return (VisualTreeHelper.GetChild(element, i) as ScrollViewer);                   

                }

                else

                    FindScrollViewerRecursive(VisualTreeHelper.GetChild(element, i) as UIElement);

            }

            return null;

        }              

 

  • Used the High Performance Progress Bar from Jeff's blog.

  • Finally, I implemented the double tap gestures, to launch the browser. Using the Manipulation events is the recommended and performant way to implementing this as opposed to Mouse Events or TouchPanel. ManipulationEvents  maintain less data about the touch points and for a simple scenario like double tap, using manipulation events is not a big deal. Wheverever possible, use manipulation events.

This implementation takes benefits of the List's Virtualized Stack Panel to recycle UI containers. There are other solutions that might suit your application like David Anson's solution which removed the VSP and does deferred loading of images, he talks about it in Delay's Blog

Feel free to ask questions, I will get answers to your questions from the team and your feedback will help improve the platform.

Also, any feedback on the blog/article is appreciated. It will help improve how I present the information to you guys.