The Windows 8.1 Hub Control

Posted by: Sumit Maitra , on 2/10/2014, in Category DNC Magazine
Views: 5357
Abstract: Windows 8.1 introduces a new Hub Control and a Hub style application template. We dig in and see how to use it for a simple yet practical Feed Reader app.

As highlighted in our previous Windows 8.1 articles, the 8.1 release introduces a lot of features that make development for the platform way easier than Windows 8. Today we explore one more such feature that is the Hub Control and a Project template that uses it out of the box. Today we’ll use the Hub Control to build an App that gives us a centralized hub for all www.dotnetcurry.com articles.

This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free .NET tutorials from experts

 

What’s a Hub App

Of the bundled apps that came with Windows 8, the Bing Apps like Bing News, Bing Weather and Bing Travel were the better looking, functional and among the more popular apps. They all had a typical style starting with a big Hero Image that had a headline and then one could scroll across for the latest snapshots and then dig deeper for details views. For example, here is a the opening page of the Bing News app (in Windows 8.1)

bing-hero-image

As we scroll to the right, we start the ‘Top Stories’ section.

bing-get-started

When we scroll further to the right, we start seeing more categories and their top 5-6 headlines. Each category can potentially end with a “More …” list item that navigates the user to that particular section and list all the possible headlines.

bing-sections

Tapping on a headline, gives us the complete report, which you can scroll horizontally to read it completely.

bing-details

This is the typical design of a Hub App! The Hub control gives us the basic UI and navigation skeleton to build a Hub App. So lets build one.

Building a Hub App

Our Hub app will contain feeds from two sites, with Curated streams of articles being showcased in the Hub. Since our target sites are technical sites, the Images that we have are not expected to be glamorous enough to be blown up full screen, so we’ll use a standard Hero Image. For rest of the layout, we’ll start with the default and then tweak if required.

Off the blocks – The Hub Project Template

We start off with the new Hub App Project Template in Visual Studio 2013.

new-hub-app

Straight off the bat, we end up with an application that looks as follows:

default-ui-tiled

As we can see, a lot of groundwork with respect to the layout is already done for us. What remains is to plug in real data and add some navigation if required.

Before we go into getting real data, lets look at the settings of the Hub Control in HubPage.xaml

hub-page-default-xaml

The above XAML snapshot can be interpreted as follows:

1. The Hub Page has two Data Templates – Standard310x260ItemTemplate and Standard420x130Item Template.

2. The Root panel is still a Grid control that contains the Hub control.

3. The Hub control has seven subsections in it each defining a typical layout. Let’s look at these sections in details:

a. The Hub.Header – This comprises of the Application Header Text and the back button. The detailed markup for it is as follows:

<Hub.Header>
<!-- Back button and page title -->
<Grid Margin="0,20,0,0">
  <Grid.ColumnDefinitions>
   <ColumnDefinition Width="80"/>
   <ColumnDefinition Width="*"/>
  </Grid.ColumnDefinitions>
  <StackPanel Height="40">
   <AppBarButton x:Name="backButton" Icon="Back" Margin="-30,-14,0,0"
    Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
    Visibility="{Binding IsEnabled, Converter={StaticResource
     BooleanToVisibilityConverter}, RelativeSource={RelativeSource Mode=Self}}"
    AutomationProperties.Name="Back"
    AutomationProperties.AutomationId="BackButton"
    AutomationProperties.ItemType="Navigation Button"/>
   </StackPanel>
   <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}"
    Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
    IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Top"/>
</Grid>
</Hub.Header>

As we can see the header uses a Grid layout with two columns. The first column is for the button and the second column is for the Application Name.

b. Next comes the Hub Section with the width of 780px. This is the Hero Image section. The markup for it is as follows:

<HubSection Width="780">
<HubSection.Background>
  <ImageBrush ImageSource="Assets/MediumGray.png" Stretch="UniformToFill" />
</HubSection.Background>
</HubSection>

The default image is set to the MediumGray.png from our app’s Assets folder.

c. The third Hub Section is a single column section that shows the latest/most important item of the feed. It’s markup is as follows:

<HubSection Width="580" Padding="120,30,40,44" VerticalAlignment="Top" >
<HubSection.Header>
  <TextBlock x:Uid="Section1Header" TextLineBounds="TrimToBaseline"
   OpticalMarginAlignment="TrimSideBearings" Text="Section 1"/>
