Skip to content
September 11, 2010 / slimcode

Detect when a ListBox scrolls to its end (WP7)

I wanted to fetch and add new items to a ListBox when it scrolls to its end, to reproduce infinite scrolling.

Fellow tweep @GaryGJohnson pointed me to the ScrollViewer’s VerticalOffset, ExtentHeight and ViewportHeight, which would give me all the maths I needed.

Unfortunately, there it no scrolling events available, either on the ListBox or the inner ScrollViewer. From what I understand, I could have received change notifications directly from the ScrollViewer’s VerticalOffset property if I were to work with SL4, but not with SL3 on Windows Phone 7. My friend Jacques came to the rescue! He suggested I create myself a custom DependencyProperty with a changed notification, and bind it to the ScrollViewer’s VerticalOffset.

 

My ListBox declaration
<ListBox x:Name="businessesListBox"
         ItemsSource="{Binding Listings}"
         ItemTemplate="{StaticResource ListingItemTemplate}"
         SelectionChanged="businessesListBox_SelectionChanged"
         Style="{StaticResource BusinessListBoxStyle}" />

 

From Blend, I right-clicked my ListBox, chose “Edit Template” –> “Edit a copy” to add that BusinessListBoxStyle, and added a handler for the ScrollViewer’s Loaded event.

 

My ListBox’ Style
<Style x:Key="BusinessListBoxStyle" TargetType="ListBox">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <ScrollViewer x:Name="scrollViewer"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}"
                              Background="{TemplateBinding Background}"
                              Foreground="{TemplateBinding Foreground}"
                              Padding="{TemplateBinding Padding}"
                              Loaded="ScrollViewer_Loaded">
                    <ItemsPresenter/>
                </ScrollViewer>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

 

I created a custom property in my PhoneApplicationPage.

 

My custom property
public static readonly DependencyProperty ListVerticalOffsetProperty = DependencyProperty.Register(
  "ListVerticalOffset",
  typeof( double ),
  typeof( SearchBusinessResultsPage ),
  new PropertyMetadata( new PropertyChangedCallback( OnListVerticalOffsetChanged ) ) );

public double ListVerticalOffset
{
  get { return ( double )this.GetValue( ListVerticalOffsetProperty ); }
  set { this.SetValue( ListVerticalOffsetProperty, value ); }
}

 

From the ScrollViewer’s Loaded handler, I just need to bind my custom property to to the ScrollViewer’s VerticalOffset. Notice I keep a reference on the ScrollViewer, since it’s in a ContentTemplate.

 

ScrollViewer’s Loaded handler
private ScrollViewer _listScrollViewer;

private void ScrollViewer_Loaded( object sender, RoutedEventArgs e )
{
  _listScrollViewer = sender as ScrollViewer;

  Binding binding = new Binding();
  binding.Source = _listScrollViewer;
  binding.Path = new PropertyPath( "VerticalOffset" );
  binding.Mode = BindingMode.OneWay;
  this.SetBinding( ListVerticalOffsetProperty, binding );
}

 

The final job is to verify how far the scrolling is every time my custom property changes (thus every time the ScrollViewer’s VerticalOffset changes). I also make sure not to trigger updates more than once per ScrollableHeight.

 

Scrolled to the bottom?
private double _lastFetch;

private static void OnListVerticalOffsetChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e )
{
  SearchBusinessResultsPage page = obj as SearchBusinessResultsPage;
  ScrollViewer viewer = page._listScrollViewer;

  if( viewer != null )
  {
    if( page._lastFetch < viewer.ScrollableHeight )
    {
      // Trigger within 1/4 the viewport.
      if( viewer.VerticalOffset >= viewer.ScrollableHeight – viewer.ViewportHeight / 4 )
      {
        page._lastFetch = viewer.ScrollableHeight;
        this.ViewModel.FetchNextPage();
      }
    }
  }
}

 

As you can see, you can adjust how close to the end you trigger an update. There is one final gotcha you much be warned of. Make sure your “FetchNextPage” equivalent prevents reentrancy, just in case adding a new item to your bound collection causes the OnListVerticalOffset event to fire (we never know) and another fetch is triggered.

About these ads

14 Comments

Leave a Comment
  1. Laurent Bugnion / Sep 11 2010 14:54

    Nice work!

  2. Pauliom / Sep 14 2010 15:56

    Nice. I was thinking about this too, although I was considering detecting when the last bound data is fetched and for that to fire event

  3. Thomas / Dec 20 2010 09:52

    Since it’s a static method, how can you put “this.ViewModel.FetchNextPage();” ?

    • slimcode / Dec 20 2010 10:03

      Ah! And nobody noticed before? It’s obviously “page.”, not “this.”. Will fix. Thanks!

      • slimcode / Dec 20 2010 10:11

        Meh. I can’t edit the code snippets without the post becoming unreadable, and Windows Live Writer doesn’t let me change the HTML. People will have to read the comments to realize the error. ;)

        It’s the single line of code anyone using this technique must replace with their own code anyway.

      • Thomas / Dec 20 2010 11:00

        Awesome ! I could have looked a little bit more to get what you were doing. Thanks a lot !

  4. Will / Jan 10 2011 23:42

    Thanks for this! This worked great in my application, and is greatly appreciated :)

    cheers

  5. Laranzu / Jan 19 2012 16:15

    Hello
    I’ve trying to get your sample all together but i can’t make it.
    Every time i’m getting an error either in the building phase the execution.

    Could you provide a sample code working? :)

    Thanks
    Laranzu

  6. Prakash / Mar 13 2012 08:54

    I liked the approach of it. What will be the differences when the listbox is in a UserControl? Because it is not working for me when the list box is in user control.

    • Prakash / Mar 13 2012 13:01

      Sorry dude, Cancel my comment. I did a mistake in implementing this. Mine is a search app so I need to clear the _lastFetch variable and I forgot to do that. I was blindly implementing it earlier since I am new to Dependencies properties and Silverlight. I read on them now and figured it. Thanks for the answer.

  7. virtyaluk / Apr 3 2012 19:43

    bad code, it’s not work.
    try to search working code

  8. Shilpa / Aug 8 2013 01:19

    Very good post..helped me a lot

  9. Shilpa / Aug 13 2013 06:34

    hi, I have many lists in panoramic page and this code is not working for all lists. Also after certain time its not detecting end of list box. Please help me

Trackbacks

  1. Дайджест технических материалов #5 (Windows Phone 7) - Oleksandr Krakovetskiy blog - Microsoft User Group Винница

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: