Diederik Krols

The XAML Brewer

Capability-driven App Bar for List Items

Lots of Windows 8.1 Store Apps come with a page that shows a list of items that have commands attached to them (like edit, copy, and delete). This article describes how to bind the command bar experience of this type of screen to the capabilities of the device. I created a Windows 8.1 XAML MVVM-style sample app that detects the presence of a mouse and/or a touch screen, and then decides to display the commands that are specific to an item in one of three ways:

  • inside the item that has the mouse, or
  • inside the item that is selected, or
  • in the main bottom app bar.

Here’s how the app looks like. For testing purposes, it allows to change the input mode at runtime:

CommandBar_Desktop

According to the Store App user interface guidance, we should always display such action commands in the bottom app bar (and navigation commands at the top). That what I always did in my apps … well … until users started to complain. The “Touch First” principle has always been one of the main drivers of the Store app UI experience. In reality, the vast majority of PC’s that run these apps still has NO touch screen, and the mouse is the main input device. Here’s the standard workflow for editing a listview item using the mouse in a touch-first environment:

  1. Select the item,
  2. assume the presence of a bottom app bar with related commands (not obvious for users that don’t use the Modern UI on a daily basis),
  3. open the command bar (e.g. by right clicking),
  4. click the corresponding app bar button (which on a large screen can be VERY far away from the item you selected), and finally
  5. explicitly close the app bar again (e.g. by clicking somewhere on the page). Clicking another item doesn’t select that item, but it closes the app bar. Confusing.

Here’s that same scenario in Microsoft’s standard Mail app:

  1. Hover with the mouse over an item, and
  2. click on the inline button. Done.

I think it’s clear that non-touch users would prefer the latter scenario. Here’s how this looks like in the Mail app:

Outlook_1

In touch mode, the inline buttons don’t appear, instead they appear in the ‘classic’ bottom app bar:

Outlook_2

The presence of that app bar is indicated by a hint at the bottom of the screen (it’s actually more than a hint: if you click or tap on the ellipsis, the app bar opens):

Outlook_3 

This is one of the UI elements that has become a standard in my own apps. If you want one in your own app, just install the AppBarHint control by Dave Smits. Here’s how to use that control in XAML - just place it at the bottom. The control will lookup the app bar, and link to it:

<Grid>
    <!— Main UI ... -->

    <!-- AppBar Hint -->
    <local:CollapsedAppBar VerticalAlignment="Bottom"
                           Grid.RowSpan="10"
                           Grid.ColumnSpan="10" />
</Grid>

The Mail app experience inspired me to create some components to enhance the user experience –based on the device’s capabilities- for Windows 8.1 Store app pages that are built around a list of items.

Device Capabilities

Let’s start with an enumeration for the input modes:

public enum InputMode
{
    Desktop,
    TouchEnabled,
    TouchOptimized
}

Basically Desktop means ‘no touch screen detected’, TouchOptimized means ‘no mouse detected’, and TouchEnabled covers all other scenarios. The Device class exposes the input mode – in an MVVM architecture it would be part of a service. The initial input mode is detected automatically, based on calls to members in the Windows.Devices.Input namespace. It can be overridden by the user at any time, that’s why it comes as a Singleton with property change notification:

class Device : BindableBase
{
    private readonly static Device _instance;
    private InputMode _inputMode;

    /// <summary>
    /// Represents the device and its capabilities.
    /// </summary>
    static Device()
    {
        _instance = new Device();

        // No touch -> Desktop
        if (new Windows.Devices.Input.TouchCapabilities().TouchPresent == 0)
        {
            _instance._inputMode = InputMode.Desktop;
            return;
        }

        // No mouse -> Touch Optimized
        if (new Windows.Devices.Input.MouseCapabilities().MousePresent == 0)
        {
            _instance._inputMode = InputMode.TouchOptimized;
            return;
        }

        // Otherwise -> Touch Enabled
        _instance._inputMode = InputMode.TouchEnabled;
    }

    public static Device Instance
    {
        get { return _instance; }
    }

    public InputMode InputMode
    {
        get { return this._inputMode; }
        set { this.SetProperty(ref this._inputMode, value); }
    }
}