</HubSection.Header>
<DataTemplate>
  <Grid Margin="0,10,0,0">
   <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
   </Grid.RowDefinitions>
   <Image Source="Assets/MediumGray.png" Stretch="Fill" Width="420" Height="280"/>
   <TextBlock Grid.Row="1"
    x:Uid="Section1Subtitle" Style="{StaticResource SubheaderTextBlockStyle}"
    TextWrapping="Wrap" Margin="0,10,0,0"
    Text="Lorem ipsum …"/>
   <TextBlock Grid.Row="2" x:Uid="DescriptionHeader" Margin="0,10,0,0"
    Style="{StaticResource TitleTextBlockStyle}" Text="Description text:"/>
   <TextBlock Grid.Row="3" x:Uid="Section1DescriptionText" Style="{StaticResource
    BodyTextBlockStyle}" Text="Lorem ipsum … Truncated Text… "/>
  </Grid>
</DataTemplate>
</HubSection>

The markup defines a custom DataTemplate here but doesn’t get the data from a data source instead it has hardcoded the data. We’ll have to fix this in our final application.

d. The fourth Hub Section uses the Standard420x130Template. Notice the custom HubSection Header that we can use.

<HubSection Padding="40,30,40,44" DataContext="{Binding Path=[0], Source={StaticResource groupedItemsViewSource}}" IsHeaderInteractive="True" >
<HubSection.Header>
  <TextBlock x:Uid="Section2Header" TextLineBounds="TrimToBaseline"
   OpticalMarginAlignment="TrimSideBearings" Text="Section 2"/>
</HubSection.Header>
<DataTemplate>
  <ListView
   x:Name="itemListView"
   Margin="-14,-4,0,0"
   AutomationProperties.AutomationId="ItemListView"
   AutomationProperties.Name="Grouped Items"
   ItemsSource="{Binding Items}"
   ItemTemplate="{StaticResource Standard420x130ItemTemplate}"
   IsSwipeEnabled="False"
   IsItemClickEnabled="True"
   ScrollViewer.VerticalScrollBarVisibility="Hidden"
   SelectionMode="None"
   ItemClick="ItemView_ItemClick">
  <ListView.ItemsPanel>
  <ItemsPanelTemplate>
   <ItemsWrapGrid />
   </ItemsPanelTemplate>
  </ListView.ItemsPanel>
</ListView>
</DataTemplate>
</HubSection>

e. The fifth Hub Section again defines a custom data template for curated data. In the default app, this is hardcoded. This section also has a collage of images with hard coded links to assets in the app. Also notice the custom Hub Section Header.

Mind you these are all samples of how you can represent data via the Hub Control. None of it is mandatory and you can have as many or as few Hub Sections as you think necessary.

f. The sixth Hub Section maps to ‘Section 4’ in the hub layout and is again using custom data binding.

g. The last section uses the standard310x260Template data template and lists out the 6th group of the grouped dataset. Again the index 5 here is chosen at random and hardcoded.

<HubSection DataContext="{Binding Path=[5], Source={StaticResource
groupedItemsViewSource}}"  Padding="40,30,150,44">
<HubSection.Header>
  <TextBlock x:Uid="Section5Header" TextLineBounds="TrimToBaseline"
   OpticalMarginAlignment="TrimSideBearings" Text="Section 5"/>
</HubSection.Header>
<DataTemplate>
  <GridView
   x:Name="itemGridView"
   Margin="-13,-4,0,0"
   AutomationProperties.AutomationId="ItemGridView"
   AutomationProperties.Name="Items In Group"
   ItemsSource="{Binding Items}"
   ItemTemplate="{StaticResource Standard310x260ItemTemplate}"
   SelectionMode="None"
   IsSwipeEnabled="false"
   IsItemClickEnabled="True"
   ItemClick="ItemView_ItemClick">
  </GridView>
</DataTemplate>
</HubSection>

That was a glimpse of what we get from the Hub Control and the Hub App template out of the box! The 7 templates are there by default to showcase different possibilities. You don’t have to bend your data to fit the template, rather you can easily pick and choose the template you want to use and build your own if you want to.

Now that we’ve got an idea of how the Hub Template is structured, let’s setup our data and then bind it to the hub.

Setting up your Data

www.dotnetcurry.com has different RSS feeds for each category of articles. To create a nice Feed reader that shows a glimpse of all categories we can use these Feed URLs as our data sources.

dnc-services

Then we have the Home Page Feed that shows feeds the latest articles. From this feed we will use the first item as the Hero Item.

