The solution was posted months ago in Silverlight – Windows Phone Team’s blog. The only thing I made is making an extended ListBox control with an event which allows easy management of end-of-scroll states.
Advantages against the traditional ListBox control? ExtendedListBox will fire an event when the user has scrolled to the end or beginning of the list. With this event you can manage to do an infinite-scrolling control (more or less).
The class is this:
public class ExtendedListBox : ListBox { // Compression states: Thanks to http://blogs.msdn.com/b/slmperf/archive/2011/06/30/windows-phone-mango-change-listbox-how-to-detect-compression-end-of-scroll-states.aspx protected bool _isBouncy = false; private bool alreadyHookedScrollEvents = false; public ExtendedListBox() { this.Loaded += new RoutedEventHandler(ListBox_Loaded); } private void ListBox_Loaded(object sender, RoutedEventArgs e) { ScrollBar sb = null; ScrollViewer sv = null; if (alreadyHookedScrollEvents) return; alreadyHookedScrollEvents = true; this.AddHandler(ExtendedListBox.ManipulationCompletedEvent, (EventHandler<manipulationcompletedeventargs>)LB_ManipulationCompleted, true); sb = (ScrollBar)FindElementRecursive(this, typeof(ScrollBar)); sv = (ScrollViewer)FindElementRecursive(this, typeof(ScrollViewer)); if (sv != null) { // Visual States are always on the first child of the control template FrameworkElement element = VisualTreeHelper.GetChild(sv, 0) as FrameworkElement; if (element != null) { VisualStateGroup vgroup = FindVisualState(element, "VerticalCompression"); VisualStateGroup hgroup = FindVisualState(element, "HorizontalCompression"); if (vgroup != null) vgroup.CurrentStateChanging += new EventHandler<visualstatechangedeventargs>(vgroup_CurrentStateChanging); if (hgroup != null) hgroup.CurrentStateChanging += new EventHandler</visualstatechangedeventargs><visualstatechangedeventargs>(hgroup_CurrentStateChanging); } } } public delegate void OnCompression(object sender, CompressionEventArgs e); public event OnCompression Compression; private void hgroup_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) { if (e.NewState.Name == "CompressionLeft") { _isBouncy = true; if (Compression != null) Compression(this, new CompressionEventArgs(CompressionType.Left)); } if (e.NewState.Name == "CompressionRight") { _isBouncy = true; if (Compression != null) Compression(this, new CompressionEventArgs(CompressionType.Right)); } if (e.NewState.Name == "NoHorizontalCompression") { _isBouncy = false; } } private void vgroup_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) { if (e.NewState.Name == "CompressionTop") { _isBouncy = true; if (Compression != null) Compression(this, new CompressionEventArgs(CompressionType.Top)); } if (e.NewState.Name == "CompressionBottom") { _isBouncy = true; if(Compression!=null) Compression(this, new CompressionEventArgs(CompressionType.Bottom)); } if (e.NewState.Name == "NoVerticalCompression") _isBouncy = false; } private void LB_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (_isBouncy) _isBouncy = false; } private UIElement FindElementRecursive(FrameworkElement parent, Type targetType) { int childCount = VisualTreeHelper.GetChildrenCount(parent); UIElement returnElement = null; if (childCount > 0) { for (int i = 0; i < childCount; i++) { Object element = VisualTreeHelper.GetChild(parent, i); if (element.GetType() == targetType) { return element as UIElement; } else { returnElement = FindElementRecursive(VisualTreeHelper.GetChild(parent, i) as FrameworkElement, targetType); } } } return returnElement; } private VisualStateGroup FindVisualState(FrameworkElement element, string name) { if (element == null) return null; IList groups = VisualStateManager.GetVisualStateGroups(element); foreach (VisualStateGroup group in groups) if (group.Name == name) return group; return null; } } public class CompressionEventArgs : EventArgs { public CompressionType Type { get; protected set; } public CompressionEventArgs(CompressionType type) { Type = type; } } public enum CompressionType { Top, Bottom, Left, Right };
It fires the event Compression whenever a scroll has ended. CompressionEventArgs contains the info on the side the scroll has ended. But this is not sufficient to make things work. You have to add this code to your App.xaml:
<style TargetType="ScrollViewer"> <setter Property="VerticalScrollBarVisibility" Value="Auto"/> <setter Property="HorizontalScrollBarVisibility" Value="Auto"/> <setter Property="Background" Value="Transparent"/> <setter Property="Padding" Value="0"/> <setter Property="BorderThickness" Value="0"/> <setter Property="BorderBrush" Value="Transparent"/> <setter Property="Template"> </setter><setter .Value> <controltemplate TargetType="ScrollViewer"> <border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"> <visualstatemanager .VisualStateGroups> <visualstategroup x:Name="ScrollStates"> </visualstategroup><visualstategroup .Transitions> <visualtransition GeneratedDuration="00:00:00.5"/> </visualstategroup> <visualstate x:Name="Scrolling"> <storyboard> <doubleanimation Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/> <doubleanimation Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/> </storyboard> </visualstate> <visualstate x:Name="NotScrolling"/> <visualstategroup x:Name="VerticalCompression"> <visualstate x:Name="NoVerticalCompression"/> <visualstate x:Name="CompressionTop"/> <visualstate x:Name="CompressionBottom"/> </visualstategroup> <visualstategroup x:Name="HorizontalCompression"> <visualstate x:Name="NoHorizontalCompression"/> <visualstate x:Name="CompressionLeft"/> <visualstate x:Name="CompressionRight"/> </visualstategroup> </visualstatemanager> <grid Margin="{TemplateBinding Padding}"> <scrollcontentpresenter x:Name="ScrollContentPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/> <scrollbar x:Name="VerticalScrollBar" IsHitTestVisible="False" Height="Auto" Width="5" HorizontalAlignment="Right" VerticalAlignment="Stretch" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" IsTabStop="False" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Value="{TemplateBinding VerticalOffset}" Orientation="Vertical" ViewportSize="{TemplateBinding ViewportHeight}" /> <scrollbar x:Name="HorizontalScrollBar" IsHitTestVisible="False" Width="Auto" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" IsTabStop="False" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Value="{TemplateBinding HorizontalOffset}" Orientation="Horizontal" ViewportSize="{TemplateBinding ViewportWidth}" /> </grid> </border> </controltemplate> </setter> </style>
Just with this, you can start working with the ExtendedListBox. And I suppose you could also extend in the same way similar controls, just reusing the code.
All of the code is completely free to use, and the credits go to Silverlight – WP blog, they developed the main part of this trick. I only put it together.