Joelxxx
Joelxxx

Reputation: 133

Maui CollectionView scrolls to top when replacing an item - But only on Windows

I have a cart system on a catalog of items with 2 DataTemplates. one is for item with a count of 0. a second for items with a count greater than 0.

We use an Add button within each item For the greater than 0 template we also have a Remove button and change the background color for that grid to make it easier to find which items in the catalog have nonzero purchase numbers.

I have a height set for the collectionView This code works correctly on Android. When I switch and test on Windows, any change that reassigns the item in order to force a template change causes the collectionview to scroll to the top. I have already tried replacing the top VerticalStackLayout with a grid. Also tried CollectionView as top element and putting the stuff in the HorizontalStackLayout in the CollectionView header. And tried using FillAndExpand as the VerticalOptions on the collection view. All of these alternatives still scrolled to the top when running as a Windows app on any change that replaces the element in the collection view. On Android it works fine.

        <DataTemplate
            x:DataType="viewmodel:FlatRateUI"
            x:Key="FlatRateNoItems">
            <Grid Padding="10"  Margin="0,5,0,0" 
                           BackgroundColor="{AppThemeBinding Light={StaticResource FlatRateBGNoItemsLight}, Dark={StaticResource FlatRateBGNoItemsDark}}"
                  >

        <DataTemplate
            x:DataType="viewmodel:FlatRateUI"
            x:Key="FlatRateWithItems">
            <Grid Padding="10"  Margin="0,5,0,0" 
                           BackgroundColor="{AppThemeBinding Light={StaticResource FlatRateBGHasItemsLight}, Dark={StaticResource FlatRateBGHasItemsDark}}"
                  >
        <viewmodel:FlatRateTemplateSelector x:Key="FlatRateItemSelector"
                                             NoItemsSelector="{StaticResource FlatRateNoItems}"
                                             HasItemsSelector="{StaticResource FlatRateWithItems}"                                            
                                          />


    <VerticalStackLayout>
        <HorizontalStackLayout>
            <Button 
                x:Name="BackButton"
                Text="&lt; Back"
                VerticalOptions="Center"
                Clicked="OnBackButtonClicked"
                HorizontalOptions="Center" />
            <Button 
                x:Name="CartButton"
                Text="Cart"
                VerticalOptions="Center"
                Clicked="OnCartButtonClicked"
                HorizontalOptions="Center" />
            <Button 
                x:Name="SCButton"
                Text="SC"
                VerticalOptions="Center"
                Clicked="OnSCButtonClicked"
                HorizontalOptions="Center" />
        </HorizontalStackLayout>
        <CollectionView  ItemsSource="{Binding Items}"
                        ItemTemplate="{StaticResource FlatRateItemSelector}"
                         VerticalOptions="Fill"
                         HeightRequest="680"
                         HorizontalOptions="Fill">
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>
        [RelayCommand]
        async Task Add(FlatRateUI i)
        {
            System.Diagnostics.Debug.WriteLine(i.ItemName + " " + i.Qty);
            i.Qty += 1;
            if (i.Qty == 1)
            {
                // If Qty=1, then this slot need to re-render with a different DataTemplate
                // have to affect the Items collection object or it doesn't re-render with new Template
                int idx = Items.IndexOf(i);
                Items[idx] = i;
            }
        }
        [RelayCommand]
        async Task Remove(FlatRateUI i)
        {
            System.Diagnostics.Debug.WriteLine(i.ItemName + " " + i.Qty);
            if (i.Qty > 0)
            {
                i.Qty -= 1;
                if (i.Qty == 0)
                {
                    // If Qty=0, then this slot need to re-render with a different DataTemplate
                    // have to affect the Items collection object or it doesn't re-render with new Template
                    int idx = Items.IndexOf(i);
                    Items[idx] = i;
                }
            }
        }

Upvotes: 1

Views: 135

Answers (3)

Akli
Akli

Reputation: 1549

Add this to your CollectionView to set KeepScrollOffset to ItemsUpdatingScrollMode, applicable only on Windows:

<CollectionView ....>
      <CollectionView.ItemsUpdatingScrollMode>
          <OnPlatform x:TypeArguments="ItemsUpdatingScrollMode">
              <On Platform="WinUI" Value="KeepScrollOffset" />
          </OnPlatform>
      </CollectionView.ItemsUpdatingScrollMode>
</CollectionView>

Upvotes: 0

aw3
aw3

Reputation: 192

Instead of directly setting indexes try using Insert and Remove methods like this:

int idx = Items.IndexOf(i);
Items.RemoveAt(idx) // or just Items.Remove(i);
Items.Insert(idx,i);

I believe windows may have some problems with reordering, but inserting and removing should call native methods. Didn't try myself, so please reply if it worked.

Upvotes: 0

Rafael
Rafael

Reputation: 2049

I would save the position of the selected element (to variable like positionOfSelectedElement) before the operation that causes the reported issue.

And after the operation I would set the scroll position to the memorized state via

collectionView.ScrollTo(positionOfSelectedElement);

More details can be found here: https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/scrolling

=== Update

In case you're using view-model, try this way. Set two-way binding to SelectedItem

<CollectionView  
       <!-- ... skipped other properties ... -->
       ItemsSource = {Binding ListElementsCollection}
       SelectedItem="{Binding SelectedListElement, Mode=TwoWay}">

</CollectionView>

Viewmodel:


// before replacing the item, find the index of SelectedListElement
int positionOfCurrentlySelectedElement = ListElementsCollection.FindIndex(...);

....

// after replacing the item, 
// we're assuming that positionOfCurrentlySelectedElement is inside of correct bounds

SelectedListElement = ListElementsCollection[positionOfCurrentlySelectedElement];

// if necessary call OnPropertyChanged
OnPropertyChanged(nameof(SelectedListElement));

Upvotes: 0

Related Questions