With the sources decided the entities to hold the Feed Data is defined as follows:

public class FeedDataItem
{
public FeedDataItem(String uniqueId, String title, String subtitle, String  
  contentPath, String description, String content)
{
  this.UniqueId = uniqueId;
  this.Title = title;
  this.Subtitle = subtitle;
  this.Description = description;
  this.ContentPath = contentPath;
  this.Content = content;
}

public string UniqueId { get; private set; }
public string Title { get; private set; }
public string Subtitle { get; private set; }
public string Description { get; private set; }
public string ImagePath { get; private set; }
public string ContentPath { get; set; }
public string Content { get; private set; }
public override string ToString()
{
  return this.Title;
}
}

The FeedItem class has each result from a given RSS Feed. It contains the Title, Subtitle (in which we store Author), Description (the article Summary) and ContentPath the actual URL of the article. Rest of the properties can be removed, I just kept them to keep the default UI Bindings working.

Next we have the FeedDataGroup class.

public class FeedDataGroup
{
public FeedDataGroup(String uniqueId, String title, String subtitle, String
  feedPath, String description)
{
  this.UniqueId = uniqueId;
  this.Title = title;
  this.Subtitle = subtitle;
  this.Description = description;
  this.FeedPath = feedPath;
  this.Items = new ObservableCollection<FeedDataItem>();
}
 
public string UniqueId { get; private set; }
public string Title { get; private set; }
public string Subtitle { get; private set; }
public string Description { get; private set; }
public string ImagePath { get; private set; }
public string FeedPath { get; set; }
public ObservableCollection<FeedDataItem> Items { get; private set; }

public FeedDataItem HeroItem
{
  get
  {
   if (Items.Count > 0)
   {
    return Items[0];
   }
   return null;
  }
}
public override string ToString()
{
  return this.Title;
}
}

This Class is modeled from the SampleDataGroup class that comes by default. It stores a UniqueId, Title, SubTitle, Description and the FeedPath or the URL to the Feed source. The Items collection is the list of FeedItem objects based on the data returned by the Feed. Note the HeroItem property added for easy data binding. This always points to the first element in the Items array.

Next we have a helper class called FeedItemIterator. This class (inspired by the Atom Feed Sample from SDK) contains helper methods to loop through a Feed and return appropriate data once the Feed has been loaded.

class FeedItemIterator
{
private SyndicationFeed feed;
private int index;
public FeedItemIterator()
{
  this.feed = null;
  this.index = 0;
}
public void AttachFeed(SyndicationFeed feed)
{
  this.feed = feed;
  this.index = 0;
}
public bool MoveNext()
{
  if (feed != null && index < feed.Items.Count - 1)
  {
   index++;
   return true;
  }
  return false;
}

public void MovePrevious()
{
  if (feed != null && index > 0)
  {
   index--;
  }
}

public bool HasElements()
{
  return feed != null && feed.Items.Count > 0;
}

public string GetTitle()
{
  // Nothing to return yet.
  if (!HasElements())
  {
      return "(no title)";
  }

  if (feed.Items[index].Title != null)
  {
      return WebUtility.HtmlDecode(feed.Items[index].Title.Text);
  }

  return "(no title)";
}

public string GetContent()
{
  // Nothing to return yet.
  if (!HasElements())
  {
     return "(no value)";
    }
    else if ((feed.Items[index].Content != null) &&
     (feed.Items[index].Content.Text != null))
    {
        return feed.Items[index].Content.Text;
    }
    else if (feed.Items[index].Summary != null &&
     !string.IsNullOrEmpty(feed.Items[index].Summary.Text))
    {
        return feed.Items[index].Summary.Text;
    }
return "(no value)";
}

public string GetIndexDescription()
{
// Nothing to return yet.
if (!HasElements())
{
     return "0 of 0";
}

return String.Format("{0} of {1}", index + 1, feed.Items.Count);
}

public Uri GetEditUri()
{
// Nothing to return yet.
if (!HasElements())
{
     return null;
}

return feed.Items[index].EditUri;
}

public SyndicationItem GetSyndicationItem()
{
// Nothing to return yet.
if (!HasElements())
{
  return null;
}
return feed.Items[index];
}

internal string GetAuthor()
{
if (feed.Items[index].Authors != null &&
  feed.Items[index].Authors.Count > 0)
{
  string authors = "";
  foreach (var author in feed.Items[index].Authors)
  {
   authors += (author.NodeValue + ",");
  }
  return authors.TrimEnd(',');
  }
  return "";
}

internal string GetUrlPath()
{
  if (feed.Items[index].Links != null &&
   feed.Items[index].Links.Count > 0)
  {
   return feed.Items[index].Links[0].Uri.AbsoluteUri;
  }
  return "";
}
}

