Pull to refresh for WinRT

'Pull to refresh' has been a standard UX pattern which is expected in all apps, on all platforms and I've made it available for Windows Phone 8.1 and Windows 8.1 (Universal) apps. 

The solution has been wrapped in a single control based on a ScrollViewer which is ready to use right out of the box.

<controls:PullToRefreshScrollViewer Grid.Row="1">
 ..Content goes here..
</controls:PullToRefreshScrollViewer>

Download and try the source

Pro tip: The sample works for Windows 8.1 apps as well! 

Should you need Pull to refresh for Listviews and gridviews it modifying this implementation should be fairly easy. So don't hesitate giving it a shot! 



 

Pull to refresh implementation explained

<controls:PullToRefreshScrollViewer
Grid.Row="1"
RefreshContent="PullToRefreshScrollViewer_RefreshContent"
RefreshCommand="{Binding RefreshCommand}"
ArrowColor="#004050"
RefreshText="Release to refresh"
PullText="Pull to refresh" />

This is the available properties in the control (PullToRefreshScrollViewer.cs)

  • RefreshContent (event)
  • RefreshCommand (Command)
  • ArrowColor (Brush)
  • RefreshText (string)
  • PullText (string)

The Command and event is invoked when the user pulls down and releases appropriately. 
ArrowColor is the brush of the arrow that animates once the user pulls down. During the pull there's two different texts - RefreshText and PullText
 

Deep dive

As far as I know there isn't a smooth and easy way to learn from the scrollviewer if the scrollviewer offset is negative.
After endless amounts of googling I couldn't figure out a way to do it so I decided to tweak the scrollviewer using two timers, negative margin, composite translate transform and some code to figure out whether the scrolling has a negative offset and hereby means to trigger the pull to refresh.
This solution is far from ideal (bit of a hack really) but get's the job done.


Loading the control and the timers

The control requires some timers in order to check whether the user scrolls into negative space or not. 
Note: The timers will only run as long as the scrolling offset is 0.

private void PullToRefreshScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
    timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromMilliseconds(100);
    timer.Tick += Timer_Tick;

    compressionTimer = new DispatcherTimer();
    compressionTimer.Interval = TimeSpan.FromSeconds(1);
    compressionTimer.Tick += CompressionTimer_Tick;

    timer.Start();
}


Working with the offset

Most of the magic comes from the ScrollViewers ViewChanged event which is invoked when the user scrolls. The event is fired continuously, multiple times in the second, and it tells us about the current offset. This is event looks whether we're at the top (offset 0) or not.
If we're at the top we start some timers used to determine if the users decides to pull to refresh.

private void ScrollViewer_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
{
    if (e.NextView.VerticalOffset == 0)
    {
        timer.Start();
    }
    else
    {
        if (timer != null)
        {
            timer.Stop();
        }

        if (compressionTimer != null)
        {
            compressionTimer.Stop();
        }

        isCompressionTimerRunning = false;
        isCompressedEnough = false;
        isReadyToRefresh = false;

        VisualStateManager.GoToState(this, VisualStateNormal, true);
    }
}

Determining pull to refresh by using the timers

The timer will tick every 100 ms and will use the TransformToVisual method to detect it's bound and compare it with the expected offset. If the user scrolls far enough and stays there for 1 second - which is checked by using another timer (compression timer) - a boolean property, IsReadyToRefresh, is set to true.
Once the offset goes back to 0 we check IsReadyToRefresh is true and invoke the refresh events to the world. 

private void Timer_Tick(object sender, object e)
{
    if (containerGrid != null)
    {
        Rect elementBounds = pullToRefreshIndicator.TransformToVisual(containerGrid).TransformBounds(new Rect(0.0, 0.0, pullToRefreshIndicator.Height, RefreshHeaderHeight));

        var compressionOffset = elementBounds.Bottom;
        Debug.WriteLine(compressionOffset);

        if (compressionOffset > offsetTreshhold)
        {
            if (isCompressionTimerRunning == false)
            {
                isCompressionTimerRunning = true;
                compressionTimer.Start();
            }

            isCompressedEnough = true;
        }
        else if (compressionOffset == 0 && isReadyToRefresh == true)
        {
            InvokeRefresh();
        }
        else
        {
            isCompressedEnough = false;
            isCompressionTimerRunning = false;
        }
    }
}

We use a visual state to change the text and rotate the arrow. If we after a second stayed long enough within the right bounds, we've set isReadyToRefresh to true and show the "release ready" visual state telling the user it will refresh once released.

private void CompressionTimer_Tick(object sender, object e)
{
    if (isCompressedEnough)
    {
        VisualStateManager.GoToState(this, VisualStateReadyToRefresh, true);
        isReadyToRefresh = true;
    }
    else
    {
        isCompressedEnough = false;
        compressionTimer.Stop();
    }
}


Telling the page and ViewModel about the refresh

The final part is telling the world that it should refresh the content. The world outside the control can listen by using an event or a command. If either is available they'll be raised once the InvokeRefresh is called and the control returns to it's original state.

private void InvokeRefresh()
{
    isReadyToRefresh = false;
    VisualStateManager.GoToState(this, VisualStateNormal, true);

    if (RefreshContent != null)
    {
        RefreshContent(this, EventArgs.Empty);
    }

    if (RefreshCommand != null && RefreshCommand.CanExecute(null) == true)
    {
        RefreshCommand.Execute(null);
    }
}

Final words

This is a quite hacky way to do it, but I couldn't find any other way when working with a scrollviewer. The solution is plug an play and works right out of the box. It get's the job done and hopefully this will be a lot simpler with Windows 10.

I'd love to hear your thoughts, improvement and feedback in the comments below - so don't hesitate writing something! 

P.S Make sure you follow me on twitter @deanihansen for more news, design tips, articles and how-to's.

Natural User Interface implementation

Filling out forms can sometimes be a quite boring and in some cases even confusing experience for your users. One way to ease up the experience is to use a UX and interface design principle called 'Natural User Interface' (NUI) which basically means to use a familiar and real life environment which the user is already familiar with from the everyday life. 
There's a lot of other aspects in NUI but the core idea is to make the interaction between user and computer more seamless.

This sample demonstrates one approach to this principle where the user have to fill out a form by doing two selections from a list. Rather than having a static standard form with a header and a few combobox'es this approach uses a longer sentence with buttons inside which allows the user to choose a selection from a list of items. 
The buttons is highlighted using color differences and should feel completely intuitive.
 

Download source & sample


The sample both works for Windows Phone and Windows Store. 

 

Source code explained

The UI is created based on a sentence and a home made syntax indicating where we need the lists: 

var text = "I would like to see blendrocks focus more on #1 and less on #2"

First we break up the sentence into words create a list of words that should either be displayed as text or as a selection input. The hashtag indicates that we need a list of options. 
The list will be bound to an itemscontrol that will populate what looks like a textblock with some inline functionality. 


Viewmodel

public MainViewModel()
{
    ContentList = new ObservableCollection<ContentBase>();
    LoadData();
}

private void LoadData()
{
    var text = "I would like to see blendrocks focus more on #1 and less on #2";
    var words = text.Split(' ');
    foreach (var word in words)
    {
        if (word.StartsWith("#"))
        {
            var items = new List<string>();

            if (word == "#1")
            {
                items.Add("controls styling");
                items.Add("C# code samples");
                items.Add("complete designs");
                items.Add("custom animations");
            }
            else if (word == "#2")
            {
                items.Add("kitty pictures");
                items.Add("movie quotes");
                items.Add("gardening tips");
                items.Add("indoor swim guides");
            }

            ContentList.Add(new ContentSelection() { Items = items, SelectedItem = items[0], Type = ContentType.ComboBox });
        }
        else
        {
            ContentList.Add(new ContentText() { Text = word, Type = ContentType.Text });
        }
    }
}

 

Itemscontrol and the resources

As said in the UI we'll have an itemsControl which uses a template selector to show either a textblock or a button depending whether we're showing a ContentText or ContentSelection object. 
The itemscontrol will wrap it nicely like a normal sentence because it uses a custom ItemsPanel which my friend Daniel Vistisen created.

<Page.Resources>
    <DataTemplate
        x:Key="ContentSelectionTemplate">
        <local:ContentSelectionUserControl />
    </DataTemplate>
    <DataTemplate
        x:Key="TextTemplate">
        <TextBlock
            Text="{Binding Text}"
            Style="{StaticResource CustomTextBlockStyle}" />
    </DataTemplate>
    <local:MainPageTemplateSelector
        TextTemplate="{StaticResource TextTemplate}"
        ContentSelectionTemplate="{StaticResource ContentSelectionTemplate}"
        x:Name="MainPageTemplateSelector" />
</Page.Resources>

-----
<ItemsControl
    Grid.Row="1"
    Margin="0"
    ItemTemplateSelector="{StaticResource MainPageTemplateSelector}"
    ItemsSource="{Binding ContentList}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:WrapPanel
                HorizontalAlignment="Left"
                VerticalAlignment="Center" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>


Displaying the selection options

We use a regular button and a flyout to show the selections - everything bundled in a usercontrol called ContentSelectionUserControl.
I tried mutiple approaches including a combobox to show the selection options next to the text. After trying multiple things I concluded the flyout-button approach offered the most flexibility and extendibility so I chose to go with that one.

<UserControl
    x:Class="Natural_input.ContentSelectionUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Natural_input"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    <Grid>
        <Button
            Content="{Binding SelectedItem}">
            <Button.Flyout>
                <Flyout
                    x:Name="flyout"
                    FlyoutPresenterStyle="{StaticResource CustomFlyoutPresenter}">
                    <ListBox
                       x:Name="list"
                       SelectionChanged="list_SelectionChanged"
                       ItemsSource="{Binding Items}"
                       SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                       ItemContainerStyle="{StaticResource CustomListBoxItemStyle}"
                       Style="{StaticResource CustomListBoxStyle}">
                    </ListBox>
                </Flyout>
            </Button.Flyout>
        </Button>
    </Grid>
</UserControl>

Note: To get everything to look good I created some styles and modified the templates. All this is not subject to explanation in this blogpost, but you're more than welcome to ask about it in the comments should you have any questions.


Conclusion

This is a quite new UX pattern on the app platform and it was a lot of fun to create it. It uses a lot of styles and templates to get the right visual look and feel. 
The pattern itself offers a lot of possibilities that I hope I'll investigate in the future.
I hope this has inspired you consider creating user input in new ways.
Feel free to let me know what you think in the comments or on twitter. I'd love to hear from you.
Happy coding! 

P.S Make sure you follow me on twitter @deanihansen for more news, design tips, articles and how-to's.