Visually Located

XAML and GIS

Getting and Setting the SeletedIndex (visible section) of the Hub, now with Binding!

In my last post I talked about how you can restore the last visible section of the Hub. In that post I talked about the lack of the SelectedIndex and SelectedItem properties in the Hub control. These properties were in the Panorama control. Not having these properties means setting the visible section from a view model requires access to the Hub. This is not ideal. When functionality is not available, create it!

When you want to add functionality to a control there are two basic solutions.

  1. Extend the control by creating a new one.
  2. Extend the control with attached properties

The first solution is generally accomplished by inheriting from the control itself. The second is most often solved with a behavior. Whenever possible I prefer option 1 over option 2. The downside to option 1 is adding more and more functionality trying to come up with a good name for your control.

Extending existing controls is really easy. There [usually] is not a need to create a new style for the control. We can easily add new dependency properties to the control.

public class SelectionHub : Hub
{
    public int SelectedIndex
    {
        get { return (int)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for SelectedIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedIndexProperty =
        DependencyProperty.Register(
        "SelectedIndex", 
        typeof(int), 
        typeof(SelectionHub), 
        new PropertyMetadata(0, OnSelectedIndexChanged));
 
    private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // TODO
    }
}

When extending a control, you’ll want to override the OnApplyTemplate method to plug in your custom functionality. For adding the ability to add set or get the functionality, we’ll want to listen to when the section changes. In the last post I described how you can use the SelectionsInViewChanged event to be notified when the visible sections change. An odd thing about this event that Atley Hunter found is that it will not fire when the Hub has two sections. If we want a solution to work for all hubs, we need another event to hook into. If we view the style of the Hub control, we’ll see that is has a ScrollViewer control that aids moving content.

<Canvas Grid.RowSpan="2">
...
</Canvas>
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollMode="Auto" HorizontalSnapPointsType="None" HorizontalAlignment="Left" HorizontalScrollBarVisibility="Hidden" Grid.RowSpan="2" Template="{StaticResource ScrollViewerScrollBarlessTemplate}" VerticalScrollBarVisibility="Disabled" VerticalScrollMode="Disabled" ZoomMode="Disabled">
    <ItemsStackPanel  x:Name="Panel" CacheLength="6" Orientation="{TemplateBinding Orientation}"/>
</ScrollViewer>
<Canvas Grid.Row="0">
...
</Canvas>

The ScrollViewer has the ViewChanged event that we can hook into to tell when the visible section changes! In the event we can what the current selected index is by checking the first index of the SectionsIdView list.

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
 
    var scroller= GetTemplateChild("ScrollViewer") as ScrollViewer;
    if (scroller == null) return;
 
    scroller.ViewChanged += ScrollerOnViewChanged;
}
 
private void ScrollerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs scrollViewerViewChangedEventArgs)
{
    _settingIndex = true;
    SelectedIndex = Sections.IndexOf(SectionsInView[0]);
    _settingIndex = false;
}

When the SelectedIndex changes, we want to set the visible section. The SelectedIndex can change from binding, from code behind, or from the user swiping. the _settingIndex property above is to prevent trying to change the visible section when setting the SelectedIndex when swiping.

private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var hub = d as SelectionHub;
    if (hub == null) return;
 
    // do not try to set the section when the user is swiping
    if (hub._settingIndex) return;
 
    // No sections?!?
    if (hub.Sections.Count == 0) return;
 
    var section = hub.Sections[hub.SelectedIndex];
    hub.ScrollToSection(section);
}

Using this new hub control is simple

<controls:SelectionHub SelectedIndex="{Binding SeletedSectionIndex}"
                       Header="application name" 
                       Background="{ThemeResource HubBackgroundImageBrush}">
    <!-- sections -->
</controls:SelectionHub>

If you prefer option 2 for extending controls, then can easily be converted to a behavior. First, add a reference to the Behaviors SDK.

BehaviorReference