Loading Feeds

Finally we have our FeedDataSource class that encapsulates the Data loading operations. The GetFeedDataAsync class is the heart of the class that loads the actual data. The Groups property represents the datasource.

As we can see below, we have take seven of the available Feed URLs and created a FieldGroup out of each. The First group is the Home Page feed, and the rest are assorted Feeds of various tags/categories. Next we loop through each group and load the feed items which represent one article each.

private async Task GetFeedDataAsync()
{
FeedItemIterator feedIterator = new FeedItemIterator();
if (this._groups.Count != 0)
     return;
Groups.Add(new FeedDataGroup("whatsnew", "What's New", "Latest articles on DNC",
  "http://feeds.feedburner.com/netCurryRecentArticles", "Latest DNC articles"));
Groups.Add(new FeedDataGroup("aspnetmvc", "ASP.NET MVC", "Latest ASP.NET MVC
  articles","http://www.dotnetcurry.com/GetArticlesRss.aspx?CatID=67","Latest
  ASP.NET MVC articles"));
Groups.Add(new FeedDataGroup("winrt", "Windows 8 and 8.1", "Articles for Windows
  8 and 8.1 Store Apps",
  "http://www.dotnetcurry.com/GetArticlesRss.aspx?CatID=75", "Articles for Windows
  8 and 8.1 Store Apps")); //WinRT
Groups.Add(new FeedDataGroup("azure", "Windows Azure", "Windows Azure Tutorials",
  "http://www.dotnetcurry.com/GetArticlesRss.aspx?CatID=73", "Windows Azure
  Tutorials")); //Windows Azure
Groups.Add(new FeedDataGroup("aspnet", "ASP.NET and WebAPI", "Web API articles",
  "http://www.dotnetcurry.com/GetArticlesRss.aspx?CatID=54", "ASP.NET Web API 
  Articles")); //ASP.NET
Groups.Add(new FeedDataGroup("vstfs", "Visual Studio and Team Foundation Server",
  "Visual Studio and TFS Articles",
  "http://www.dotnetcurry.com/GetArticlesRss.aspx?CatID=60",  "Visual Studio and
  Team Foundation Server"));  //Visual Studio Team System
Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
Uri resourceUri;
foreach (var group in Groups)
{
  if (!Uri.TryCreate(group.FeedPath, UriKind.Absolute, out resourceUri))
  {
    return;
  }
  try
  {
   feedIterator.AttachFeed(await
   FeedDataSource.GetClient().RetrieveFeedAsync(resourceUri));
   while (feedIterator.MoveNext())
   {
    FeedDataItem item = new FeedDataItem(group.UniqueId,
    feedIterator.GetTitle(),
    feedIterator.GetAuthor(),
    feedIterator.GetUrlPath(),
    feedIterator.GetContent(),
    feedIterator.GetContent());
    group.Items.Add(item);
   }
  }
  catch (Exception ex)
  {
   throw;
  }
}
}

The DataSource is created and initialized in the HubPage.xaml.cs classes’ NavigationHelper_LoadState method.

private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var feedDataGroups = await FeedDataSource.GetGroupsAsync();
this.DefaultViewModel["Groups"] = feedDataGroups;
}

With our custom data source setup, if we run the application as is, we’ll see that Section 2 and 5 are getting live data from the Feeds. Why? That’s because these two sections were using Data binding were bound to the Groups data. Rest of the Sections was hard coded.

To test out our HeroItem property, let’s data bind the very first section to show the latest article published.

As highlighted below we’ve added a DataContext to the HubSection and based on the context we have bound the Title, Subtitle and Description properties of the HeroItem of the very first Feed Group which in our case is the site wide feed.

<HubSection Width="580" Padding="120,30,40,44" DataContext="{Binding Path=[0],
Source={StaticResource groupedItemsViewSource}}"
VerticalAlignment="Top" >
<HubSection.Header>
  <TextBlock TextLineBounds="TrimToBaseline"
   OpticalMarginAlignment="TrimSideBearings" Text="Latest Article"/>
</HubSection.Header>
<DataTemplate>
  <Grid Margin="0,10,0,0">
   <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
   </Grid.RowDefinitions>
   <Image Source="Assets/MediumGray.png" Stretch="Fill" Width="420" Height="280"/>
   <TextBlock Grid.Row="1" Style="{StaticResource SubheaderTextBlockStyle}"
    TextWrapping="Wrap" Margin="0,10,0,0" Text="{Binding Path=HeroItem.Title}"/>
   <TextBlock Grid.Row="2" Margin="0,10,0,0" Style="{StaticResource
    TitleTextBlockStyle}" Text="{Binding HeroItem.Subtitle}"/>
   <TextBlock Grid.Row="3" Style="{StaticResource BodyTextBlockStyle}"
   Text="{Binding HeroItem.Description}"/>
  </Grid>
</DataTemplate>
</HubSection>

Next we’ll bind Hub Section 2 to the same feed group but now it will show a list of latest items. The current Hub Section 3 is not very practical for us because we currently don’t have images to show.

However we can replicate Section 4 to show the Hero item of each feed. Be sure to remove the x:Uid="ItemTitle" from each TextBlock element else it will get the data from the Resource file and (for strange reason) ignore the data-binding (I would have though successful data-binding overrides everything else).

Once we run the application now we’ll see that it is showing us the Hero units for each Feed Group. But there is no way to navigate to the Feeds! This brings us to the next section – Navigation.

Hooking up Navigation

Actually hooking up Navigation is pretty easy. It is already done for us in Section 2. All you have to do is set the IsHeaderInteractive="True" property. This will raise the HubSectionHeader_Click event which has this default implementation

void Hub_SectionHeaderClick(object sender, HubSectionHeaderClickEventArgs e)
{
    HubSection section = e.Section;
    var group = section.DataContext;
    this.Frame.Navigate(typeof(SectionPage), ((FeedDataGroup)group).UniqueId);
}

This simply looks up the FeedDataGroup item for the header and passes it to the SectionPage. So for all our headers we can enable the IsHeaderInteractive flag.

If we run the App now, we can see that the “What’s New” Section has a ‘>’ indicating that it is an actionable link

latest-articles

Clicking on “What’s New” takes us to the Sections Page with the list of Feeds.

whats-new-items

However clicking on a FeedItem navigates to a blank page. Ideally we would like to see the article content. Let’s see what we can do to show the Article content.

Adding a WebView to ItemsPage.xaml and Rendering Feed Link

If we navigate to SectionsPage.xaml.cs and check the ItemView_Click event we’ll see that it’s passing the UniqueId property to the Items page. We’ll take a shortcut here and instead of passing the UniqueId we’ll pass the entire FeedItem itself.

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
var item = (FeedDataItem)e.ClickedItem;
this.Frame.Navigate(typeof(ItemPage), item);
}

Now we move on to the SectionPage.xaml and add a WebView to the content section

<Grid Grid.Row=”1” x:Name=”contentRegion”>
<WebView Name=”ContentWebView” Margin=”0,3,0,0”></WebView>
</Grid>

Finally in the navigationHelper_LoadState event we update the code to load the URL into the Web View

private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var item = (FeedDataItem)e.NavigationParameter;
this.DefaultViewMode[“Item”] = item;
if(!string.IsNullOfEmpty(item.ContentPath))
{
  WebView contentWebView = this.FindName(“ContentWebView”) as WebView;
  if(contentWebView!=null)
  {
   contentWebView.Navigate(new Uri(item.ContentPath));
  }
}
}

All set, if we run the app now and Navigate to an article from the ItemsPage we’ll see the following View

items-page-final

Pretty neat, we have our very own Feed Reader ready App ready to go.

Summary

We covered a lot of ground today and it is worth doing a recap to make sure we didn’t miss anything:

  1. We learnt about the new Hub Control and the Hub App Template
  2. We saw how to configure and layout Hub Sections, Headers, Hero Image etc.
  3. We built a rudimentary Feed Reader using the built in Syndication APIs.
  4. We data bound the Hub Control Sections to show up Feed Data.
  5. Finally we used a WebView control to render the final Link in the Feed.

Overall we got to see how we could leverage the Hub Application template to build rich, interactive, full screen Windows Store Apps.

Download the entire source code from our GitHub Repository at bit.ly/dncm9-win81hub

Give me a +1 if you think it was a good article. Thanks!
Recommended Articles
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by QSS on Thursday, February 13, 2014 11:03 PM
Thanks - nice tutorial ,
Can this project also read web feeds with .xml extension ?

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel