3

I have created a user control that scrolls to the right automatically through a given set of images using a Virtualized Stack Panel.

The data context of the user control has any number of images.

These images are loaded initially in a randomized order into a List Collection View. The LCV live sorts by the UnloadedTimestamp property on each image.

As an image scrolls out of view (detected by the UpdateSnapshot method, the unloaded timestamp is set which moves the image to the end of the list, allowing for the scrolling marquee to loop indefinitely.

I'm also making use of CompositionTargetEx which I found here Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh? that seemed to improve the performance.

My issue is that the scrolling is "janky" in that it is not smooth and scrolls in small chunks. It's more like clicking the scrollbar arrows repeatedly then dragging the scrollbar thumb.

Can anyone suggest improvements to the code or a better approach? Any input would be much appreciated. Thanks!

public partial class ImageScroller : UserControl
{
    private EventHandler<RenderingEventArgs>? renderHandler;
    private VirtualizingStackPanel _panel;
    private bool _shifting;
    private bool ImageUnloaded;
    private IList<MyImage> _snapshot = new List<MyImage>();
    private IUserTag UserTag => (IUserTag)DataContext;

    public ImageScroller()
    {
        InitializeComponent();
    }

    public void Shift(ScrollViewer target, double speed = 11.0, double distance = 20.0)
    {
        ScrollViewer scrollViewer = target;
        double startOffset = scrollViewer.HorizontalOffset;
        double destinationOffset = scrollViewer.HorizontalOffset + distance;
        if (destinationOffset < 0.0)
        {
            destinationOffset = 0.0;
            distance = scrollViewer.HorizontalOffset;
        }
        if (destinationOffset > scrollViewer.ScrollableWidth)
        {
            destinationOffset = scrollViewer.ScrollableWidth;
            distance = scrollViewer.ScrollableWidth - scrollViewer.HorizontalOffset;
        }
        double animationTime = Math.Abs(distance / speed);
        DateTime startTime = DateTime.Now;
        CompositionTargetEx.Rendering -= renderHandler;

        renderHandler = (object? sender, RenderingEventArgs e) =>
        {
            _shifting = true;
            if (base.DataContext == BindingOperations.DisconnectedSource || Application.Current == null || Application.Current.MainWindow == null)
            {
                _shifting = false;
                CompositionTargetEx.Rendering -= CompositionTargetEx_Rendering;
            }
            else
            {
                if (ImageUnloaded)
                {
                    distance = scrollViewer.ScrollableWidth - scrollViewer.HorizontalOffset;
                    animationTime = Math.Abs(distance / speed);
                    startTime = DateTime.Now;
                    ImageUnloaded = false;
                }

                double totalSeconds = (DateTime.Now - startTime).TotalSeconds;
                if (totalSeconds >= animationTime)
                {
                    scrollViewer.ScrollToHorizontalOffset(destinationOffset);
                    _shifting = false;
                    CompositionTargetEx.Rendering -= renderHandler;
                }
                if (distance < 0.0)
                {
                    scrollViewer.ScrollToHorizontalOffset(startOffset - totalSeconds * speed);
                }
                else
                {
                    scrollViewer.ScrollToHorizontalOffset(startOffset + totalSeconds * speed);
                }
            }
        };

        CompositionTargetEx.Rendering += renderHandler;
    }

    private void VirtualizingStackPanel_Loaded(object sender, RoutedEventArgs e)
    {
        _panel = (VirtualizingStackPanel)sender;
        _panel.ScrollOwner.ScrollChanged -= _scrollChanged;
        _panel.ScrollOwner.ScrollChanged += _scrollChanged;
        if (_shifting)
        {
            return;
        }
        IUserTag userTag = UserTag;
        if (userTag != null && userTag.RotatingImages.Count > 1 || _panel.ScrollOwner.VerticalOffset != _panel.ScrollOwner.ScrollableWidth)
        {
            base.Dispatcher.BeginInvoke((Action)delegate
            {
                Shift(_panel.ScrollOwner, 2.0, _panel.ScrollOwner.ScrollableWidth);
            }, DispatcherPriority.ContextIdle, null);
        }
    }

    private void _scrollChanged(object sender, RoutedEventArgs e)
    {
        if (!_shifting)
        {
            IUserTag userTag = UserTag;
            if (userTag != null && userTag.RotatingImages.Count > 1)
            {
                base.Dispatcher.BeginInvoke((Action)delegate
                {
                    Shift(_panel.ScrollOwner, 2.0, _panel.ScrollOwner.ScrollableWidth);
                }, DispatcherPriority.ContextIdle, null);
            }
        }
        UpdateSnapshot();
    }

    private void UpdateSnapshot()
    {
        Rect layoutBounds = LayoutInformation.GetLayoutSlot(_panel);
        List<MyImage> list = (from visualChild in _panel.GetChildren()
                              let childBounds = LayoutInformation.GetLayoutSlot(visualChild)
                              where layoutBounds.Contains(childBounds) || layoutBounds.IntersectsWith(childBounds)
                              select visualChild.DataContext).Cast<MyImage>().ToList();
        foreach (MyImage item in _snapshot.Except(list))
        {
            item.UnloadedTimestamp = DateTimeOffset.Now;
            ImageUnloaded = true;
        }
        _snapshot = list;
    }
}
Julien
  • 212
  • 1
  • 18
  • 53
  • I think you are beyond help (following "discussion" in the answer I deleted because I realised that the OP cannot be helped, he needs someone with angelic patience to explain him how to open bottle of beer or scroll a framework element out of view).. good luck – Boppity Bop Aug 24 '22 at 14:39
  • 1
    @BoppityBop Why would you delete an answer because you thought the OP had trouble understanding? It could have been useful 1) to others looking at this post in the future or 2) to anyone who tried to help him but didn't immediately know where to start. – Traveller Aug 24 '22 at 23:27
  • it was downvoted because people simply dont get it. also OP does not need what he asks (his comment on the deleted post shows he is looking to solve different issue). all in all - not worth the hassle. and I dont think there is any value in this question/answer for future references.. this is inane functionality and OP just blew it out of proportions, changed his mind few time, wrote loads of unnecessary code, bother bother. this question should be rather closed. – Boppity Bop Aug 25 '22 at 11:15
  • This functionality belongs to the Panel. You should extend VirtualizingPanel for this task. Because you want pixel based scrolling, the performance will drastically degrade when the number of items increases. This is because you would have to track all items i.e. calculate their layout in order to determine the visible items. You can't access the items by index (as it would be the case when allowing to scroll item based). – BionicCode Aug 29 '22 at 07:00

0 Answers0