To make the input mode available to all view models in the app, we place it in the ViewModelBase base class:

class ViewModelBase : BindableBase
{
    public InputMode InputMode
    {
        get { return Device.Instance.InputMode; }
        set
        {
            Device.Instance.InputMode = value;
            this.OnPropertyChanged("InputMode");
        }
    }
}

Let’s start with the implementation of the different behaviors.

Desktop Mode

First comes Desktop mode, where the item specific commands are represented by app bar buttons in the item template. The buttons appear on the item on which the mouse hovers – without the need for actually selecting the item itself:

CommandBar_Desktop

To deal with the hovering aspect, we introduce a HasPointer property. It’s in a base class for all viewmodels that represent an item. The property will only become true when the item has the mouse focus, but only in Desktop mode. That’s the only mode in which we are sure that the PointerEntered and PointerExited events that trigger the value change, are generated by the mouse. In touch or pen mode the exact same events are triggered, and unfortunately there’s no way to detect the source of the events: touch, pen, mouse, or other. In touch mode, the HasPointer property would create a very confusing behavior: when the user slides into an item the command buttons would appear. But when he lifts his finger to tap one of the buttons, the PointerExited event would fire and they would disappear. The same happens when an item is selected by touch or pen: both PointerEntered and PointerExited events would fire (well: PointerEntered only when the device detects a move), and the command bar will just briefly flash.

Here’s ItemViewModelBase class, hosting the HasPointer implementation:

/// <summary>
/// Base class for viewmodels that represent an item in a list.
/// </summary>
class ItemViewModelBase : ViewModelBase
{
    private bool hasPointer = false;

    // ... (other stuff)

    public bool HasPointer
    {
        get { return this.hasPointer; }
        set
        {
            // Property is only active in Desktop mode.
            if (this.InputMode == Services.InputMode.Desktop)
            {
                this.SetProperty(ref this.hasPointer, value);
            }
        }
    }
}

Here’s the XAML definition of the inline command bar. It’s using standard app bar buttons in a stackpanel of which the visibility is bound to the HasPointer property:

<ListView.ItemTemplate>
    <DataTemplate>
        <!-- ... (more stuff) -->
            <StackPanel Visibility="{Binding HasPointer, Converter={StaticResource BooleanToVisibilityConverter}}">
                <AppBarButton Label="Smell"
                                Command="{Binding SmellCommand}"
                                RequestedTheme="Dark">
                </AppBarButton>
                <AppBarButton Label="Eat"
                                Command="{Binding EatComand}"
                                RequestedTheme="Dark">
                </AppBarButton>
            </StackPanel>
        <!-- ... (more stuff) -->
    </DataTemplate>
</ListView.ItemTemplate>

The HasPointer property is changed by the view through a behavior:

<ListView.ItemTemplate>
    <DataTemplate>
        <Grid>
        <!-- ... (again more stuff) -->
            <i:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="PointerEntered">
                    <core:ChangePropertyAction PropertyName="HasPointer"
                                                Value="True"
                                                TargetObject="{Binding}" />
                </core:EventTriggerBehavior>
                <core:EventTriggerBehavior EventName="PointerExited">
                    <core:ChangePropertyAction PropertyName="HasPointer"
                                                Value="False"
                                                TargetObject="{Binding}" />
                </core:EventTriggerBehavior>
            </i:Interaction.Behaviors>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

For Visual Studio 2013, this implies that you need to install the Behaviors SDK through the Extensions menu, and add a reference to it:

Behaviors_SDK_1

Visual Studio 2015 RC does not require a separate install: the SDK comes with the Visual Studio/Blend package.

For the sake of completeness: Desktop mode can probably also be implemented using a PointerOver visual state.

TouchEnabled Mode

The second input mode is TouchEnabled. In this mode, the item specific commands still appear still appear inside the item template, but only in the selected item:

CommandBar_TouchEnabled

The notion of having an item selected (HasSelection) and the SelectedItem itself, are implemented ListViewModelBase<T>, a generic base class for the viewmodels that represent a list. When the selection changes, the previous item is unselected (if there was one), the new item is selected (if there is one), and all bindings to HasSelection are updated:

class ListViewModelBase<T> : ViewModelBase where T : ItemViewModelBase
{
    private T selectedItem;

    public T SelectedItem
    {
        get { return this.selectedItem; }
        set
        {
            if (this.selectedItem != null)
            {
                this.selectedItem.IsSelected = false;
            }

            this.SetProperty(ref this.selectedItem, value);

            if (this.selectedItem != null)
            {
                this.selectedItem.IsSelected = true;
            }

            this.OnPropertyChanged("HasSelection");
        }
    }

    public bool HasSelection
    {
        get { return this.selectedItem != null; }
    }
}

In the view, the selected item of the listview is two-way bound to the one in the viewmodel:

<ListView SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <!-- (all the rest) -->
</ListView>

The item itself comes with an IsSelected property, again in the ItemViewModelBase class. When an item is selected in TouchEnabled mode, it updates the HasPointer property, and the inline commands become visible:

public bool IsSelected
{
    get { return this.isSelected; }
    set
    {
        this.SetProperty(ref this.isSelected, value);
        // Update HasPointer if in TouchEnabled mode.
        if (this.InputMode == Services.InputMode.TouchEnabled)
        {
            this.hasPointer = value;
            this.OnPropertyChanged("HasPointer");
        }
    }
}

TouchOptimized Mode

Let’s move on to the third input mode: TouchOptimized is chosen when no mouse is detected. In this mode we stick to the prescribed UI: an app bar at the bottom of the page:

CommandBar_TouchOptimized

The item specific commands are part of the standard bottom app bar, but only appear in TouchEnabled mode, and only when there’s an item selected. For the implementation we cannot use a PrimaryCommands or SecondaryCommands element here, because these elements don’t have a Visibility property. So we have to help ourselves with some good old stack panels:

<StackPanel Orientation="Horizontal"
            Visibility="{Binding InputMode, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter='TouchOptimized'}">
    <StackPanel Orientation="Horizontal"
                Visibility="{Binding HasSelection, Converter={StaticResource BooleanToVisibilityConverter}}">
        <!-- AppBarButtons ... --> 
    </StackPanel>
</StackPanel>

When the odds are high that the user is going to execute a command after he selected an item, then it makes sense to automatically open the command bar for him. That’s something that the view can handle:

<ListView SelectionChanged="CheesePlate_SelectionChanged">
  <!-- ... -->
</ListView>

When an item is selected we open the bottom app bar, and make it sticky. When the user taps on another item, the app bar remains open. In the standard (non-sticky) mode, when the user taps another item that item is not selected, but the tap event is wasted to close the app bar. I can assure you: most users don’t like that! Alternatively you could close the app bar using a timer. Anyway, here’s the event handler that opens and closes the bottom app bar:

private void CheesePlate_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (Device.Instance.InputMode == InputMode.TouchOptimized && e.AddedItems.Count == 1)
    {
        this.BottomAppBar.IsOpen = true;
        this.BottomAppBar.IsSticky = true;
    }
    else
    {
        this.BottomAppBar.IsOpen = false;
    }
}

By the way, the Mail app uses yet another approach. When you tap a message, it is highlighted, but the command bar remains closed:

Mail_Touch_1

When you tap the check box in the item template, the command bar pops open:

Mail_Touch_2

As you see, there are a lot of options available to make the manipulation of a list of items more user friendly, based on the capabilities of the device. I focused on techniques that can be implemented easily in existing Windows 8.1 Store apps: all code is in the views and in high level framework classes.

The sample app is available on GitHub. It was built using Visual Studio 2013 Update 4 and Visual Studio 2015 RC (yes: the solution and project files are interchangeable).

Enjoy,

XAML Brewer

Using the OneDrive SDK in universal apps

This article shows how to use the new OneDrive SDK in a Windows universal app. It presents a sample app with the following features:

  • Singing in with a Windows account.
  • Creating a working folder. 
  • Browsing the content of a working folder.
  • Downloading a file.
  • Uploading a file.
  • Deleting a file.
  • Fetching the changes in a folder since the last check.

Here’s how that app looks like on the PC (it also runs on the phone):

screenshot_04042015_084243