The key difference is subscribing to the ScrollViewer events when the associated object is attached.

public void Attach(DependencyObject associatedObject)
{
    AssociatedObject = associatedObject;
    var hub = associatedObject as Hub;
    if (null == hub) return;
 
    _scroller = hub.GetChildOfType<ScrollViewer>();
    if (_scroller == null)
    {
        hub.Loaded += OnHubLoaded;
    }
    else
    {
        _scroller.ViewChanged += ScrollerOnViewChanged;
    }
}
 
private void OnHubLoaded(object sender, RoutedEventArgs routedEventArgs)
{
    var hub = (Hub)sender;
 
    _scroller = hub.GetChildOfType<ScrollViewer>();
    if (_scroller != null)
    {
        _scroller.ViewChanged += ScrollerOnViewChanged;
        hub.Loaded -= OnHubLoaded;
    }
}

The behavior listens to the loaded event of the hub because it is possible that the hub it attached after the hub has loaded or before. 99% of the time, it will be before the hub had loaded, but you never know. From there it is pretty much the same.

You can download a Universal app solution in which the Windows Phone project uses the behavior and the Windows project uses the new control. Either solution can be used, the choice of which was used for the the project was random.

Restore the last visible Hub Section with the DefaultSectionIndex

I previously blogged about transitioning your app from the Panorama control to the new Hub control in Windows Phone 8.1 runtime apps. That post served as a starting point for moving away from the Silverlight Panorama control. As time goes by we find more things that need to be moved over. One of those is the ability to set (or get) the the selected PanoramaItem. This is a really important feature. According to the design guidelines for the Panorama.

For subsequent visits, the user should be taken back to the pane where they left off when user revisits the same Panorama control.

The design guidelines for the Hub has the following

For subsequent visits to the same hub control, take the user back to the section where they left off.

You can see this experience in the People hub.

In the Panorama control you could get the current visible item through either the SelectedIndex or the SelectedItem property, or be notified when the visible item changes with the SelectionChanged event. Following the guideline was a little hard however because the best way to ensure the pano opened to the correct page is with the DefaultItem property. The reason this was hard is because this excepts a PanoramoItem rather than an index. With an index, it could easily be saved and retrieved. With an item, it’s a little harder to save this information off and then get it the next time the app opened. While this was difficult, it was still doable.

The Hub control keeps this same workflow but has made an improvement. Instead of setting a default item, you now set an index with the DefaultSectionIndex property. The downside is the Hub control does not have a SelectedIndex or SelectedItem property. Fear not! The cheese was not removed, just moved. You can get the currently visible section with the SectionsInView property. This property returns an IList<HubSection> in which the first item in the list is the visible section and the second item in the list is the section that is peeking in on the right. With the SectionsInView and the Sections property, you can get the currently selected index.

var section = hub.SectionsInView[0];
var selectedIndex = hub.Sections.IndexOf(section);
Now that you have the visible section, you can save that off whenever you need to navigate away from your hub page. This can be done when navigating to a new page, or when the exiting the app. The hub even fires an event when the sections in view change.
private void OnSectionsInViewChanged(object sender, SectionsInViewChangedEventArgs e)
{
    var section = Hub.SectionsInView[0];
    ViewModel.DefaultIndex = Hub.Sections.IndexOf(section);
}

The viewmodel would set the index in the local settings so that it can easily be retrieved the next time the app is opened.

public class HubViewModel
{
    public int DefaultIndex
    {
        get
        {
            object defaultIndex;
            if (ApplicationData.Current.LocalSettings.Values.TryGetValue("defaultIndex", out defaultIndex))
            {
                return (int) defaultIndex;
            }
            return 0;
        }
        set { ApplicationData.Current.LocalSettings.Values["defaultIndex"] = value; }
    }
}

And then we bind to the property in our Hub

