9

I have 2 list views...and add/remove buttons between them.

On collection changed event of a list-view-collection in viewmodel, can i rollback the changes for a particular condition ?

Relativity
  • 6,210
  • 20
  • 74
  • 121

3 Answers3

5

You could handle the CollectionChanged event of the ObservableCollection to backup (via the VM or whatever) the old values (see NotifyCollectionChangedEventArgs.OldItems property) and get them back when needed i.e. when user clicks 'Undo' etc.

Update In reference to comments bellow:

If you do want to rollback the collection from withing the CollectionChanged event-handler, create a flag where you escape the handler from a recursive call (not tested with multi-threaded application), here is a simple example, you can easily tweak it to fit in your V/VM.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
  var x = new ObservableCollection<string>();
  x.CollectionChanged += 
    new NotifyCollectionChangedEventHandler(x_CollectionChanged);
  x.Add("asdf");
  x.Remove("asdf");
}

bool rollingBack = false;
void x_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
  if (rollingBack) return;

  if (e.Action == NotifyCollectionChangedAction.Remove)
  {
    if (e.OldItems.Contains("asdf"))
    {
      var oc = (ObservableCollection<string>)sender;
      rollingBack = true;
      oc.Add("asdf");
      rollingBack = false;
    }
  }
}
Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
  • One question => if i assign e.OldItems to my collection, wouldn't it again raise a collection changed event ? – Relativity Dec 25 '10 at 17:07
  • You can't assign e.OldItems, it's 1) a readonly property, 2) it returns a `ReadOnlyList`. – Shimmy Weitzhandler Dec 25 '10 at 17:10
  • I mean to say...My observable collection = e.oldItems..Can I change ReadOnlyList to an observable collection ? – Relativity Dec 25 '10 at 17:11
  • No. but you can simply readd/replace back the old items into your observable collection, don't forget that the `sender` argument passed to the `CollectionChanged` handler is the `ObservableCollection` itself. – Shimmy Weitzhandler Dec 25 '10 at 17:17
  • 1
    for instance `var x = (ObservableCollection)sender;` (your `ObservableCollection` and its type), then `foreach (var item in e.OldItems) x.Add(item);` – Shimmy Weitzhandler Dec 25 '10 at 17:20
  • So it wont raise a collection changed event again, Right ? – Relativity Dec 25 '10 at 17:29
  • It will, of course, and this will update your UI, but you're not intended to make the rollback from within the `CollectionChanged` handler anyway, this handler should be used to retrieve and backup the changed data so when the user click undo, you have it right there. In case you do want to make the rollback from the handler simply create a flag that the handler should escape when it's one, view my updated answer. – Shimmy Weitzhandler Dec 25 '10 at 17:32
  • 4
    How did you get this to work? When I try to do this I get an InvalidOperationException "Cannot change ObservableCollection during a CollectionChanged event." - I'm trying to allow a user to cancel from deleting a row in a datagrid – Tod Mar 24 '12 at 01:46
  • @Tod can u debug it and see where exactly is the source of your exception? – Shimmy Weitzhandler Mar 24 '12 at 19:56
  • Right on the .Add where I try to put the item back in the collection. – Tod Mar 24 '12 at 20:50
  • @Tod, it's been a long time since I dealt with this issue, I can't seem to remember what's going on. Hope one of the above people might be able to help. Sorry it's not me :( – Shimmy Weitzhandler Mar 25 '12 at 00:45
  • Thanks anyway, I decided to take a different approach to this. – Tod Mar 26 '12 at 18:46
1

Considering you get the sender of the event as an object (ie. the first parameter of the event) and the list of objects that were modified, yes you can do that. I wouldn't advise that though. If you encounter such a condition, provide a method on the ViewModel which is provided with the EventArgs, and let it do the work. The view isn't the place to do logic.

Even better: check for the condition in the ViewModel itself (ie in the commands that are responseable for adding/removing)! The viewmodel is responseable for the state of the information, so keep your logic there. The view is just there to display the data.

Femaref
  • 58,195
  • 7
  • 126
  • 170
  • One question => if i assign e.OldItems to my collection, wouldn't it again raise a collection changed event ? – Relativity Dec 25 '10 at 17:07
0

Shimmy's answer didn't work for me on a Windows Store application, you'll still run into re-entrancy issues and get an InvalidOperationException saying "Cannot change ObservableCollection during a CollectionChanged event."

I had to use the UI's dispatcher and disable/enable the event handler to avoid these issues.

Be warned: this is a hack, and the framework designers went to great lengths to prevent you from doings this. So if you want to ignore their warning, be careful not to shoot yourself in the foot.

Items.CollectionChanged += ItemsChanged;

private async void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if(condition)
    {
        //rollback
        await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
            CoreDispatcherPriority.Normal, () => {

                //disable/enable event handler
                Items.CollectionChanged -= ItemsChanged;

                Items.Remove(e.NewItems[0]);

                Items.CollectionChanged += ItemsChanged;
            })).AsTask();
    }
}

This will avoid the exception, avoid calling the handler recursively, and update the UI correctly.

dcastro
  • 59,520
  • 20
  • 126
  • 147
  • and the accepted answer doesn't work in a wpf desktop app either. Look at my [post](http://stackoverflow.com/questions/42528460/add-a-check-with-messagebox-when-datagrid-is-changed) too. –  Mar 01 '17 at 14:01