Some weeks ago, Microsoft launched a new OneDrive API with a couple of new features, like easy synchronization and support for very large files. The API also came with three sample projects, one in HTML/Javascript,, one in WinForms/C#, and one for Android (yup: no XAML). The Windows Forms sample contains the source of the new OneDrive SDK. And I quote: ‘An example library for interacting with OneDrive. This library is implemented as a Portable Class Library and can be used on a number of target platforms including .NET Framework 4.5, Windows 8, Windows Phone 8.1, and Windows Phone Silverlight 8’. The SDK is a lightweight wrapper around the OneDrive OData REST API.

Here are some of the key classes in this new SDK:

  • The ODItem class represents a file or a folder on your OneDrive. It contains all their properties, such as path, status, size, and thumbnail.
  • The ODConnection class represents your connection to your OneDrive. It contains the CRUD methods to work with files and folders.
  • The IAuthenticationInfo interface describes the authentication requirements. The SDK does not contain an implementation since that depends on the technology you’re using.

 SDK_API

Authenticating to Live

You have to provide an authentication infrastructure to use the SDK. It expects a class that gets a token for your Windows Account, and that implements the SDK’s IAuthenticationInfo interface. Of course you could rely on the Live SDK to authenticate (instructions can be found here), but the OneDrive SDK is actually a replacement for this Live SDK. So it makes more sense to create (or find) a lightweight class on top of the Live OAuth services. I did not built class from scratch: fortunately I could reuse the authentication and authorization calls from another sample, the OneNote API universal app sample. After all, both OneNote and OneDrive are both Live services that sit behind the same authentication infrastructure.

The authentication is done through an OnlineIdAuthenticator instance.

Here’s the beef of the code:

// TODO: Replace the below scopes with the least permissions your app needs
private const string Scopes = "wl.signin onedrive.readwrite";

// TODO: Replace the below ClientId with your app's ClientId.
private const string ClientId = "<YOUR APP's CLIENT ID HERE>";

private const string MsaTokenRefreshUrl = "https://login.live.com/oauth20_token.srf";
private const string TokenRefreshContentType = "application/x-www-form-urlencoded";
private const string TokenRefreshRedirectUri = "https://login.live.com/oauth20_desktop.srf";

private const string TokenRefreshRequestBody = "client_id={0}&redirect_uri={1}&grant_type=refresh_token&refresh_token={2}";

/// <summary>
/// Gets a valid authentication token. Also refreshes the access token if it has expired.
/// </summary>
/// <remarks>
/// Used by the API request generators before making calls to the OneNote and OneDrive APIs.
/// </remarks>
/// <returns>valid authentication token</returns>
internal static async Task<string> GetAuthToken()
{
    if (String.IsNullOrWhiteSpace(_accessToken))
    {
        try
        {
            var serviceTicketRequest = new OnlineIdServiceTicketRequest(Scopes, "DELEGATION");
            var result =
                await Authenticator.AuthenticateUserAsync(new[] { serviceTicketRequest }, CredentialPromptType.PromptIfNeeded);
            // TODO: catch exception when user says no
            if (result.Tickets[0] != null)
            {
                _accessToken = result.Tickets[0].Value;
                _accessTokenExpiration = DateTimeOffset.UtcNow.AddMinutes(AccessTokenApproxExpiresInMinutes);
            }
        }
        catch (Exception ex)
        {
            // Authentication failed
            if (Debugger.IsAttached) Debugger.Break();
        }
    }
    await RefreshAuthTokenIfNeeded();
    return _accessToken;
}

/// <summary>
///     This method tries to refresh the access token. The token needs to be
///     refreshed continuously, so that the user is not prompted to sign in again
/// </summary>
/// <returns></returns>
private static async Task AttemptAccessTokenRefresh()
{
    var createMessage = new HttpRequestMessage(HttpMethod.Post, MsaTokenRefreshUrl)
    {
        Content = new StringContent(
            String.Format(CultureInfo.InvariantCulture, TokenRefreshRequestBody,
                ClientId,
                TokenRefreshRedirectUri,
                _refreshToken),
            Encoding.UTF8,
            TokenRefreshContentType)
    };

    var httpClient = new HttpClient();
    HttpResponseMessage response = await httpClient.SendAsync(createMessage);
    await ParseRefreshTokenResponse(response);
}

