Supporting Transacted Object Editing with lEditable Object

If you ran the CustomBusinessObjects sample from the last section, and edited a property on either a Customer or Order object in the grid, then tabbed into the next field, the edit to that property is immediately written to the underlying bound object property. If there are any other controls that are data bound to the same object and displaying it, you should see their display immediately update to the new value as well. This is because the change was made through a data-bound control, which notifies the currency manager controlling the data binding of the change, and that change will then be rippled to any other bound controls that are in communication with the same currency manager.

This behavior may or may not be what you want. Sometimes when you are editing an object, you want changes to that object to be treated in a transacted way, with all changes to properties of a single object being done all together or not at all. Say you have a custom object with three properties bound to controls on a form. You edit properties one and two of the object, and then something goes wrong while editing property three. Do you want the changes to properties one and two to remain in effect, or do you want them to revert to what they were before the editing operation on that object was initiated? Or perhaps you have interdependencies between properties, where the first property can only be between 1 and 10 if it is Tuesday, but otherwise must be between 10 and 20. If the second property tells you what day it is, you won't know whether you have a valid combination of row values until they have all been edited. In a transacted world, the changes to those properties could be rolled back to their previous values automatically if something went wrong or if you decided you wanted to abort the object editing process.

The DataRowView class has built-in support for this kind of thing, as does thfiataGridView. If you have a data set bound to a grid and start editing the fields in a row and then press the Esc key, the changes you made to that row will be rolled back, changing the values back to their original values before the editing of that row commenced. If you shift the focus to a new row with the arrow keys, tab off the last field in a row, press the Enter key, or click on a different control or row in the form, the changes are accepted (committed), and you will no longer have the opportunity to revert them to their previous values.

Likewise, if your grid is set up to let users add new rows, and they commence entering values for a new row, those entered values have to go somewhere, and the grid is only displaying rows that are actually part of the bound table. So once you start editing fields within the empty row at the bottom of a grid that is marked with an asterisk in the row header cell, a new DataRow is actually added to the table and you are editing the fields in that row as you edit the row in the grid. But if you press the Esc key during the editing of the new row, the grid is able to remove the row from the table and pretend that it never existed. This is another form of transacted object editing, where a new object isn't considered to be fully part of the collection until the initial editing of that newly created object is complete. However, for data-binding display purposes, you usually need that object to physically be added to the collection before the editing is complete, so that the entered field or property values have an object in memory on which they can be set and which can be displayed by the normal data-binding mechanisms.

To support this kind of transacted editing of your objects in a form, you need to implement the lEditableObject interface on the custom object definition. This interface includes three methods that you need to implement on your class: BeginEdit, EndEdit, and CancelEdit. As you might expect, from a transaction perspective, these operations logically correspond to beginning a transaction, committing the transaction, and rolling back the transaction.

® BeginEdit should be called by a data-bound control when an editing operation is commenced against a single instance of an object.

® EndEdit should be called when the editing process is complete.

® If CancelEdit is called anytime between BeginEdit and EndEdit, any changes made since BeginEdit was called should be rolled back.

® CancelEdit shouldn't be called after EndEdit unless BeginEdit is called again to initiate a new transaction.

What you typically need to do inside the implementation of the interface methods is to cache changes to the object using some temporary variables to keep track of the property's original values before the editing operation commenced from the BeginEdit method. Then if editing is completed normally with a call to EndEdit, you can discard the cached original values and treat the current values of the properties as permanent. If CancelEdit is called, you should use the cached original values to reset the values of the object properties to their original values.

To do this on your simple Customer object type requires the following additions to the class:

public class Customer: lEditableObject {

private int m_OldCustomerld;

private string m_OldName;

private bool m_Editing;

// other fields, properties, and methods ...

public void BeginEdit() {

m_OldCustomerld = m_Customerld; m_OldName = m_Name;

m_Editing = true;

public void CancelEdit() {

Customerld = m_OldCustomerld; CustomerName = m_OldName;

m_Editing = false;

public void EndEdit() {

m_Editing = false;

First, you add the lEditableObject interface to your class definition. You then need somewhere to stuff the cached original values of properties when an edit operation is in progress. To keep the sample simple, this just caches the values of the object's name and ID properties in additional private fields on the class named m_OldCustomerld and m_OldName. For a more complex object with many fields, having two of every field would get cluttered, so you could also simplify this process by creating a new instance of your custom object type, populating its fields with the original values, and holding a private reference to that object as the original values to revert to if CancelEdit is called. Note that this sets the current values through the properties, rather than directly on the member variables. The reason for this will become apparent in the next section.

As you can see from the code, the BeginEdit method stores the current values of the fields in the cached fields and sets a flag to indicate that you are in edit mode. This is necessary because depending on how data binding is hooked up, BeginEdit will likely be called multiple times by the form, and you want to store the old values just when the edit operation really begins, not repeatedly as the rest of the editing process gets going.

EndEdit effectively accepts the current values by setting the editing flag back Walse. CancelEdit rolls back the changes by setting the current property values back to the original value using the cached values.

By implementing lEditableObject like this, your custom business objects will now behave like a data set when being edited in a grid like the DataGridView. Specifically, if several properties of a given row have been edited, and the user presses the Esc key, the changes will be rolled back and the original values will be reset on the object.

This simple implementation doesn't account for the fact that conceptually the Orders collection could also be changed as part of the logical editing of a Customer object. You would want to be able to roll back those changes as well since they are child objects, and conceptually they are "part of a Customer object. However, since the lEditableObject interface is primarily used in a data-binding context, you would have to switch the focus to the Orders grid to modify theOrders collection associated with the selectedCustomer, and that change of control focus is going to call EndEdit on the Customer object anyway. There may be more advanced scenarios where you might want to use the lEditableObject implementation to support fully transacted editing of an object, whether through a bound control or through programmatic edits, but that isn't really what it was designed for.



Was this article helpful?

0 0

Post a comment