(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows 8 Metro: Improve GridView and ListView with SemanticZoom and Incremental Loading

(7 votes)
Andrea Boschin
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
4 comments   /   posted on Jul 03, 2012
Categories:   Windows 8 , General

In the previous article from this series I've explored the main controls that support the new user experience in metro style applications. GridView, ListView and FlipView are a big help to fully embrace the metro-styled experience and having a deep knowledge about their features is important to develop effective applications. In this number I would want to go for two advanced features I've skipped in the introduction. I'm referring to Semantic Zoom, that is the ability to coordinate the content of two controls to easily explore a collection, and to Incremental Loading that let you load items in chunks when they really need.

Introducing Semantic Zoom

When you think at zooming something, you probably recall to your mind how you zoom a map in google or bing, or probably how you zoom an image or a document.

These kinds of zoom go under the name of "optical" zoom, because you are enlarging your optical view of an element as you were using a lens. If you think at a GridView, or also at a ListView, you easily understand that optical zoom cannot be applied just because it does have a real importance. What you can try to zoom instead, are the groups you defined for your view. In the case you have a number of items in your grid it may be difficult to scroll and scroll just to reach the item you are searching for, but probably you know what is the group it belongs to, so you can zoom out to the groups, select the group and then zoom in again to the items in the group. This is the so-called semantic zoom, a zoom that does not imply an optical enlargement but that is based on the semantic of your grouping.

Metro style apps introduce a new control that is able to coordinate a pair of other controls that supports the ISemanticZoomInformation interface. This interface is supported by both the ListView and GridView so you can use your favorite combination to supply two views for the semantic zoom, the ZoomedIn and ZoomedOut. The control is able to transition gracefully from one to the other according with the user interaction. Semantic zoom can be triggered by the pinch and zoom gesture or using the mouse wheel in combination with the CTRL key. Also you can use the CTRL + Plus or CTRL + Minus with the keyboard.

To show how semantic zoom works I've used a RSS feed, I've already consumed for my Windows Phone series. The USGS provides a feed with latest earthquakes so I've created a simple class that loads this feed and produced a list of instances of the Earthquake class. Here is the class and the grouping:

   1: public class Earthquake
   2: {
   3:     public string Id { get; set; }
   4:     public DateTime Date { get; set; }
   5:     public string Place { get; set; }
   6:     public string Level { get; set; }
   7: }
   9: public class EarthquakeGroup : IEnumerable<Earthquake>
  10: {
  11:     public string Level { get; set; }
  12:     public IEnumerable<Earthquake> Quakes { get; set; }
  13:     public int Count { get { return this.Count(); } }
  15:     public IEnumerator<Earthquake> GetEnumerator()
  16:     {
  17:         return (this.Quakes ?? Enumerable.Empty<Earthquake>()).GetEnumerator();
  18:     }
  20:     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  21:     {
  22:         return (this.Quakes ?? Enumerable.Empty<Earthquake>()).GetEnumerator();
  23:     }
  24: }

The EarthquakeGroup is used to create groups of earthquake based on the level of magnitude. The feed provides a value called subject that determine this level. The count property retrieve the number of items in the group because I would like to show this information in the ZoomedOut view. Now, in the XAML I have created a simple grouped GridView:

   1: <GridView x:Name="QuakesGrid" 
   2:           IsSwipeEnabled="False" SelectionMode="None" ItemTemplate="{StaticResource ZoomedInItemTemplate}"
   3:           Visibility="Visible" IsItemClickEnabled="True" VerticalAlignment="Stretch" 
   4:           CanReorderItems="False" CanDragItems="False"
   5:           ItemsSource="{Binding Source={StaticResource GroupedSource}}">
   6:     <GridView.ItemsPanel>
   7:         <ItemsPanelTemplate>
   8:             <WrapGrid VerticalChildrenAlignment="Center" />
   9:         </ItemsPanelTemplate>
  10:     </GridView.ItemsPanel>
  11:     <GridView.GroupStyle>
  12:         <GroupStyle>
  13:             <GroupStyle.HeaderTemplate>
  14:                 <DataTemplate>
  15:                     <Grid Margin="100,0,0,0">
  16:                         <TextBlock Text="{Binding Level}" Foreground="White" Style="{StaticResource HeaderTextStyle}" Margin="5" />
  17:                     </Grid>
  18:                 </DataTemplate>
  19:             </GroupStyle.HeaderTemplate>
  20:             <GroupStyle.Panel>
  21:                 <ItemsPanelTemplate>
  22:                     <VariableSizedWrapGrid Margin="100,0,0,0" Orientation="Vertical" MaximumRowsOrColumns="4" />
  23:                 </ItemsPanelTemplate>
  24:             </GroupStyle.Panel>
  25:         </GroupStyle>
  26:     </GridView.GroupStyle>
  27: </GridView>

imageThis GridView is filled by a CollectionViewSource called GroupedSource (for details please read my previous article). The code in this snippet produces the result in the figure on the side. Now it is time to configure the XAML for the Semantic Zoom. I use the grouped GridView for the ZoomedInView and another GridView for the ZoomedOut view. this is because I want to use the same rectangles centered in the screen, but I can also use a ListView it is is good for my needs. So you need to add a SemanticZoom control to the XAML and wrap the previous element in the ZoomedInView property:

   1: <SemanticZoom x:Name="semanticZoom">
   2:     <SemanticZoom.ZoomedOutView>
   3:         <GridView ItemTemplate="{StaticResource ZoomedOutItemTemplate}" SelectionMode="None">
   4:             <GridView.ItemsPanel>
   5:                 <ItemsPanelTemplate>
   6:                     <WrapGrid ItemWidth="300" ItemHeight="150" 
   7:                               VerticalAlignment="Center" HorizontalAlignment="Center" />
   8:                 </ItemsPanelTemplate>
   9:             </GridView.ItemsPanel>
  10:         </GridView>
  11:     </SemanticZoom.ZoomedOutView>
  12:     <SemanticZoom.ZoomedInView>
  13:         <GridView x:Name="QuakesGrid" 
  14:                   IsSwipeEnabled="False" SelectionMode="None" ItemTemplate="{StaticResource ZoomedInItemTemplate}"
  15:                   Visibility="Visible" IsItemClickEnabled="True" VerticalAlignment="Stretch" 
  16:                   CanReorderItems="False" CanDragItems="False"
  17:                   ItemsSource="{Binding Source={StaticResource GroupedSource}}">
  18:             <GridView.ItemsPanel>
  19:                 <ItemsPanelTemplate>
  20:                     <WrapGrid VerticalChildrenAlignment="Center" />
  21:                 </ItemsPanelTemplate>
  22:             </GridView.ItemsPanel>
  23:             <GridView.GroupStyle>
  24:                 <GroupStyle>
  25:                     <GroupStyle.HeaderTemplate>
  26:                         <DataTemplate>
  27:                             <Grid Margin="100,0,0,0">
  28:                                 <TextBlock Text="{Binding Level}" Foreground="White" Style="{StaticResource HeaderTextStyle}" Margin="5" />
  29:                             </Grid>
  30:                         </DataTemplate>
  31:                     </GroupStyle.HeaderTemplate>
  32:                     <GroupStyle.Panel>
  33:                         <ItemsPanelTemplate>
  34:                             <VariableSizedWrapGrid Margin="100,0,0,0" Orientation="Vertical" MaximumRowsOrColumns="4" />
  35:                         </ItemsPanelTemplate>
  36:                     </GroupStyle.Panel>
  37:                 </GroupStyle>
  38:             </GridView.GroupStyle>
  39:         </GridView>
  40:     </SemanticZoom.ZoomedInView>
  41: </SemanticZoom>

This does not suffice. If you run the code in this way you will see that when you zoom out the screen goes blank. What I missed is to connect the ZoomedOutView with the group names. It is a single row in your codebehind:

(semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = this.GroupedSource.View.CollectionGroups;
imageThe CollectionGroups property exposes the groups. They are the instanes of EarthquakeGroups so I can connect to the Level and Count property. Whe I zoom out here is the view:
The Semantic zoom control is really awesome. If you click on the "moderate" tile you go directly to the group you choosed but it works also if you use the mousewheel.

If you want to see the full code please download the attached example.

Loading items incrementally.

The use of GridView and ListView is really interesting but often, you have to deal with the number of items you load in a view. If the items have to be loaded from the network, it can be a good idea to not retrieve all the items just because it may take lot of time and degrade the user experience. Tipically, in a web application, this problem is solved with the use of pagination. The user is presented with a number of pages, with a reduced number of items and he can decide to move forward and backward.

This solution is for sure effective, but it goes versus one of the pillars of metro guidelines: the fast and fluid requirement. The preferred way to handle this scenario in metro applications, is to handle deferred loading of pages following the interaction of the user. When he is near to the end of the items, you manage to load another chunk and append it to the end of the list. For this purpose GridView and ListView rely on an interesting interface; the ISupportIncrementalLoading that is tailored to simplify this task. 

To work with ISupportIncrementalLoading you have to relay on a normal collection. This collection is initially filled with the items the control need to display an entire screen. When the user scrolls the control, the collection that implements the interface is notified and it can load another chunk of items to add to the tail of the collection itself. Then, thanks to the usual CollectionChanged event the control is notified abuot the presence of new items and it loads the new items with the given template. Here is the interface I'm discussing:

   1: public interface ISupportIncrementalLoading
   2: {
   3:     bool HasMoreItems { get; }
   4:     IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count);
   5: }

Since the HasMoreItems property is used to determine if the collection has been fully downloaded, the core method is the LoadMoreItemsAsync. As you can expect this method needs to be implemented in an asynchronous way. This is completely understandable because you might have to download the items from the network and it is also an asynchronous operation. Differently from what you may expect the method must return an instance of IAsyncOperation<T> instead of Task<T>. This is because the IAsyncOperation<T> is an asynchronous operation of WinRT as well as Task<T> is the same for Metro. Since most of the times you have to deal with Task<T> you can easily convert it using the AsAsyncOperation<T>() extension method.

So, going back to the ISupportIncrementalLoading, if you want to create an infinite collection returning all the Pair numbers you have to write something like this:

   1: public class InfiniteSequence : ObservableCollection<int>, ISupportIncrementalLoading
   2: {
   3:     public Func<int, bool> Selector { get; set; }
   5:     public InfiniteSequence(Func<int, bool> selector)
   6:     {
   7:         this.Selector = selector;
   8:     }
  10:     public bool HasMoreItems
  11:     {
  12:         get { return true; }
  13:     }
  15:     public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
  16:     {
  17:         CoreDispatcher dispatcher = Window.Current.Dispatcher;
  19:         return Task.Run<LoadMoreItemsResult>(
  20:             () =>
  21:             {
  22:                 int[] numbers = Enumerable.Range(this.LastOrDefault(), 100).ToArray();
  24:                 dispatcher.RunAsync(CoreDispatcherPriority.Normal,
  25:                      () =>
  26:                      {
  27:                          foreach (int item in numbers)
  28:                              if (this.Selector(item))
  29:                                  this.Add(item);
  30:                      });
  32:                 return new LoadMoreItemsResult() { Count = 100 };
  33:             }).AsAsyncOperation<LoadMoreItemsResult>();
  34:     }
  35: }

In the constructor of the class I ask for a function returning bool. This funcion simple determines if a number can enter in the collection or if has to be excluded. To retrieve only pair numbers user a lambda like this:

o => o % 2 == 0;


The HasMoreItems property returns "True" just because the collection of the natural numbers is infinite. Obviously if the user scrolls too much I can have some problem because numbers are loaded into the collection and use memory. But for the purpose of the example I will trascure this problem. The LoadMoreItemsAsync instead launched a thread and it selects a the next 100 numbers and it applies the selector function. Then the remainig numbers are loaded in the ObservableCollection just to make it loaded in the GridView. After this code runs here is the result you have to expect:

In this case, instead of implementing INotifyCollectionChanged I have simple extended the ObservaleCollection<T>. When I add the items to the collection I need to be careful because I generate them in a separate thread so I have to use the Dispatched to marshal the context of the thread because the collection is binded to a UI element. Omitting the use of RunAsync raises a creoss thread violation exception.

Hard but easy.

In these articles I've explained some important topics that you need to know when you have to deal with the new metro applications layout requirements. GridView and ListView are for sure something of hard to start but, after some training, you understand they are key to keep the ui fast and fluid. In the next article I will go for another pillar of the metro guidelines, the "win as one", and I will speak about search and share contracts.



  • SureshPokkuluri

    Re: Windows 8 Metro: Improve GridView and ListView with SemanticZoom and Incremental Loading

    posted by SureshPokkuluri on Aug 11, 2012 02:17

    First of all thanks Andrea Boschin for your valuable post. Silverlightshow was doing really great job , helping developers to learn new technology concepts.

    I tried SemanticZoom. Really nice feature. I want to try incremental items loading. the given project doesn't have code for this. Can you please share that project as well.


  • CristvoMorgado

    Re: Windows 8 Metro: Improve GridView and ListView with SemanticZoom and Incremental Loading

    posted by CristvoMorgado on Aug 20, 2012 20:07

    Can we have the source code demonstrating the incremental loading.?

  • AtesDANIS

    Re: Windows 8 Metro: Improve GridView and ListView with SemanticZoom and Incremental Loading

    posted by AtesDANIS on Oct 07, 2012 19:51

    Please upload a sample for incrimental loading.

    Thank you.

  • AbdoAmmi

    Re: Windows 8 Metro: Improve GridView and ListView with SemanticZoom and Incremental Loading

    posted by AbdoAmmi on May 13, 2013 05:21
    dima alise wal7amdo  lilah

Add Comment

Login to comment:
  *      *       

From this series