In a universal app, the very first time that GetAuthToken is called, the user has to give consent, based on the Scopes in the code (wl.signin and onedrive.readwrite):

Consent

This only works if your universal app is registered in the Store(s). Otherwise you don’t have a Client ID.

Here’s how to find the Client ID of your app. Register your app in the Store and associate is with your project. Then go to the app’s Services configuration in the Store dashboard. From there you can follow the link to the Live Services site that reveals the Client ID:

app_services

app_live

Connecting to your OneDrive

Once authenticated, all you need to do is present your token to the OneDrive SDK through the IAuthenticationInfo interface to create an ODConnection that gives you access to a all the API calls. To protect my viewmodels from the internals of the SDK, I created my own MyOneDrive class in between. Here’s its Login method:

/// <summary>
/// Login and create the connection, if necessary.
/// </summary>
public static async Task Login()
{
    if (Connection == null || !LiveOAuth.IsSignedIn)
    {
        // Get an OAuth2 access token through REST.           
        var token = await LiveOAuth.GetAuthToken();

        // Initialize connection
        Connection = new ODConnection("https://api.onedrive.com/v1.0", new OneDriveSdkAuthenticationInfo(token));
    }
}

The rest of the methods in that class will throw an error if the user is not authenticated. They all start with a call to EnsureConnection:

private static void EnsureConnection()
{
    if (Connection == null || !LiveOAuth.IsSignedIn)
    {
        throw new Exception("You're not logged in.");
    }
}

Browsing OneDrive content

For walking the OneDrive tree, you start with getting a reference to the root. And that’s just what GetRootItemAsync does:

// Connect to OneDrive root.
var rootFolder = await Connection.GetRootItemAsync(ItemRetrievalOptions.DefaultWithChildren);

From there you can navigate on the Connection, and enumerate or search for folders and files using methods such as GetChildrenOfItemAsync and SearchForItemsAsync. Some of these calls are also available on ODItem as extension methods. For the REST (pun intended) that ODItem class is just a JSON data transfer object. Here’s how the MyOneDrive class fetches the files in a specific folder. Since folders can be large –look at your pictures/camera roll folder- the API provides paging. So you have to iterate:

public static async Task<IEnumerable<ODItem>> GetFiles(ODItem parentItem)
{
    EnsureConnection();

    List<ODItem> result = new List<ODItem>();
    var page = await parentItem.PagedChildrenCollectionAsync(Connection, ChildrenRetrievalOptions.Default);

    var list = from f in page.Collection
                where f.File != null
                select f;

    result.AddRange(list);

    while (page.MoreItemsAvailable())
    {
        list = from f in page.Collection
                where f.File != null
                select f;

        result.AddRange(list);

        page = await page.GetNextPage(Connection);
    }

    return result;
}

To download the contents of a file, just call GetContentsStreamAsync:

public static async Task<Stream> DownloadFile(ODItem item)
{
    EnsureConnection();

    return await item.GetContentStreamAsync(Connection, StreamDownloadOptions.Default);
}

Modifying files and folders

Typically an app that uses OneDrive services would have a working folder that contains all its files. That concept is supported by the OneDrive API through the App Folder: Apps/YourAppName is the pattern. This concept does not exist in the SDK (yet).

The app’s working folder needs to be created. Here’s the method that I call after signing in. It looks for a folder with a specific name (directly under the root folder), and creates it if necessary:

public static async Task<ODItem> SetWorkingFolder(string folderName)
{
    EnsureConnection();

    // Connect to OneDrive root.
    var rootFolder = await Connection.GetRootItemAsync(ItemRetrievalOptions.DefaultWithChildren);

    // Search working folder
    var existingItem = (from f in rootFolder.Children
                        where f.Name == folderName
                        select f).FirstOrDefault();

    if (existingItem != null)
    {
        if (existingItem.Folder == null)
        {
            // There is already a file with this name.
            throw new Exception("There is already a file with this name.");
        }
        else
        {
            // The folder already exists.
            WorkingFolder = existingItem;
            return WorkingFolder;
        }
    }

    var newFolder = await Connection.CreateFolderAsync(rootFolder.ItemReference(), folderName);
    WorkingFolder = newFolder;
    return WorkingFolder;
}

