Search code examples
c#wpfuser-controlswpf-controlsitemscontrol

Loop Items in WPF ItemsControl or ScrollViewer via Animation?


When moving through items in an ItemsControl or ScrollViewer, can I loop the contents so that the first item comes "after" the last item (as perceived by the user)?

I have a custom ScrollViewer that will animate between selections, which currently contains an ItemControl. The XAML for setting up my control is as follows:

<local:AnimatedScrollControl x:Name="myScroll" Grid.Column="1" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden">
    <ItemsControl x:Name="myList"
                    ItemsSource="{Binding SlidesList}"
                    ItemTemplate="{StaticResource SlideTemplateOne}"
                    VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</local:AnimatedScrollControl>

Each item in the ItemControl takes up the entire visible area for the control, so the user only sees one item at a time. The image below represents the control in action, with the red box indicating the viewport. A timer moves to the next item automatically. Control Example 01 The code to shift the slides (simplified for brevity) currently looks like this:

private void NextSlide()
{
    _currentSlide++;

    // get the current offset
    double offset = (double)myScroll.GetValue(AnimatedScrollControl.SlideOffsetProperty);

    // get the width of the current slide
    FrameworkElement elementContainer = myList.ItemContainerGenerator.ContainerFromItem(CurrentSlide) as FrameworkElement;

    // set the `to` value for a single slide shift
    double to = offset + elementContainer.ActualWidth;

    if (_currentSlide == _items.Count)
    {
        to = 0;
        _currentSlide = 0;
    }

    DoubleAnimation animation = new DoubleAnimation();
    animation.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut };

    animation.From = offset;
    animation.To = to;
    animation.Duration = TimeSpan.FromMilliseconds(300);

    myScroll.BeginAnimation(AnimatedScrollControl.SlideOffsetProperty, animation);
}

When the last slide is reached and the viewport is updated to the first slide, it scans all the slides in reverse to reach the beginning. Like so: Control Examples 02 What I would like to do is make it appear as if the viewport scan directly from 'Slide 5' to 'Slide 1', such as: Control Examples 03

Is there a way I can set up my ItemsControl or ScrollViewer, or adjust my Animation in order to achieve this?


Solution

  • My solution came in the form of using an old LoopFrame class, written by Dr WPF.

    http://drwpf.com/blog/2009/08/05/itemscontrol-n-is-for-natural-user-interface/