.NET Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Creating a Hub Experience in a Windows Store Application

11.23.2012
| 4267 views |
  • submit to reddit

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.