Daily Gem: VB / Silverlight / WP7.5 – Infinite scrolling lists

How to raise an end-of-list reached event so you can add more items.

Step 1:  Subclass the standard Listbox with the following code

Code Snippet

  1. Imports System.Windows.Controls.Primitives
  2.  
  3. Public ClassExtendedListBox
  4.     InheritsListBox
  5.  
  6.     Protected _isBouncy As Boolean = False
  7.     PrivatealreadyHookedScrollEvents As Boolean = False
  8.  
  9.     PublicSub New()
  10.         AddHandlerMe.Loaded, NewRoutedEventHandler(AddressOfListBox_Loaded)
  11.     EndSub
  12.  
  13.     PrivateSub ListBox_Loaded(sender As Object, e As RoutedEventArgs)
  14.         Dimsb As ScrollBar = Nothing
  15.         Dimsv As ScrollViewer = Nothing
  16.         IfalreadyHookedScrollEvents Then
  17.             Return
  18.         EndIf
  19.  
  20.         alreadyHookedScrollEvents = True
  21.         Me.[AddHandler](ExtendedListBox.ManipulationCompletedEvent, DirectCast(AddressOf LB_ManipulationCompleted, EventHandler(OfManipulationCompletedEventArgs)), True)
  22.         sb = DirectCast(FindElementRecursive(Me, GetType(ScrollBar)), ScrollBar)
  23.         sv = DirectCast(FindElementRecursive(Me, GetType(ScrollViewer)), ScrollViewer)
  24.  
  25.         Ifsv IsNot Nothing Then
  26.             ‘ Visual States are always on the first child of the control template
  27.             Dim element As FrameworkElement = TryCast(VisualTreeHelper.GetChild(sv, 0), FrameworkElement)
  28.             If element IsNot Nothing Then
  29.                 Dimvgroup As VisualStateGroup = FindVisualState(element, "VerticalCompression")
  30.                 Dimhgroup As VisualStateGroup = FindVisualState(element, "HorizontalCompression")
  31.                 Ifvgroup IsNot Nothing Then
  32.                     AddHandlervgroup.CurrentStateChanging, NewEventHandler(OfVisualStateChangedEventArgs)(AddressOfvgroup_CurrentStateChanging)
  33.                 EndIf
  34.                 Ifhgroup IsNot Nothing Then
  35.                     AddHandlerhgroup.CurrentStateChanging, NewEventHandler(OfVisualStateChangedEventArgs)(AddressOfhgroup_CurrentStateChanging)
  36.                 EndIf
  37.             EndIf
  38.         EndIf
  39.  
  40.     EndSub
  41.  
  42.     PublicDelegate Sub OnCompression(sender As Object, e AsCompressionEventArgs)
  43.     PublicEvent Compression As OnCompression
  44.  
  45.     PrivateSub hgroup_CurrentStateChanging(sender AsObject, e AsVisualStateChangedEventArgs)
  46.         If e.NewState.Name = "CompressionLeft" Then
  47.             _isBouncy = True
  48.             RaiseEvent Compression(Me, NewCompressionEventArgs(CompressionType.Left))
  49.         EndIf
  50.  
  51.         If e.NewState.Name = "CompressionRight" Then
  52.             _isBouncy = True
  53.             RaiseEvent Compression(Me, NewCompressionEventArgs(CompressionType.Right))
  54.         EndIf
  55.         If e.NewState.Name = "NoHorizontalCompression" Then
  56.             _isBouncy = False
  57.         EndIf
  58.     EndSub
  59.  
  60.     PrivateSub vgroup_CurrentStateChanging(sender AsObject, e AsVisualStateChangedEventArgs)
  61.         If e.NewState.Name = "CompressionTop" Then
  62.             _isBouncy = True
  63.             RaiseEvent Compression(Me, NewCompressionEventArgs(CompressionType.Top))
  64.         EndIf
  65.         If e.NewState.Name = "CompressionBottom" Then
  66.             _isBouncy = True
  67.             RaiseEvent Compression(Me, NewCompressionEventArgs(CompressionType.Bottom))
  68.         EndIf
  69.         If e.NewState.Name = "NoVerticalCompression" Then
  70.             _isBouncy = False
  71.         EndIf
  72.     EndSub
  73.  
  74.     PrivateSub LB_ManipulationCompleted(sender AsObject, e AsManipulationCompletedEventArgs)
  75.         If _isBouncy Then
  76.             _isBouncy = False
  77.         EndIf
  78.     EndSub
  79.  
  80.     PrivateFunction FindElementRecursive(parent AsFrameworkElement, targetType As Type) As UIElement
  81.         DimchildCount As Integer = VisualTreeHelper.GetChildrenCount(parent)
  82.         DimreturnElement As UIElement = Nothing
  83.         IfchildCount > 0 Then
  84.             For i As Integer = 0 To childCount – 1
  85.                 Dim element As [Object] = VisualTreeHelper.GetChild(parent, i)
  86.                 ‘If element.[GetType]() = targetType Then
  87.                 ‘If element.GetType.FullName = targetType.FullName Then
  88.                 If element.GetType.Equals(targetType) Then
  89.  
  90.                     ReturnTryCast(element, UIElement)
  91.                 Else
  92.                     returnElement = FindElementRecursive(TryCast(VisualTreeHelper.GetChild(parent, i), FrameworkElement), targetType)
  93.                 EndIf
  94.             Next
  95.         EndIf
  96.         ReturnreturnElement
  97.     EndFunction
  98.  
  99.     PrivateFunction FindVisualState(element AsFrameworkElement, name As String) As VisualStateGroup
  100.         If element Is Nothing Then
  101.             ReturnNothing
  102.         EndIf
  103.  
  104.         Dim groups As IList(OfVisualStateGroup) = VisualStateManager.GetVisualStateGroups(element)
  105.         ForEach group As VisualStateGroupIn groups
  106.             If group.Name = name Then
  107.                 Return group
  108.             EndIf
  109.         Next
  110.  
  111.         ReturnNothing
  112.     EndFunction
  113. End Class
  114.  
  115. Public ClassCompressionEventArgs
  116.     InheritsEventArgs
  117.     PublicProperty Type() As CompressionType
  118.         Get
  119.             Return m_Type
  120.         EndGet
  121.         ProtectedSet(value AsCompressionType)
  122.             m_Type = value
  123.         EndSet
  124.     EndProperty
  125.     Private m_Type As CompressionType
  126.  
  127.     PublicSub New(type__1 AsCompressionType)
  128.         Type = type__1
  129.     EndSub
  130. End Class
  131.  
  132. Public EnumCompressionType
  133.     Top
  134.     Bottom
  135.     Left
  136.     Right
  137. End Enum

 

Step 2:  Add the following XAML to the Application.Resources section of App.xaml

XAML Snippet

  1. <StyleTargetType="ScrollViewer">
  2.     <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
  3.     <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
  4.     <Setter Property="Background" Value="Transparent"/>
  5.     <Setter Property="Padding" Value="0"/>
  6.     <Setter Property="BorderThickness" Value="0"/>
  7.     <Setter Property="BorderBrush" Value="Transparent"/>
  8.     <Setter Property="Template">
  9.         <Setter.Value>
  10.             <ControlTemplateTargetType="ScrollViewer">
  11.                 <BorderBorderBrush="{TemplateBindingBorderBrush}"BorderThickness="{TemplateBindingBorderThickness}" Background="{TemplateBinding Background}">
  12.                     <VisualStateManager.VisualStateGroups>
  13.                         <VisualStateGroup x:Name="ScrollStates">
  14.                             <VisualStateGroup.Transitions>
  15.                                 <VisualTransitionGeneratedDuration="00:00:00.5"/>
  16.                             </VisualStateGroup.Transitions>
  17.                             <VisualState x:Name="Scrolling">
  18.                                 <Storyboard>
  19.                                     <DoubleAnimation Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
  20.                                     <DoubleAnimation Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
  21.                                 </Storyboard>
  22.                             </VisualState>
  23.                             <VisualState x:Name="NotScrolling"/>
  24.                         </VisualStateGroup>
  25.                         <VisualStateGroup x:Name="VerticalCompression">
  26.                             <VisualState x:Name="NoVerticalCompression"/>
  27.                             <VisualState x:Name="CompressionTop"/>
  28.                             <VisualState x:Name="CompressionBottom"/>
  29.                         </VisualStateGroup>
  30.                         <VisualStateGroup x:Name="HorizontalCompression">
  31.                             <VisualState x:Name="NoHorizontalCompression"/>
  32.                             <VisualState x:Name="CompressionLeft"/>
  33.                             <VisualState x:Name="CompressionRight"/>
  34.                         </VisualStateGroup>
  35.                     </VisualStateManager.VisualStateGroups>
  36.  
  37.  
  38.                     <Grid Margin="{TemplateBinding Padding}">
  39.                         <ScrollContentPresenter x:Name="ScrollContentPresenter" Content="{TemplateBinding Content}"ContentTemplate="{TemplateBindingContentTemplate}"/>
  40.                         <ScrollBar x:Name="VerticalScrollBar"IsHitTestVisible="False" Height="Auto" Width="5"HorizontalAlignment="Right"VerticalAlignment="Stretch" Visibility="{TemplateBindingComputedVerticalScrollBarVisibility}"IsTabStop="False" Maximum="{TemplateBindingScrollableHeight}" Minimum="0" Value="{TemplateBindingVerticalOffset}" Orientation="Vertical"ViewportSize="{TemplateBindingViewportHeight}" />
  41.                         <ScrollBar x:Name="HorizontalScrollBar"IsHitTestVisible="False" Width="Auto" Height="5"HorizontalAlignment="Stretch"VerticalAlignment="Bottom" Visibility="{TemplateBindingComputedHorizontalScrollBarVisibility}"IsTabStop="False" Maximum="{TemplateBindingScrollableWidth}" Minimum="0" Value="{TemplateBindingHorizontalOffset}" Orientation="Horizontal"ViewportSize="{TemplateBindingViewportWidth}" />
  42.                     </Grid>
  43.                 </Border>
  44.             </ControlTemplate>
  45.         </Setter.Value>
  46.     </Setter>
  47. </Style>