To upload a new or a modified file, just call PutNewFileToParentItemAsync:

public static async Task<ODItem> SaveFile(ODItem parentItem, string filename, Stream fileContent)
{
    EnsureConnection();

    return await Connection.PutNewFileToParentItemAsync(parentItem.ItemReference(), filename, fileContent, ItemUploadOptions.Default);
}

Of course, there’s also a DeleteItemAsync:

public static async Task<bool> DeleteItem(ODItem item)
{
    EnsureConnection();

    return await Connection.DeleteItemAsync(item.ItemReference(), ItemDeleteOptions.Default);
}

As you see, CRUD operations on OneDrive files and folders are easy.

Enumerating changes

A fantastic new feature in the OneDrive API is the ability to check for changes in a folder in an incremental way. The ViewChangesAsync method returns not only the changes to a folder, but also a token that the app can store (e.g. in its settings). That token can then be presented in the next call to ViewChangesAsync to get the delta. The ODViewChangesResult contains details on the items that were added, modified, or deleted since the token was generated.

There are some apps out there that use a OneDrive folder to synchronize their working data across devices. With the ‘old’ Live SDK that is a hard and error prone job (sample code here), with the new OneDrive API this becomes a piece of cake.

Here’s the ViewChanges from the MyOneDrive class. It stores the token in a variable. It also allows to reset the token, in which case a full list of children from the folder is returned. I’ll explain this in a minute:

private static string Token = string.Empty;

/// <summary>
/// Returns the list of changes to the working folder since the previous call.
/// </summary>
/// <param name="reset">Whether or not to reset the token.</param>
/// <remarks>In the first call and whenever the token is reset, the whole folder's content is returned.</remarks>
public static async Task<ODViewChangesResult> ViewChanges(bool reset = false)
{
    EnsureConnection();

    if (reset)
    {
        Token = null;
    }

    var options = string.IsNullOrEmpty(Token) ? ViewChangesOptions.Default : new ViewChangesOptions() { StartingToken = Token };

    var result = await Connection.ViewChangesAsync(WorkingFolder.ItemReference(), options);
    Token = result.NextToken;

    return result;
}

In a real-world app, I assume that paging will also be required here. The default is 200 changes per page.

Wrap-up

Here are the classes that I created to use the new OneDrive SDK in a universal app:

  • LiveOAuth takes care of the authentication. It’s largely copied from the sample OneNote authenticator.
  • OneDriveSdkAuthenticationInfo makes the previous class compatible with the OneDrive SDK by implementing the IAuthentication interface.
  • MyOneDrive is an MVVM service that provides a friendly facade to the SDK.

This is the corresponding class diagram:

My_API

The full documentation of the new OneDrive SDK is coming soon, I also assume that Microsoft will publish it as a Nuget package.

I’m already a fan of this SDK: it’s more lightweight than the full Live SDK (which requires a local install on the development machine) and it comes with very useful new features. It’s also good to see the full source on GitHub, since some of our customers don’t like black box dll’s in their projects.

A warning for Windows Phone developers

Due to asynchronicity and caching there may be some delays in getting the exact current state of your OneDrive content. The following animation shows the sample app running on a phone. You’ll notice the following:

  • User tries to browse content without signing in, and gets an error.
  • User signs in.
  • User fetches content of the working folder. 8 sample files are returned.
  • User selects file 1, and successfully deletes it.
  • User fetches changes. The token was empty so the full content of the working folder is returned. File 1 is not in this list.
  • User selects file 2, and successfully deletes it.
  • User fetches changes. The deletion of file 2 is returned.
  • User browses content of working folder. File 1 and File 2 are still returned.
  • User fetches changes with an empty token. File 2 is still in this list.

 Phone_Sample

I was not able to reproduce this delay on the PC. There the information was always up-to-date, after all a PC is continuously synchronizing that drive.

So you may not assume that the information is real-time. But don’t let that stop you: it’s probably real-enough time.

Source code

The full source code of the sample includes a non-maintained version of the source code of the OneDrive SDK, and lives on GitHub. It was written in Visual Studio 2013. [The odds are high that this will be my last blog project in that version.]

Enjoy!

XAML Brewer

