Pull-to-refresh on a Windows 10 UWP

Share this:

If you’ve ever tried to pull this off, you’ve likely either A) pulled your hair out then drank ruthlessly in celebration or B) researched what it takes and said “yeah… looks like we’re going with a button”

I was the same way.

While perusing through NuGet yesterday I found a (relatively) new project, PullToRefresh.UWP. “WHAT?!” I thought and immediately hit the project page link. Unfortunately the entire site is in Chinese (though IE does a decent job of translating w/ the Bing Bar) so I thought I’d write this to help out other devs that might be trying to use it, and show how I threw it in to my latest UWP.

Obviously, first thing’s first:

nuget install PullToRefresh.UWP

Once you’ve done that it’s time to integrate! As the project page shows with its XAML example, the most basic implementation looks like:

But let’s break this apart in to the parts that matter. If you look at what PullToRefresh.UWP offers you, you have a few other controls, namely PullToRefreshScrollViewer. But alas, they’re deprecated and tell you to use the Box. I like this approach as it means you can literally shove *any* scrolling container in to the Box and then the work is done for you.

When you use the base implementation, your refresh trigger height is 80px (you can find this by inspecting an instance of PullToRefreshBox and looking at RefreshThreshold). The nice thing is you can change this to be whatever you want.

The default implementation also includes a nice “progressively drawn” circle showing the progress toward the threshold, but alas the “Pull” and “Refresh” wording is in Chinese. To change this you need to template the Top Indicator of the Box like so:

The TopIndicatorTemplate is set to contain a default instance of PullRefreshProgressControl, with the Pull and Release text set to what we want

The Progress property of the ProgressControl is bound to the datacontext of the IndicatorTemplate. This is by default set to the % complete the box has been “pulled” relative to its threshold.

When the Progress value is set, the Control is doing work internally to paint the circle that gradually completes, and then switch the Visual State Manager to a “ReleaseToRefresh” state value. This state is what changes the text from the PullToRefreshText value to the ReleaseToRefreshText value. On the project page, you can see a fully templated instance of the progress control which uses only text and reacts to this Visual State change.

But what if we want to go to the next level and make the style completely our own?

It’s as simple as our good friend UserControl. I created one like this:

I give a DependencyProperty to bind to the Progress value, same as the out-of-the-box ProgressControl that was shown earlier

I provide Style DependencyProperties for the Iconography and the Text of the control and bind the Style property of the Icon and Text elements of the control to those

Using Blend, I set up the Visual State Manager with transitions that cross-fade the “Pull” and “Refresh” texts when the user hits the threshold

In the Handler for the Progress DependencyProperty, I rotate the RelativePanel in which I put the ‘Sync’ and ‘Up’ icons based on the progress value, with a max value of 180 (this “rotates” the iconography to where the ‘Up’ icon goes from pointing down (due to its initial state of being flipped) to pointing up when the user should release to perform the refresh

Which simply puts my control in where the ProgressControl was for the OEM example. Then I style it to set the foreground of the symbol and text to my app’s secondary foreground theme brush.

Finally there was one nuance I fought with, which was the main thing that compelled me to write this post: the RefreshThreshold value. This value must be less than the total height of the contents of the IndicatorTemplate. Here’s what I mean:

Notice line #3: it specifies that the user should pull down 160px before refreshing kicks in. The problem is the size of my PullToRefresh control is going to be ‘Auto’ and not set to be 160 high, so when it gets the “Visual Size” of the element, it stops at some value < 160, so Progress never hits 1.0 (complete)

Logical step? Set the height of my control:

1:<ptr:PullToRefreshBoxGrid.Row="1"

2:RefreshInvoked="PullToRefreshBox_RefreshInvoked"

3:RefreshThreshold="160">

4:<ptr:PullToRefreshBox.TopIndicatorTemplate>

5:<DataTemplate>

6:<myControls:PullToRefreshPullProgress="{Binding}"

7:Height="160"

8:VerticalAlignment="Bottom">

Making lines 3 & 7 equal seems right, but doesn’t quite do it. Why? I have no idea, honestly. What I had to do to make this work was make the height of the Indicator Template at least +1 from the value of RefreshThreshold.

I hope this helps you to integrate this desperately-needed component in to your UWP! It’s worth noting this will work on any UWP when touch interaction is enabled. For instance, when using a mouse in an app, you can’t click & pull a scroll viewer, so that doesn’t work, but switching to touch interaction (eg: using your finger on a Surface Pro vs the trackpad) it works just like it does on Phone.