Deliverable: n-level undo
Let us start with the simple 2-level undo that we have already
seen in my demo application. This was where the program allowed
an undo of changes made to both the customer, as well as any
of the customer's order headers.
Firstly the application set things up by instructing the CSLA
framework to take a snapshot of the Customer BO, including any imbedded order headers, before allowing the
user to gain access to it:
_customer.BeginEdit();
Data at both levels was then turned over to data binding for use by the UI.
customerBindingSource.DataSource = _customer;
customerOrdersBindingSource.DataSource = _customer.Orders;
The user then was allowed to make data entry changes to the customer
level data, and it's collection of customer order headers. Data binding
updated our Customer BO and the embedded collection of Order BO's.
Data binding worked behind the scenes to issue BeginEdit and
ApplyEdit instructions at the Order Header level, as the user changed, then
departed from, order rows.
Nothing is persisted to the database unless the user presses the
“Save” button, which calls the following line of code.
The Customer object knows how, and when, to save any changed rows in
it's Order collection. The UI can therefore ignore the Order objects,
as from it's perspective it is dealing with a Customer object that
has an Orders property. The database update is essentially triggered
via the following.
customerBindingSource.EndEdit();
customerOrdersBindingSource.EndEdit();
_customer.Save();
If the user wanted to discard the changes, this could be done by
pressing the CancelEdit button, which allows us to achieve a 2-level
undo with the following instruction. All data, including any changed
orders, is reset to the snapshot.
_customer.CancelEdit();
To proceed beyond a 2-level undo we need to extend our application to
also open a modal form. The first form can open the modal form,
passing it the active grid row's Order BO from the Customer's
collection. The modal form will display that order's details, and
allow us to update the freight value.
To
run this demonstration I opened the main screen, and used it to alter
the freight amount of the second order item from the database's
original $71.07 to $11.11. I did not press the “save”
button, so although the Customer BO has been modified I still had the
option of undoing my change.
I then clicked the order's ID link field, opening the modal form
showing just the altered order. The freight value showed the changed
value of $11.11. I used the modal form to change the freight
amount again, this time to $22.22. The change was reflected back into
the underlying main form, as both forms are working with the same
Order BO. The modal form's “Cancel” button took me back
to the modified value of $11.11 which was the value that it 1st
saw, but would not take me back to the original $71.07 which was
loaded from the database. The modal screen's “cancel”
action also caused the underlying main screen's value to be reset to
$71.07.
I
changed the modal screen's value back to $22.22 again, pressed the
modal screen's “save” button, and then closed the form.
The main form regained focus, with the second order line showing
the $22.22 which the modal screen had updated into my BO. When I
pressed the main form's “Cancel” button the freight
amount was reset back to the original $11.11. This action restored us
to the original value, wiping out changes made in n-levels (ie. both
the main and modal forms).
All of this was achieved without referring back to the database.
The database did not receive any of the updates as the main form's
“save” button was not pressed. The order details are
children of the customer BO. They are obtained from the database by
the customer BO, and can only be updated back to the database by the
Customer BO.

The code required to achieve this n-level undo capacity is very
simple. The main form loads the modal form, passing it the Order BO
shown by the current grid row.
private void customerOrdersDataGridView_CellContentClick(object sender,
DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == iDDataGridViewLinkColumn.Index)
{
/* close off 1-level undo support for the current grid row
* implicitly started by .Net data binding */
customerOrdersBindingSource.EndEdit();
using (OrderDetailForm ordersForm =
new OrderDetailForm((Order)customerOrdersBindingSource.Current))
{
ordersForm.ShowDialog();
}
}
}
The modal form's constructor is as follows. We really only need focus
upon the incoming parameter, and the line where we issue a
BeginEdit call against that order. Most of the remainder of the
method deals with obtaining the order detail lines for display purposes.
public OrderDetailForm(Order order)
{
InitializeComponent();
_order = order;
this.readWriteAuthorization1.ResetControlAuthorization();
this.Text = "Update freight for order number " + _order.Id.ToString();
_order.BeginEdit();
orderBindingSource.DataSource = _order;
try
{
OrderLineList _orderLines = OrderLineList.GetOrderLines(_order.Id);
if (_orderLines != null)
{
orderLineListBindingSource.DataSource = _orderLines;
}
}
catch (Csla.DataPortalException ex)
{
MessageBox.Show(ex.BusinessException.ToString(),
"Data load error", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(),
"Data load error", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
The remainder of the modal program is as follows. Really, the only
point to notice from all this is that the CSLA framework has given us
n-level undo without any effort on our part.
private void SaveButton_Click(object sender, EventArgs e)
{
/* commit change, then make a new snapshot so that any further changes
* are undoable.*/
orderBindingSource.EndEdit();
_order.BeginEdit();
}
private void CancelButton_Click(object sender, EventArgs e)
{
_order.CancelEdit();
_order.BeginEdit();
}
private void OrderDetailForm_FormClosing(object sender, FormClosingEventArgs e)
{
_order.CancelEdit();
}
private void orderBindingSource_CurrentItemChanged(object sender, EventArgs e)
{
SaveButton.Enabled = _order.IsValid;
}
}
The n-level undo was achieved via the main form's “cancel”
button.
private void cancelButton_Click(object sender, EventArgs e)
{
_customer.CancelEdit();
_customer.BeginEdit();
} |