Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Databinding with the Windows 8.1 Hub control

One of the controls that was introduced with Windows 8.1 is the Hub control. This control is fantastic as it provides a great starting point for application developers to build applications that use the default L structure common to most Windows 8 style applications. Visual Studio 2013 comes with a dedicated Hub project template which is a useful starting point but the XAML Hub Control Sample is an even better resource in terms of getting familiar with the control. It includes:

  • Basic usage.
  • Adding an image in a section which displays from edge to edge of the screen.
  • Adding interactive headers.
  • Adding semantic zoom.
  • Adding a vertical layout.

     

    Specifically if you looking to replicate the structure of any of the Bing apps (eg Bing News) where there is a Hub experience and Semantic zoom to jump through sections, this sample is the place to start.

    There are however two areas where this control is just plain awful and no consideration was given to how the control was to be used in practice:

    1) Support in Blend is woeful. Each hub section  has to be developed as a content template which is fine, except that you then can’t use visual states to control layout changes within a hub section. This is just plain disappointing.

    2) Minimal data binding support. Yes, you can set data context and data bind individual sections but unlike the gridview/listview which support grouping, the Hub control doesn’t support data binding the sections to a data source. If you look at the Panorama or Pivot controls on Windows Phone if you have a collection of sections, you can data bind this to the ItemsSource property which will dynamically create the appropriate number of panoramaitem or pivotitems. No such luck (unless I missed something) with the Hub control.

    The good news is that it’s not that hard to build a DYI data binding solution. Here’s a snippet that’ll get you started:

    public class HubBinder : DependencyObject
    {
        public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.RegisterAttached(
            "HeaderTemplate",
            typeof(DataTemplate),
            typeof(HubBinder), new PropertyMetadata(null, HeaderTemplateChanged)
            );

        private static void HeaderTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var hub = d as Hub;
            if (hub == null) return;
            var template = e.NewValue as DataTemplate;
            if (template == null) return;
            foreach (var hubSection in hub.Sections)
            {
                hubSection.HeaderTemplate = template;
            }
        }
        public static void SetHeaderTemplate(UIElement element, DataTemplate value)
        {
            element.SetValue(HeaderTemplateProperty, value);
        }

        public static DataTemplate GetHeaderTemplate(UIElement element)
        {
            return element.GetValue(HeaderTemplateProperty) as DataTemplate;
        }

        public static readonly DependencyProperty SectionTemplateProperty = DependencyProperty.RegisterAttached(
            "SectionTemplate",
            typeof(DataTemplate),
            typeof(HubBinder), new PropertyMetadata(null, SectionTemplateChanged)
            );

        private static void SectionTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var hub = d as Hub;
            if (hub == null) return;
            var template = e.NewValue as DataTemplate;
            if (template == null) return;
            foreach (var hubSection in hub.Sections)
            {
                hubSection.ContentTemplate = template;
            }
        }
        public static void SetSectionTemplate(UIElement element, DataTemplate value)
        {
            element.SetValue(SectionTemplateProperty, value);
        }

        public static DataTemplate GetSectionTemplate(UIElement element)
        {
            return element.GetValue(SectionTemplateProperty) as DataTemplate;
        }
        public static readonly DependencyProperty DataSourceProperty = DependencyProperty.RegisterAttached(
            "DataSource",
            typeof (object),
            typeof (HubBinder), new PropertyMetadata(null, DataSourceChanged)
            );

        private static void DataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var data = e.NewValue as IEnumerable;
            var hub = d as Hub;
            var template = GetSectionTemplate(hub);
            var header = GetHeaderTemplate(hub);
            if (data == null || hub == null) return;
            foreach (var section in data)
            {
                var sect = new HubSection { DataContext = section, ContentTemplate = template, HeaderTemplate = header };
                var hubData = section as IHubData;
                if (hubData != null)
                {
                    sect.Header = hubData.Header;
                    }

                hub.Sections.Add(sect);
            }
        }

        public static void SetDataSource(UIElement element, object value)
        {
            element.SetValue(DataSourceProperty, value);
        }

        public static object GetDataSource(UIElement element)
        {
            return  element.GetValue(DataSourceProperty);
        }
    }

    public interface IHubData
    {
        object Header { get; }

    }

    To use this attached property you simply need to add the DataSource attribute to your Hub control and create a data template called HubSectionTemplate:

    <Page x:Class="App2.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="using:App2"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d">
        <Page.Resources>
            <DataTemplate x:Key="HubSectionTemplate">
                <StackPanel>
                    <TextBlock Text="{Binding Content}"
                               FontSize="26.667"
                               Foreground="#FF1133A6" />
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="HubHeaderTemplate">
                <StackPanel>
                    <TextBlock Text="{Binding}"
                               FontSize="40"
                               Foreground="White" />
                </StackPanel>
            </DataTemplate>
        </Page.Resources>

        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <Hub local:HubBinder.DataSource="{Binding Sections}"
                 local:HubBinder.SectionTemplate="{StaticResource HubSectionTemplate}"
                 local:HubBinder.HeaderTemplate="{StaticResource HubHeaderTemplate}">

            </Hub>
        </Grid>
    </Page>

    It’s definitely not a fully fledged data binding solution but it should be enough to get those who are stuck looking up and running. The view model for the page could then look like:

  • public class MainViewModel
        {
            private ObservableCollection<Section> sections = new ObservableCollection<Section>();

            public ObservableCollection<Section> Sections
            {
                get
                {
                    return sections;
                }
            }
        }

        public class Section:IHubData
        {
            public string Title { get; set; }
            public string Content { get; set; }

            public object Header
            {
                get
                {
                    return Title;
                }
            }
        }

    Comments are closed