PullToRefreshListView for UWP

Hello there developer who was just asked to incorporate the PullToRefresh behavior into one of your ListViews for the UWP app you are building. And of course you are looking to steal code on the internet. No blaming, I do it all the time. I actually want you to use this.

photo-15-642x322In case you just crawled out of your cave and aren’t really sure what PullToRefresh is here you go.

 

 

After evaluating the few solutions that were available on the internet I realized none of them fulfilled my requirements perfectly so I had to make my own. To be exact I had to steal code and was hoping that after some tweaking and refactoring I might be able to make it work.

After shamelessly stealing great code from  @dotMorten ‘s blog here RefreshBox for Windows Phone 7 as well as some interesting ideas from an otherwise mediocre Microsoft sample here XamlPullToRefresh I ended up with the code below.

Enjoy!

P.S Scroll to the bottom for some interesting details about this solution.

<Geeking>

The first most interesting and important detail of this solution is the trick the Microsoft sample used to bypass the classic problem of putting a ListView inside a ScollViewer which effectively kills Virtualization. Since the PullToRefresh indicator needs to be inside and above the ScrollViewer that hosts the list the same problem applies here. To overcome this
the Microsoft sample used two custom panels (an Inner and Outer) that know each other and can “talk” to each other. The reason virtualization is killed is because when a ListView is hosted inside a ScrollViewer then it thinks it has infinite vertical space because that’s what the ScrollViewer is telling it. The solution above works around this problem by allowing the Inner panel to figure out the Outer panel’s actual visible height and thus correctly calculate it’s own actual visible height which it can get by simply subtracting the size of the PullToRefresh indicator from the Outer panel’s height.
Cool trick.

The second most interesting and important detail was figuring out if the user released their finger when the ScrollViewer was over-stretched.  The limitation of the sample is that the Refresh is scheduled to be triggered once the user overstretches even if the user decides to cancel by slowly unstretching the ScrollViewer back to the original position.
The original attempts to solve this failed because ScrollViewer only exposes the DirectManipulationStarted and DirectManipulationCompleted events and the second one fires too late. Loong after the user has released their finger. The DirectManipulationPointerReleased  event was not available unfortunately.
The trick to solve this turned out to be the use of the ViewChanging event (see line 114) and figuring out the exact moment the user released his/her finger from the screen.
When this occurs at the desired time then :

  1. The event would be Inertial.
  2. The ScrollViewer would be overstretched so VerticalOffset should be zero and
  3. The direction of the inertial movement should be upwards towards the non-stretched state.

This allowed users to change their minds if they didn’t really want to refresh.

</Geeking>

Update: Microsoft has released an updated PullToRefresh  sample that you might also want to evaluate here. There’s also the UWP Community Toolkit here.

Advertisements

WP7 vs WP8 Checkbox Style Changes

Hey. I had a small problem yesterday. Minor annoyance to be exact.

What were you trying to do?
Copy paste a WP7 control’s XAML, as is, to be used in a WP8 app.

What happened?
It didn’t work as is. A built-in control’s default style changed in WP8

Really? Which one?
The Checkbox. Well here, see for yourself :

WP7_WP8_Checkbox_Diff

What the what?? How’d that happen?
I think they just changed the controls style. Just like that.
Wait, i have a WinMerge report of the style differences as well here (D/L the html file and open it)

Tip: Open up Blend and click “Edit Template” on the Checkbox to get a copy of its style. Be sure
to do it inside the correct project each time.

Oh i see. How’d that make you feel?
I was a little bummed out..

I was bummed out

Why?
Why? Cause i am lazy and that’s extra work i didn’t want to do.

Relax. It happens. Why would Microsoft do that anyways?
Hell if i know..

Hell if i know

So what are you planning on doing?
Well, I don’t care for the new style. I want to keep the old one. At first i thought I’d make an App-wide StaticResource out of the old style.
Turns out that won’t seem to work easily. Here’s the exception i got by using the old style

CheckBoxStyleException

The new Checkbox doesn’t seem to like it’s old style anymore… Stay tuned for the solution.

-START UPDATE (WITH FIX AND BONUS TOOL) :
The fact that the old style didn’t immediately work on WP8 had to do obviously with a bunch
of missing or changed styles. Well after some dirty work here is the WP7-Like CheckBox Style for WP8 that makes it look like it did on WP7 WP7CheckBoxStyle.xaml.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Color x:Key="WP7PhoneRadioCheckBoxCheckDisabledColor">#66000000</Color>
    <Color x:Key="WP7PhoneRadioCheckBoxDisabledColor">#66FFFFFF</Color>
    <Color x:Key="WP7PhoneRadioCheckBoxPressedBorderColor">#FFFFFFFF</Color>
    <Color x:Key="WP7PhoneRadioCheckBoxColor">#BFFFFFFF</Color>
    <Color x:Key="WP7PhoneRadioCheckBoxPressedColor">#FFFFFFFF</Color>
    <Color x:Key="WP7PhoneRadioCheckBoxCheckColor">#FF000000</Color>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxBrush" Color="{StaticResource WP7PhoneRadioCheckBoxColor}"/>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxCheckDisabledBrush" Color="{StaticResource WP7PhoneRadioCheckBoxCheckDisabledColor}"/>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxDisabledBrush" Color="{StaticResource WP7PhoneRadioCheckBoxDisabledColor}"/>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxPressedBorderBrush" Color="{StaticResource WP7PhoneRadioCheckBoxPressedBorderColor}"/>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxPressedBrush" Color="{StaticResource WP7PhoneRadioCheckBoxPressedColor}"/>
    <SolidColorBrush x:Key="WP7PhoneRadioCheckBoxCheckBrush" Color="{StaticResource WP7PhoneRadioCheckBoxCheckColor}"/>

    <Style x:Key="WP7PhoneButtonBase" TargetType="ButtonBase">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
        <Setter Property="BorderThickness" Value="{StaticResource PhoneBorderThickness}"/>
        <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
        <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
        <Setter Property="Padding" Value="10,3,10,5"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonBase">
                    <Grid Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver"/>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneBackgroundBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneForegroundBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneForegroundBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0" Margin="{StaticResource PhoneTouchTargetOverhang}">
                            <ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="WP7PhoneRadioButtonCheckBoxBase" BasedOn="{StaticResource WP7PhoneButtonBase}" TargetType="ToggleButton">
        <Setter Property="Background" Value="{StaticResource WP7PhoneRadioCheckBoxBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource WP7PhoneRadioCheckBoxBrush}"/>
        <Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
        <Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="0"/>
    </Style>
    <Style x:Key="WP7CheckBoxStyle" BasedOn="{StaticResource WP7PhoneRadioButtonCheckBoxBase}" TargetType="CheckBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="CheckBox">
                    <Grid Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver"/>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="CheckBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxPressedBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="CheckBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxPressedBorderBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="CheckMark">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxCheckBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="IndeterminateMark">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxCheckBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="CheckBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="CheckBackground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="CheckMark">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxCheckDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="IndeterminateMark">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WP7PhoneRadioCheckBoxCheckDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="CheckStates">
                                <VisualState x:Name="Checked">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="CheckMark">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unchecked"/>
                                <VisualState x:Name="Indeterminate">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="IndeterminateMark">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid Margin="{StaticResource PhoneTouchTargetLargeOverhang}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="32"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid Grid.Column="0">
                                <Border x:Name="CheckBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{StaticResource PhoneBorderThickness}" Background="{TemplateBinding Background}" HorizontalAlignment="Left" Height="32" IsHitTestVisible="False" VerticalAlignment="Center" Width="32"/>
                                <Rectangle x:Name="IndeterminateMark" Fill="{StaticResource WP7PhoneRadioCheckBoxCheckBrush}" HorizontalAlignment="Center" Height="16" IsHitTestVisible="False" Visibility="Collapsed" VerticalAlignment="Center" Width="16"/>
                                <Path x:Name="CheckMark" Data="M0,123 L39,93 L124,164 L256,18 L295,49 L124,240 z" Fill="{StaticResource WP7PhoneRadioCheckBoxCheckBrush}" FlowDirection="LeftToRight" HorizontalAlignment="Center" Height="21" IsHitTestVisible="False" Stretch="Fill" StrokeThickness="3" StrokeLineJoin="Round" Visibility="Collapsed" VerticalAlignment="Center" Width="23"/>
                            </Grid>
                            <ContentControl x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="12,0,0,0" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Here is a screenshot that shows off 1) how to use the Style and 2) how it actually looks/works:

WP7_WP8_FixedStyle_Vertical
I needed some help for that painful process of finding out what style changed and how between WP7 and WP8 thats why i built a small tool :

WPThemeReviewerTool1 WPThemeReviewerTool2

I call it “WinPhone Theme Reviewer” , it targets WP8 only for now and you can download the source or xapfile.
What i did, essentially, is include MS’s Theme xaml files located “C:\Program Files (x86)\Microsoft SDKs\Windows Phone\vXX\Design\ThemeResources.xaml” for both WP71 and WP8 in the App.

Enjoy 🙂


-END UPDATE


Wait. That probably means even more default styles might be messed up..
know…We’ll just have to wait and see..

Btw. You know you are talking to yourself right?
….