You can now simply replace your ListBox references with ExtendedListBox references in your code wherever a listbox is used.

  1. <local:ExtendedListBox x:Name="lbMyList"VerticalAlignment="Top"ItemsSource="{Binding}">

 

You might handle list-end / top scroll events in your code-behind as follows:

Code Snippet

  1. Private SublbMyList_Compression(sender As Object, e As CompressionEventArgs) HandleslbMyList.Compression
  2.     DimCompressionArgs = CType(e, CompressionEventArgs)
  3.     SelectCase CompressionArgs.Type
  4.         CaseCompressionType.Bottom ‘Bottom reached… add more list items
  5.             …
  6.         CaseCompressionType.Top ‘At top  fetch and add earlier list items
  7.             …
  8.     EndSelect
  9. End Sub

Cheers.  Smile


Adapted from C# to VB, and for completeness and accuracy, from: 

  1. Windows Phone Mango change, Listbox: How to detect compression(end of scroll) states ?http://blogs.msdn.com/b/slmperf/archive/2011/06/30/windows-phone-mango-change-listbox-how-to-detect-compression-end-of-scroll-states.aspx
  2. ExtendedListBox: custom WP7 Listbox control which detects end of scroll, allows infinite scrolling
    http://nuncaalaprimera.com/2012/01/extendedlistbox-custom-wp7-listbox-control-which-detects-end-of-scroll-allows-infinite-scrolling/