<Hub x:Name="Hub" x:Uid="Hub" Header="application name" Background="{ThemeResource HubBackgroundImageBrush}"
     DefaultSectionIndex="{Binding DefaultIndex}"
     SectionsInViewChanged="OnSectionsInViewChanged">
    <!-- setions -->
</Hub>

Now every time the user opens the app the hub will be on the last page they left from!

Download the sample solution and try it out.

Sorting mail from a contact list to a folder in outlook.com

Outlook.com offers the ability to add rules to your inbound traffic to help filter your mail. If you are part of a mailing list that gets a lot of emails, you may want to take advantage of these rules. Adding a rule to put these emails into a separate folder will help reduce “clutter” from your inbox folder. To accomplish this go to the settings of outlook.com and click Manage rules

MailSettings

From there click the New button, that will bring up the create rule page. Create a new rule when the email is sent to the mailing list.

CreateRule

Select a new folder for the mail to go into.

RuleAction

Click Save and from then on any mail to the mailing list will go to that folder!

Bind a collection of items to the Windows Phone MapControl

With every major version of Windows Phone comes a new way to work with maps. Keeping up with all of these changes has, honestly, been a hassle. Windows Phone 8.1 continues this trend with the new MapControl. It does improve from Windows Phone 8 in a lot of ways. One of those areas is that you no longer need another toolkit to perform basic map functionality. In Windows Phone 8 you needed the Windows Phone Toolkit to do things like add map elements to the map. Now this functionality is part of the core functionality.

To bind a collection of items to the new MapControl you use the MapItemsControl within the MapControl itself.

<maps:MapControl x:Name="Map" MapServiceToken="abcdef-abcdefghijklmno">
    <maps:MapItemsControl ItemsSource="{Binding Locations}">
    </maps:MapItemsControl>
</maps:MapControl>

The MapItemsControl is just a DependencyObject that has the ability to set items, through the Items and ItemsSource properties, and define what they look like with the ItemTemplate property. With the MapItemsControl ItemTemplate you can set your map icons to anything you want! Any XAML can be placed on your map!

Using an Image:

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"/>
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

MapPinImage

Using a Path:

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Path Width="28.5" Height="38" Stretch="Fill" Fill="Red" Data="F1 M 36.4167,19C 44.2867,19 50.6667,24.6711 50.6667,31.6667C 50.6667,32.7601 50.5108,33.8212 50.2177,34.8333L 36.4167,57L 22.6156,34.8333C 22.3225,33.8212 22.1667,32.7601 22.1667,31.6667C 22.1667,24.6711 28.5466,19 36.4167,19 Z M 36.4167,27.7083C 34.2305,27.7083 32.4583,29.4805 32.4583,31.6667C 32.4583,33.8528 34.2305,35.625 36.4167,35.625C 38.6028,35.625 40.375,33.8528 40.375,31.6667C 40.375,29.4805 38.6028,27.7083 36.4167,27.7083 Z "/>
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

MapPinPath

This will put map icons on your map, but will not place them where they need to be (unlike the images I provided). To set the map location of the icons, you use the attached Location property on the MapControl.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

This example assumes that the object you are binding to has a Geopoint property that returns a Geopoint. When map icons are placed, they are anchored at the top left of the icon. This works well if you use an icon that points to the top left, otherwise you will want to change it. To change the anchor point of the icon, you set the NormalizedAnchorPoint attached property on the MapControl. Set this with a Point with values between 0 and 1. Where 0,0 is the top left and 1,1 is the bottom right.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" 
               maps:MapControl.NormalizedAnchorPoint=".5,1" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

Here is a weird bug that I swear was not there when I started working with the new maps back in May. Fellow MVP and mapping addict, Joost van Schaik, noticed that setting this value in xaml does nothing. It will always be anchored at the top left. He found that if you bind to an Anchor property then it works well.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" 
               maps:MapControl.NormalizedAnchorPoint="{Binding Anchor}" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

Hopes this helps get you going with the new maps in Windows Phone 8.1. You can download a sample projectsample project to test as well.