9

I wish to recalculate things everytime a DataGrid gets more rows or some are removed. I tried to use the Loaded event, but that was fired only once.

I found AddingNewItem, but that is fired before it has been added. I need to do my stuff afterwards.

There's also LayoutUpdated, which works, but I'm afraid it's not wise to use it, because it fires way too often for my purposes.

Tower
  • 98,741
  • 129
  • 357
  • 507

7 Answers7

11

If your DataGrid is bound to something, I think of two ways of doing this.

You could try getting the DataGrid.ItemsSource collection, and subscribing to its CollectionChanged event. This will only work if you know what type of collection it is in the first place.

// Be warned that the `Loaded` event runs anytime the window loads into view,
// so you will probably want to include an Unloaded event that detaches the
// collection
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
    var dg = (DataGrid)sender;
    if (dg == null || dg.ItemsSource == null) return;

    var sourceCollection = dg.ItemsSource as ObservableCollection<ViewModelBase>;
    if (sourceCollection == null) return;

    sourceCollection .CollectionChanged += 
        new NotifyCollectionChangedEventHandler(DataGrid_CollectionChanged);
}

void DataGrid_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // Execute your logic here
}

The other solution would be to use an Event System such as Microsoft Prism's EventAggregator or MVVM Light's Messenger. This means your ViewModel would broadcast a DataCollectionChanged event message anytime the bound collection changes, and your View would subscribe to receive these messages and execute your code anytime they occur.

Using EventAggregator

// Subscribe
eventAggregator.GetEvent<CollectionChangedMessage>().Subscribe(DoWork);

// Broadcast
eventAggregator.GetEvent<CollectionChangedMessage>().Publish();

Using Messenger

//Subscribe
Messenger.Default.Register<CollectionChangedMessage>(DoWork);

// Broadcast
Messenger.Default.Send<CollectionChangedMessage>()
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • 1
    I am trying to implement this in my code, adding the notify collection changed event handler, but my sourceCollection always is equal to "null". I don't have the ViewModelBase as an available class for the ObservableCollection template . . . I am using VS 20113 (express) binding to a SQLServer 2014 express database, and WPF application. I have not been able to find a missing "using" directive that would give me the ViewModelBase . . . what am I doing wrong? Thanks Paul – Paul Gibson Oct 28 '14 at 22:50
  • 1
    @PaulGibson `ViewModelBase` is a custom class that I usually implement on all my ViewModels. In your case, just cast it to whatever type of item is in your ItemsSource. – Rachel Oct 29 '14 at 13:27
  • 1
    Thanks for the explanation, that is what I figured after some googling. So I have tried the class of the data table, as well as the row class (both the generic DataRow, and the specific class for my dataRow) and both yeild null for sourceCollection . . . I have posted a question that has the code blocks here [link](http://stackoverflow.com/questions/26620496/wpf-datagrid-roweditending-event-to-call-update) . . . I'm sure that I'm doing something simple wrong, but can't find the right combo. – Paul Gibson Oct 29 '14 at 13:50
  • 1
    I have tried the second approach, EventAggregator in Prism. In this case when a new row is added to the DataGrid, the focus is already set to the next element. When I click on that row, the event fires as collection really changes at that point. Is there something that fires immediately when a new row is added to the datagrid, when CanUserAddRows is set to true? – Vishal Sep 17 '15 at 19:38
  • 1
    @Vishal Personally I wouldn't use CanUserAddRows for this. I would either provide users with an "Add" button that adds a record to the source collection, or provide them with a blank item and whenever that blank item is populated, add a new blank item to take the place of the "New" row. Personally I prefer the first solution as I find extra blank rows in a grid often confuse users, and make it harder to validate as I always have to check if the item is a "new" item – Rachel Sep 17 '15 at 20:19
  • @Rachel Thanks for the explaination. I will try it as you said, as I always like that users should feel free while using my application. If it is possible for you to share your final datagrid layout then please share a screenshot of that. – Vishal Sep 17 '15 at 21:35
  • For the first option, you can use INotifyCollectionChanged like `var sourceCollection = dg.ItemsSource as INotifyCollectionChanged`. This avoids having to know the concrete type used for generic and still lets you get the collection changed event. – Alphaceph Jun 06 '21 at 09:38
2

How about DataGrid.LoadingRow(object sender, DataGridRowEventArgs e)?

Same for Unloading.

DataGrid.UnLoadingRow(object sender, DataGridRowEventArgs e)?

Nikhil Agrawal
  • 47,018
  • 22
  • 121
  • 208
2

Have you tried an MVVM approach and binding to an Observable collection?

public ObservableCollection<Thing> Items{
get { return _items; }
set{ _items = value; RaisePropertyChanged("Items");  // Do additional processing here 
}
}

So you can watch the add / remove of items without being tied to the UI?

Josh
  • 10,352
  • 12
  • 58
  • 109
  • Although this is the right way to handle collection changed events for application logic, I think the OP is asking about collection changed events for the View based on some comments in [another question of his](http://stackoverflow.com/a/11157916/302677) – Rachel Jul 02 '12 at 20:06
1

If you want you can go down the RowUnloading route as others have described here, however note that this event fires also every time a row is losing focus.

However by playing around I found that when a row is removed the SelectedItem property of the grid is null while the CurrentItem property is not null, and so far I have seen this combination only for a deleted row, (although I can't guarantee that I have not missed an exotic situation... however for the basic situations of moving away from the row I have not seen it so far).

So when can use the following code to filter for deleted rows only:

private void CategoriesGrid_UnloadingRow(object sender, DataGridRowEventArgs e)
{     
        if (((DataGrid)sender).SelectedItem != null || ((DataGrid)sender).CurrentItem == null)
        {
            return;
        }

        // The rest of your code goes here
}
yoel halb
  • 12,188
  • 3
  • 57
  • 52
0

If you want use ObservableCollection and get notification about add or another operation, the best way use INotifyCollectionChanged

var source = datagrid.ItemsSource as INotifyCollectionChanged;

Because, when you will unwrap to ObservableCollection<MyClass>(), you must write strogly MyClass (not ObservableCollection<ParentOfMyClass>())

Smagin Alexey
  • 305
  • 2
  • 6
-1

Depending on what "things" you want to recalculate, you might consider using the ScrollViewer.ScrollChanged attached event. This can be set in XAML as follows:

<DataGrid
...
ScrollViewer.ScrollChanged="control_ScrollChanged">

The ScrollChangedEventArgs object has various properties that can be helpful for computing layout and scroll position (Extent, Offset, Viewport). Note that these are typically measured in numbers of rows/columns when using the default virtualization settings.

Matt
  • 659
  • 6
  • 11
-1

I was looking for solution to this and I have found the perfect event to handle this, the event is called UnloadingRow

<DataGrid ....
    UnloadingRow="DataGrid_UnloadingRow">
 ...
</DataGrid>

In your C# code u get this

private void ProductsDataGrid_UnloadingRow(object sender, DataGridRowEventArgs e)
{
   MyObject obj = (MyObject)e.Row.Item; // get the deleted item to handle it
   // Rest of your code ...
   // For example : deleting the object from DB using entityframework

}
Charaf
  • 177
  • 1
  • 2
  • 12
  • This works when any row unloads visually, not logically; thus, it will fire anytime a row is unloaded and, if virtualizing, this may happen whether or not the item already existed prior to the operation. The ideal behavior is to fire only when an item is added or removed from the underlying collection. –  Feb 01 '17 at 23:05
  • Unloading fires just by moving away from the row not just for deletion – yoel halb May 30 '18 at 03:21