An animated ScrollToSection for the Universal Hub Control

n this article I show a way to animate the scrolling to a particular HubSection of a Hub in a Universal XAML Windows app. The native Hub.ScrollToSection method comes without such an animation: instead of smoothly scrolling, it jumps directly to the target section. That’s quite scary. A lot of developers seem to encounter this problem, but I didn’t find a working solution anywhere. So here are my 2 cents.

The sample app represents the type of Windows Phone application that I created this animation for. The app’s main structure is a Hub. The first HubSection in this hub acts a menu: it contains a list of hyperlinks to the other sections in the hub. The rest of the navigation buttons are just there to verify the algorithm: it should be possible to scroll from whatever section to whatever other section, backward as well as forward:

wp_ss_20150223_0001 wp_ss_20150223_0008 wp_ss_20150223_0005 wp_ss_20150223_0006

 

The Hub itself does not have a HorizontalOffset or so. The property we need to animate here is found in its internal structure: the HorizontalOffset of the embedded ScrollViewer control. Unfortunately this is a read-only property, we can not change it through a DoubleAnimation in a StoryBoard. The scroll position can only be changed by calling one of the ChangeView method overloads. Oddly enough, if you want animation, you have to call the ChangeView that allows you to disable … animation. Here’s the official description of the disableAnimation parameter: “True to disable zoom/pan animations while changing the view; otherwise, false. The default is false”. Well, that default value ruins scrolling on the phone. The default animation is visually empty, but it probably takes some time and during that time it absorbs your own animations. So you have to explicitly disable the system animation by setting the parameter to True.

The confusion doesn’t stop here. This is the official description of the horizontalOffset parameter, the first in the ChangeView call: “A value between 0 and ScrollableWidth that specifies the distance the content should be scrolled horizontally.” To me that  sounds like the method expects a delta; e.g. +10 if you want to scroll 10 pixels to the right. It turns out that this horizontalOffset is actually the target position: with +10 you navigate to the 10th pixel in the Hub.

So here’s a working implementation of smoothly scrolling to a specific section in a hub. We first calculate the absolute current position and the relative position of the target section. Then we start to scroll towards the target in a number of steps, with an increasing wait time between the steps to simulate an easing function. That sounds cheap, but it does the trick:

public async static Task ScrollToSectionAnimated(this Hub hub, HubSection section)
{
    // Find the internal scrollviewer and its current horizontal offset.
    var viewer = hub.GetFirstDescendantOfType<ScrollViewer>();
    var current = viewer.HorizontalOffset;

    // Find the distance to scroll.
    var visual = section.TransformToVisual(hub);
    var point = visual.TransformPoint(new Point(0, 0));
    var offset = point.X;

    // Scroll in a more or less animated way.
    var increment = offset / 24;
    for (int i = 1; i < 25; i++)
    {
        viewer.ChangeView((i * increment) + current, null, null, true);
        await Task.Delay(TimeSpan.FromMilliseconds(i * 20));
    }
}

The ScrollToSectionAnimated method is implemented as an extension method. I added some extra methods in the surrounding class, to fetch the current section of the hub, and the index of this section. These make particularly sense in a Windows Phone app, where there is only one section visible. That’s the first one in Hub.SectionsInView:

public static HubSection CurrentSection(this Hub hub)
{
    if (hub.Sections.Count > 0)
    {
        return hub.SectionsInView[0];
    }
    else
    {
        return null;
    }
}

public static int CurrentIndex(this Hub hub)
{
    if (hub.Sections.Count > 0)
    {
        return hub.Sections.IndexOf(hub.CurrentSection());
    }
    else
    {
        return -1;
    }
}

So here’s the code to scroll to the next and the last section, starting from the current position in the hub:

private async void ScrollToNext_Clicked(object sender, RoutedEventArgs e)
{
    // Find out where we are.
    var index = this.MainHub.CurrentIndex();

    await this.MainHub.ScrollToSectionAnimated(this.MainHub.Sections[index + 1]);
}

private async void ScrollToLast_Clicked(object sender, RoutedEventArgs e)
{
    await this.MainHub.ScrollToSectionAnimated(this.MainHub.Sections.Last());
}

Here's how the scrolling looks like in action: