I'd like for my ListView to scroll to the last item added to the ObservableCollection<> that is its .ItemSource, when a new item is added.
I can't seem to find any way to do that. ScrollTo() is a method, not a property, so there is no way to bind that to a property in the ViewModel.
Is there a way to make the ListView auto-scroll to the SelectedItem; which can be set through binding? Its kinda silly for the ViewModel to set a new item as selected, but the ListView doesn't scroll to it/bring it into view. Something like this would be nice: EIther to binding to an item to bring into view or a bool to autoscroll to selected.
<ListView x:Name="imagesListView"
ItemsSource="{Binding PendingScans}"
SelectedItem="{Binding SelectedPendingImagePath,
Mode=TwoWay}"
ScrollToItem="{Binding SelectedPendingImagePath}"
ScrollToSelected = "True"
Grid.Row="1"
Grid.Column="1"></ListView>
I'm going to guess you're just getting started with Xamarin.Forms, because Behaviors are absolutely a thing. Xamarin has created their own Behavior class to inherit from, which you can add to a Style, which you can implicitly apply to all controls of a type. No need for custom classes in this case.
And since I was just implemented this earlier this morning, how to scroll to an item when it is added to your ItemSource ObservableCollection:
listView.ItemAppearing += (sender, e) =>
{
if (e.Item == listView.ItemSource.Last())
{
listView.ScrollTo(e.Item, ScrollToPosition.MakeVisible, false);
}
}
EDIT: Amended implementation, because this event also fires when the user scrolls an item into view.
Answers
you'll have to tie back to the view somehow. scrolling is a view only concept, there's no vm analogue. just extend listview to monitor for changes to the selected item and scroll to it.
Inheriting from ListView to make a custom control is an option I've considered. It just isn't my first choice if there is a way to make the OEM control behave as it should. I had hoped I just overlooked an option or property for it.
What issues do you see with a custom ListView? it is minimal, easy code. I don't think having scroll behavior accessible from the VM would be "behaving as it should". I know in WPF, the same scrolling "issues" exist.
Sure its easy code. It just means that every developer on the team has to know to use the custom version of the control instead of the OEM control. That's not something I like. It means a year from now and 20 controls later you have to maintain this mental list of... "I can use the OEM button, but the custom DateView, the OEM ComboBox is fine, {oh} but now that we want this behavior we have to replace all of the OEM ListViews throughout the application with the custom version. {...}
I try to look at maintainability and find ways around complicating my code - especially when in a multi-developer environment. In WPF I would add a 'behavior' that applies to all ListViews on an application-wide scope. Then nothing needs to be done on an individual control-by-control basis. But behaviors don't exist in Xamarin.Forms: DependencyObject and IBehavior are strictly WPF concepts.
If the ScrollTo object were a bindable property then it could also be rolled up in a Style, keeping consistent look/feel/behavior throughout the application without having to touch every individual UI object.
I'm going to guess you're just getting started with Xamarin.Forms, because Behaviors are absolutely a thing. Xamarin has created their own Behavior class to inherit from, which you can add to a Style, which you can implicitly apply to all controls of a type. No need for custom classes in this case.
And since I was just implemented this earlier this morning, how to scroll to an item when it is added to your ItemSource ObservableCollection:
EDIT: Amended implementation, because this event also fires when the user scrolls an item into view.
@ClintStLaurent I would extend the ListView and then use it everywhere, all the time, with all developers. There is no need to have to choose between one or the other. If you want to be able to toggle the functionality, create a DP that enables/disables the scrolling.
Behaviors certainly sounds like a nice way to go though, I don't have much experience with them. If you get that working I would love to see some code.
Thanks Joe for letting me know there is a Behaviors class to work with. You're not wrong that I'm less than 3 months working in Xamarin.Forms. I had to make a couple adjustments to your code: ItemsSource not ItemSource, and Last() doesn't exist by default, so I had to throw the source to an ObservableCollection<> first. Then I had to cast the i.Item to a string so the compare was string to string not string to object.
Sadly it made no difference to the behavior of the ListView. New items get added to the collection in the ViewModel, the last item gets selected. But the ListView never scrolls to bring that last item in to view. If the user (me) scrolls the listview with their finger the last item eventually comes into view and it is selected.
Which got me thinking that .ItemAppearing may not have been the right event in this case. Since the .ItemSelected is already bound in the XAML it made sense I could use the .ItemSelected event instead. After a bit of tweeking I eventually came to this code that is working for me.
I don't have to be thrilled about having code in the C# code-behind. In this case its fast, simple, readable and obvious when other team developers see it. I wouldn't have thought to go this route without your input. Thank you.
I'll definitely be looking at Xamarin's implementation of Behaviors when the pressure for this Proof of Concept app let's up.
So you are adding that code snippet to any view that has a ListView and referencing it by name?
That has got to be worse then an extension to ListView no?
Right now its a one-off. So the new code is only in one place. Once I have a chance to review how Xamarin does Behaviors I'll probably write a Behavior that does what I need it to do. I prefer that route to putting on extensions or inheriting from the OEM control. Its just less 'dirty' to me. I'm not against extension methods for pure data types, but I don't like them for UI elements. I'm happy to write an extension method of .ToMilSpec() for DateTime. But I wouldn't add a MilSpec extension method to a DateTimePickerControl. Just a personal preference.
We all have our own preferred styles. That's what makes coding as much an art as a science. It can be nice that there are many right ways to do something.
When a ListView is already at bottom and you add an item to the ObservableCollection, ItemAppearing is not fired...
ItemAppearing is not ItemAddedToCollection - It means item that was not in view is now coming in to view... it is appearing in view.
So if the ListView already has several items that run off screen, and you add an item that would not and should not raise an ItemAppearing event. - You already get an event from CollectionChanged for changes to the collection.
If the ListView is "at bottom" as you describe, and you add an item the new item is off screen. So again, it would not raise "ItemAppearing" because it has not yet appeared.
That's the point I'm making... When a new item is added I would like to be able to scroll to it, causing it to appear.
As the documentation says:
@ClintStLaurent I have been struggling with this for over month. I finally got Android and UWP worked out but it appears it is impossible to scroll to the bottom on iOS. There are several threads on this issue, here are two:
http://forums.xamarin.com/discussion/comment/211759/#Comment_211759
http://forums.xamarin.com/discussion/comment/211763/#Comment_211763
I gave up on ItemAppearing, its behaves different between platforms and its just doesn't behave the way you think it would anyways.
http://forums.xamarin.com/discussion/comment/190172#Comment_190172
I gave up on the native "ScrollTo" because it just did not work. It would bounce up randomly into the ListView and there was no fixing it. Most likely because XF cannot determine row heights when HasUnevenRows=True.
https://bugzilla.xamarin.com/show_bug.cgi?id=40432
I believe the root issue is that scrollTo gets called while the ListView is updating and "ContentSize.Height" is just wrong.
private void ScrollToRow(int itemIndex, int sectionIndex, bool animated) { if(itemIndex == -1) { return; } if (pinToBottom && Control!=null) { Control.LayoutIfNeeded(); Debug.WriteLine(Control.ContentSize.Height.ToString() + ":" + Control.Frame.Size.Height.ToString()); CGPoint offset = new CGPoint(0, Control.ContentSize.Height - Control.Frame.Size.Height); if (offset.Y < 0.0f) { return; } Debug.WriteLine("Scroll: " + offset.ToString()); Control.SetContentOffset(offset, false); } }
This works about 70% of the time. When I hit this code Control.ContentSize.Height sometimes returns a smaller value than the previous, and that should never happen since I am only adding rows. This same code works perfectly on Xamarin for iOS, I have had that code out for 3 years, but it just does not work in XF. I spent about 12 hours yesterday fooling with it. I was able to turn off ListView animations by coding around my elbow so it would paint fast, but had the same result.
I am out of ideas and dead in the water. I really wish we could get some heavy weights like @AdamP. @rmarinho and @BryanHunterXam to get all these ListView items pushed to the top of the bug queue. Its been too broken for too long.
Agreed @RaymondKelly - As much as Xamarin pushes ListViews, their importance, the number of Xamarin University course dedicated to them, their prominence in certification, etc. etc. - to the point where it begins to feel like ListViews are the end-all-be-all of mobile coding in THEIR eyes - they sure don't seem to show the same commitment to supporting them.
Since i cant post link i just copy the right answer from stackoverflow
Viewmodel.cs on collection change add :
MessagingCenter.Send<object, object> (this, "MessageReceived", lastReceivedMessage);
At
PageX.xaml.cs add :
MessagingCenter.Subscribe<object, object> (this, "MessageReceived", (sender, arg) => {
MainScreenMessagesListView.ScrollTo(arg, ScrollToPosition.End, true);
});
--Done
@sergiitokarchuk Splendid idea, I used your base pattern to create a custom control using this pattern, but so that I could use it using Mvvm, I added a bindable property to the control ('AddedItemMessagingCenterMessageKey') and also bound this to a property in the ViewModel, the idea being that the key was declared in the ViewModel, the item was sent in the ViewModel by the MessagingCenter busing that declared key, but the MessagingCenter in the control had first subscribed to the message using the property AddedItemMessagingCenterMessageKey in the control that was bound to the ViewModel.
Quite a nice pattern as it gets it all out of the way of the code behind in the page, as well as being reusable in the rest of the app.
At which point I added a few other bindable properties, here they are in case someone wants a leg-up, they are self explanatory:
which binds to the VM as follows:
And the following in the VM sends the message:
Naturally, the
RefreshData()
method is called after the item has been added to the collection. I call this method after adding data as well as refreshing, so the_isDataAdded
makes sure I don't send a message if the user refreshes.Thanks for the base pattern!
I upgraded the above to do the ScrollTo when the ItemSource property changed (in the this.PropertyChanged += section):
and this meant updating the following:
in which
private object _lastItemAdded;
was added to support the above change.This means that if the ItemSource update is slower than the messaging assignment, the ScrollTo is still guaranteed; without this change, if the messaging occurs before the ItemsSource update, the new item to ScrollTo wont be in the list.