Xamarin.Forms icon indicating copy to clipboard operation
Xamarin.Forms copied to clipboard

[Bug] Binding CurrentItem of CarouselView overwrites value of backing property when CarouselView initializes

Open BioTurboNick opened this issue 5 years ago • 4 comments

Description

When CarouselView initializes, it forces CurrentItem to be set to the first item in its collection. If CurrentItem is bound to a backing property, that causes it to be overwritten. Part of the issue may be that setting CurrentItem doesn't set Position?

Steps to Reproduce

  1. Initialize a property to a value that doesn't appear first in a CarouselView.
  2. Bind CarouselView.CurrentItem to the property.
  3. Run the application.

Expected Behavior

CarouselView.CurrentItem should take on the value of its backing property at initialization and the backing property should be unchanged.

Actual Behavior

CarouselView.CurrentItem is initialized to the first item in ItemsSource, which is propagated to the property bound to CurrentItem.

Basic Information

  • Version with issue: 4.8.x, 5.0.0-pre3
  • Last known good version: 4.7
  • IDE: Visual Studio 16.8.1
  • Platform Target Frameworks:
    • Android: 11.0

Reproduction Link

CarouselInitializationBinding.zip

Workaround

No, it takes control. I can't figure out how to hook it so that the position changes after loading. EDIT: see next post.

BioTurboNick avatar Nov 15 '20 06:11 BioTurboNick

Found an ugly workaround:

        bool skipNext;

        private void Carousel_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
        {
            // workaround for initial set bug

            bool isBad = new StackTrace().GetFrames().Select(x => x.GetMethod().Name).Any(x => x.Contains("SetUpNewElement"));

            if (isBad)
            {
                if (!skipNext)
                {
                    skipNext = true;
                    ((CarouselView)sender).Position = _GridLayouts.IndexOf((GridLayout)e.PreviousItem);
                }
                else
                    skipNext = false;

                return;
            }

            // your code
        }

You have to set Position, not CurrentItem, here. And the skipping thing may be specific to how I'm doing things. GridLayout is the type of item the CarouselView has in its ItemsSource.

BioTurboNick avatar Nov 15 '20 16:11 BioTurboNick

Ahhhhh the workaround breaks when Loop == false for some reason??? But when Loop == true, the displayed item is out of order and changes as soon as you touch the carousel.

😩

BioTurboNick avatar Nov 15 '20 18:11 BioTurboNick

Found an actual workaround:

        protected override void OnSizeAllocated(double width, double height)
        {
            base.OnSizeAllocated(width, height);

            gridLayoutCarousel.ScrollTo(_GridLayouts.IndexOf(_viewModel.Hemocytometer.GridLayout));    // workaround for initial set bug
        }

        private void GridLayoutCarousel_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
        {
            // workaround for initial set bug

            bool isBad = new StackTrace().GetFrames().Select(x => x.GetMethod().Name).Any(x => x.Contains("UpdateAdapter"));

            if (isBad)
                return;

            // handle the event
        }

OnSizeAllocated fires after the control is in place. Still need to avoid handling any events during the initialization.

BioTurboNick avatar Nov 16 '20 01:11 BioTurboNick

I'm having the same issue. Putting in Task.Delay on setting the backing property can help, but it's a bad work around.

JosephHarvey-Xamarin avatar Feb 22 '24 01:02 JosephHarvey-Xamarin