Lets say I have a ViewModel in a WPF application with an ObservableCollection. This collection is synchronized with a Model collection so that updating either one updates the other. The model has a business rule that states it must contain 2 Thing's at all times, but the order in the collection can be changed. The user will see a list that has two Thing's in it, and has buttons they can click to move a Thing up or down. The model class subscribes to it's collection's change handler and throws an exception if there are ever less than 2 Things in the collection.
Something like this (very simplified, edge cases not handled etc)
public class ViewModel
{
private Model _model;
public ObservableCollection<Thing> Things { get; set; }
public Thing SelectedThing { get; set; }
public ViewModel(Model model)
{
_model = model;
Things = new ObservableCollection<Thing>(_model.Things);
// Some other code that ensures the model and viewmodel collections stay in sync
// ...
}
public void MoveUp()
{
var selected = SelectedThing;
int i = Things.IndexOf(selected);
Things.Remove(selected); // Throws exception
Things.Insert(i - 1, selected);
}
}
public class Model
{
public ObservableCollection<Thing> Things { get; set; }
public Model()
{
Things = new ObservableCollection
{
new Thing(),
new Thing()
};
Things.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(object sender, CollectionChangedEventArgs e)
{
if(e.Action == CollectionChangedAction.Remove)
{
if(Things.Count < 2)
{
throw new YouCantDoThatException();
}
}
}
}
Basically, when a user clicks "Move Up", the selected item is temporarily removed from the collection and inserted above where it was before. This is an invalid state for the model though, and I can't change the model because it's an API for a lot of other things. Is there a way in C# to do an "Atomic Move" operation that will allow me to move the item without first removing it?
I see two options:
Use the "Move" function provided by ObservableCollection. The disadvantage is that your code is in a state with just one item (albeit for a very short amount of time), but your exception will not throw since CollectionChanged isn't raised until the move is complete.
Reverse your logic, and perform the insert before the remove. You could do this with the InsertItem(index, item) function, followed by the RemoveAt(index) command.
In general, there is no such thing as an "Atomic" move because of how "swap" operations work. You need to assign the "first" value to a temp value, assign the second to the first, and then assign the temp value to the second. Certain assembly operations can make this "Atomic" in the purest sense, but that won't help with NotifyCollectionChanged.
As an aside, you can derive from ObservableCollection and implement your own move algorithm, which you shouldn't need to do unless you need Move to have additional functionality. (MSDN).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With