Reputation: 3537
I use an ObservableCollection
in my ViewModel
to add a new record in my DataGrid
, so I don't have an access to this control. I wanted to scroll to the bottom of the DataGrid
every time a new item is added.
Normally I can just hook into INotifyCollectionChanged
from my View, then scroll to the bottom, something like;
public MyView(){
InitializeComponent();
CollectionView myCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(MyDataGrid.Items);
((INotifyCollectionChanged)myCollectionView).CollectionChanged += new NotifyCollectionChangedEventHandler(DataGrid_CollectionChanged);
}
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (MyDataGrid.Items.Count > 0){
if (VisualTreeHelper.GetChild(MyDataGrid, 0) is Decorator border){
if (border.Child is ScrollViewer scroll) scroll.ScrollToEnd();
}
}
}
My problem now is that I have a function to Duplicate
and Delete
an item, this whole thing is being done in my ViewModel
. With the approach above, the DataGrid will always scroll to the bottom even if I deleted or duplicate an item in any position which I don't want to happen. Scrolling to the bottom should only be working for the newly added items.
What should be the approach for this?
Upvotes: -1
Views: 1444
Reputation: 3537
You have two options, first is to compare the difference between the old and new items. Then simply get that item and using DataGrid.ScrollIntoView(item)
to scroll into that specific position.
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (YourDataGrid.Items.Count > 0 && e.Action.Equals(NotifyCollectionChangedAction.Add)){ //Do this only when new item is added.
if (sender is not CollectionView oldItems) return;
foreach (var oldItem in oldItems) {
foreach (var newItem in (e.NewItems)??new ObservableCollection<TransactionItem>()){
if (!newItem.Equals(oldItem)){
YourDataGrid.ScrollIntoView(newItem); //Scroll into this specific item.
return; //No need to do further checking
}
}
}
}
}
}
However, the same behavior with the answer of @Paolo Iommarini will be expected when you duplicate the last item. Making his approach much better compared to this in terms of performance.
The second option and much better is by using Events
. You simply need to define it into your ViewModel
, then invoke the event every time a new item is added passing the argument as the newly added item. From your View
, subscribe into that event and use DataGrid.ScrollIntoView(item)
to scroll.
Here is a more detailed example, in your ViewModel
.
public class ViewModel{
//define the event in the ViewModel.
public delegate void MyEventAction(YourObjectModelItem item);
public event MyEventAction? MyEvent;
private void AddNewItem(){ //Assuming this function is being called to add the item into your ObservableCollection.
var new_item = .... //(1) create your item.
YourObServableCollection.Add(new_item); //(2) add the item into your collection.
MyEvent?.Invoke(new_item); //(3) invoke the event and pass the new item as the argument.
}
}
Then in your View
.
public YourView(){
InitializeComponent();
...
this.Loaded += (s, a) => { //You should subscribe only when the view is loaded, otherwise you might get a null issue with your DataContext. You may also use DataContextChanged if you want.
var vm = (ViewModel)DataContext; //(1) Get the ViewModel from your DataContext.
vm.MyEvent += (item) =>{ //(2) Subscribe to the event from the ViewModel.
YourDataGrid.ScrollIntoView(item); //(3) Scroll to that item.
};
};
}
With this approach, you don't have to make any comparison. You just need to know which item you will be scrolling.
Upvotes: 1
Reputation: 259
You can try to check if NotifyCollectionChangedEventArgs.NewStartingIndex is at the end of your collection. You should scroll to the end only if the change has happened at the end.
private void DataGrid_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e){
if (MyDataGrid.Items.Count > 0 && e.NewStartingIndex == MyDataGrid.Items.Count - 1){
if (VisualTreeHelper.GetChild(MyDataGrid, 0) is Decorator border){
if (border.Child is ScrollViewer scroll) scroll.ScrollToEnd();
}
}
}
Upvotes: 1
Reputation: 1306
You can use the passed NotifyCollectionChangedEventArgs
to identify whether a Duplicate
or Delete
has occurred.
Delete
is easy, simply:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
// An item has been removed from your collection. Do not scroll.
}
Duplicate
depends on your definition of what a "duplicate" or "copy" is exactly, but most likely you can use a quick Linq check like:
if (e.Action == NotifyCollectionChangedAction.Add)
{
// An item has been added, let's see if it's a duplicate.
CollectionView changedCollection = (CollectionView)sender;
foreach (myRecordType record in e.NewItems)
{
if (changedCollection.Contains(record))
{
// This was likely a duplicate added
}
}
}
It's worth noting that EventArgs
of any type are really there for this very purpose. They'll generally provide you with more information regarding the event for exactly this kind of logical handling.
Upvotes: 0