Creating Objects that Support Edit Cancellation via IEditableObject
The IEditableObject interface provides controls with a way
to allow a data source to react intelligently to being edited. This might seem superfluous, until you
consider that complex controls, such as a data grid, allow the user to cancel
an editing session via the Escape key.
For example, suppose the user is editing a row in the
UltraGrid or XamDataGrid, and then realizes that he has been editing the wrong
row. If he presses the Escape key once,
the grid automatically reverts the active cell to the value it had before
editing began. If the user hits Escape
again, the entire row reverts to the values it had before the user started to
edit its cells.
If the grid is bound to an ADO.NET container, such as
DataTable, all of this magic happens for us automatically. However, if the grid is bound to a collection
of your own objects, such as custom business objects, this will not happen by
default. Your business objects will need
to implement that logic, just as the ADO.NET containers do. Perhaps your business objects will not
implement that functionality, since it is not part of any business domain, but
Presentation Model objects might, instead.
Regardless of the lingo, at the end of the day you will need to
implement this logic somewhere!
Fortunately, this is quite easy to do. The IEditableObject interface is all you need
to implement, as seen below:
private BinaryFormatter _formatter = new BinaryFormatter();
private MyData _myState;
private MemoryStream _snapshot;
void IEditableObject.BeginEdit()
{
if (_snapshot != null)
return;
_snapshot = new MemoryStream();
_formatter.Serialize(_snapshot, _myState);
}
void IEditableObject.CancelEdit()
{
if (_snapshot == null)
return;
// Restore our state to the snapshot taken when the editing session began.
_snapshot.Position = 0;
_myState = _formatter.Deserialize(_snapshot) as MyData;
this.ThrowAwaySnapshot();
}
void IEditableObject.EndEdit()
{
this.ThrowAwaySnapshot();
}
void ThrowAwaySnapshot()
{
if (_snapshot != null)
{
_snapshot.Dispose();
_snapshot = null;
}
}
This code assumes the MyData type (and all of its ancestor
types) is decorated with the Serializable attribute, since it is serialized by
the BinaryFormatter.
The BeginEdit method starts with a check to see if we are
already in an editing session. If so, it
immediately returns since there can only be one editing session at a time. There is no guarantee regarding when and how
often that method will be invoked, so this precautionary step is
necessary. When a new editing session
begins, a snapshot of the object's state is taken and stored in a MemoryStream.
If the user cancels the editing session, the CancelEdit
method executes. That method deserializes
the snapshot taken in BeginEdit, and applies the saved values to the editable
object. If the IEditableObject instance contains
the various fields being edited, instead of having a reference to one object
that contains all the values, your CancelEdit method will include code that
sets all of those fields to whatever values were saved in the BeginEdit method.
When the user completes an editing session (i.e. finishes
editing a row in the grid) the EndEdit method is invoked. That gives us a chance to dispose of the
snapshot data. Once the snapshot is
removed, a subsequent call to BeginEdit will cause a new editing session to
begin.