Creating a Hub Experience in a Windows Store Application
As I was developing the Microsoft WOWZAPP application for Windows 8, one of the responsibilities I had was creating the core dashboard, that displayed the event list, as well some information about event resources and participants. Normally, this is easily done with the help of a simple GridView However, in this case the items were grouped in a very specific manner, and were variable in size. By default the GridView control does not let me group items consistently in this way, therefore I had to resort to using a modified control - VariableGridView, initially documented by Jerry Nixon.

There is no XAML representation for this control, but rather a simple C#-based implementation that still relies on the GridView, overriding the PrepareContainerForItemOverride method:
using Windows.UI.Xaml.Controls;
using WOWZAPP.Models;
namespace WOWZAPP.Controls
{
public class VariableGridView : GridView
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
try
{
GenericDashItem _item = (GenericDashItem)item;
element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, _item.ColumnSpan);
element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, _item.RowSpan);
}
catch
{
element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);
element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, 1);
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
}
}
It relies on a collection of grouped items. In my case, a single dashgroup item was represented by the DashItem class:using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WOWZAPP.Common;
namespace WOWZAPP.Models
{
public class DashGroup : BindableBase
{
public DashGroup(DashGroupType dashType, string title)
{
DashType = dashType;
Title = title;
Items = new ObservableCollection<GenericDashItem>();
}
private DashGroupType _dashType;
public DashGroupType DashType
{
get { return this._dashType; }
set { this.SetProperty(ref this._dashType, value); }
}
private string _title;
public string Title
{
get { return this._title; }
set { this.SetProperty(ref this._title, value); }
}
private ObservableCollection<GenericDashItem> _items = new ObservableCollection<GenericDashItem>();
public ObservableCollection<GenericDashItem> Items
{
get { return this._items; }
set { this.SetProperty(ref this._items, value); }
}
}
}It is the foundation of the grouped hub. Its Items property is automatically set to contain the dash group children, in this case - GenericDashItem.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WOWZAPP.Common;
namespace WOWZAPP.Models
{
public class GenericDashItem : BindableBase
{
public GenericDashItem(string uniqueId, string title, string subtitle, string description, Uri imageUrl)
{
UniqueID = uniqueId;
Title = title;
Subtitle = subtitle;
Description = description;
Image = imageUrl;
}
private string _uniqueId;
public string UniqueID
{
get { return this._uniqueId; }
set { this.SetProperty(ref this._uniqueId, value); }
}
private string _title;
public string Title
{
get { return this._title; }
set { this.SetProperty(ref this._title, value); }
}
private string _tag;
public string Tag
{
get { return this._tag; }
set { this.SetProperty(ref this._tag, value); }
}
private string _subtitle;
public string Subtitle
{
get { return this._subtitle; }
set { this.SetProperty(ref this._subtitle, value); }
}
private string _description;
public string Description
{
get { return this._description; }
set { this.SetProperty(ref this._description, value); }
}
private int _columnSpan;
public int ColumnSpan
{
get { return this._columnSpan; }
set { this.SetProperty(ref this._columnSpan, value); }
}
private int _rowSpan;
public int RowSpan
{
get { return this._rowSpan; }
set { this.SetProperty(ref this._rowSpan, value); }
}
private Uri _imageUrl;
public Uri Image
{
get { return this._imageUrl; }
set { this.SetProperty(ref this._imageUrl, value); }
}
}
}Obviously, you can modify the set of properties you want to bind to, as there are no restrictions on the type or number of those. As there is a limited set of parameters that I need to actually display to the user, I kept the basic title/subtitle/image set, and added a Tag property, that will be used to carry any potential non-visible information.
Notice that both the row span (RowSpan) and the column span (ColumnSpan) are item-specific and are not set through a template selector or a similar helper.
Internally, you can create a DashGroup, add as many items as necessary and then pass those to an ObservableCollection that is bound to the custom GridView:
public static void InitializeProfilePane()
{
DashGroup group1 = new DashGroup(DashGroupType.Profile, "me");
GenericDashItem item4 = new GenericDashItem("dashItem2", "my event", "Subtitle", "description",
new Uri("http://www.microsoft.com/global/en-us/news/PublishingImages/HomePage/hero/spot_Toyota365_hero.jpg", UriKind.Absolute));
item4.ColumnSpan = 1;
item4.RowSpan = 1;
group1.Items.Add(item4);
GenericDashItem item3 = new GenericDashItem("dashItem2", "my app", "Subtitle", "description",
new Uri("http://www.microsoft.com/global/en-us/news/PublishingImages/HomePage/hero/spot_Toyota365_hero.jpg", UriKind.Absolute));
item3.ColumnSpan = 1;
item3.RowSpan = 1;
group1.Items.Add(item3);
GenericDashItem item5 = new GenericDashItem("dashItem2", "inbox", "Subtitle", "description",
new Uri("http://www.microsoft.com/global/en-us/news/PublishingImages/HomePage/hero/spot_Toyota365_hero.jpg", UriKind.Absolute));
item5.ColumnSpan = 1;
item5.RowSpan = 1;
group1.Items.Add(item5);
CoreViewModel.Instance.DashboardGroups.Add(group1);
}When binding, make sure that you proxy the collection through a CollectionViewSource:
<CollectionViewSource
x:Name="GroupedSource"
Source="{Binding Source={StaticResource CVM},Path=Instance.DashboardGroups}"
IsSourceGrouped="true"
ItemsPath="Items"/>IsSourceGrouped will be the primary flag that indicates that the grouping was already done. The grouped view will display the groups with correct binding, given the following configuration:
<controls:VariableGridView
x:Name="itemGridView"
Padding="116,46,40,46"
Grid.Row="1"
ItemsSource="{Binding Source={StaticResource GroupedSource}}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="itemGridView_ItemClick_1">
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
Style="{StaticResource TextPrimaryButtonStyle}"
Tag="{Binding}" Click="Button_Click_1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Width="800" Height="500" Orientation="Horizontal" ItemHeight="150" ItemWidth="200" Margin="0,0,24,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</controls:VariableGridView>As simple as this, I was able to get a basic dashboard up and running. The most complex part about this entire infrastructure is binding, as a lot of what is shown depends on proper binding to the grouped item collection, so pay close attention to that.





