EditableObject is an object that provides functionality to commit and rollback changes to itself.
After verifying the accuracy of changes made to the object, you can accept the changes
using the
AcceptChanges method of the object, which will set the
Current field values
to be the
Original values. The
RejectChanges method rejects all changes made to the object
since
AcceptChanges was last called. The
IsDirty property gets a value that indicates
whether the object has changed.
If we wrote an editable object manually, we could get the following code just for two editable properties:
public class TestObject : INotifyPropertyChanged
{
// The FirstName editable property.
//
private string _originalFirstName;
private string _currentFirstName;
public override string FirstName
{
get { return _currentFirstName; }
set
{
_currentFirstName = value;
OnPropertyChanged("FirstName");
}
}
bool IsFirstNameDirty
{
get { return _currentFirstName != _originalFirstName; }
}
void AcceptFirstNameChange()
{
if (IsFirstNameDirty)
{
_originalFirstName = _currentFirstName;
OnPropertyChanged("FirstName");
}
}
void RejectFirstNameChange()
{
if (IsFirstNameDirty)
{
_currentFirstName = _originalFirstName;
OnPropertyChanged("FirstName");
}
}
// The LastName editable property.
//
private string _originalLastName;
private string _currentLastName;
public override string LastName
{
get { return _currentLastName; }
set
{
_currentLastName = value;
OnPropertyChanged("LastName");
}
}
bool IsLastNameDirty
{
get { return _currentLastName != _originalLastName; }
}
void AcceptLastNameChange()
{
if (IsLastNameDirty)
{
_originalLastName = _currentLastName;
OnPropertyChanged("LastName");
}
}
void RejectLastNameChange()
{
if (IsLastNameDirty)
{
_currentLastName = _originalLastName;
OnPropertyChanged("LastName");
}
}
// Common members.
//
public bool IsDirty
{
get
{
return IsFirstNameChange || IsLastNameChange;
}
}
public void AcceptChanges()
{
AcceptFirstNameChange();
AcceptLastNameChange();
}
public void RejectChanges()
{
RejectFirstNameChange();
RejectLastNameChange();
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}BLToolkit allows implementing the same functionality by inheriting your object
from the BLToolkit
EditableObject base class and replacing editable members with abstract properties.
EditableObjectTest.cs
using System;
using NUnit.Framework;
using BLToolkit.EditableObjects;
namespace HowTo.EditableObjects
{
[TestFixture]
public class EditableObjectTest
{
public abstract class TestObject : EditableObject<TestObject>
{
// Any abstract property becomes editable.
//
public abstract string FirstName { get; set; }
public abstract string LastName { get; set; }
// This field is not editable.
//
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
[Test]
public void Test()
{
TestObject obj = TestObject.CreateInstance();
obj.FirstName = "Tester";
obj.LastName = "Testerson";
Assert.IsTrue(obj.IsDirty);
obj.AcceptChanges();
Assert.IsFalse(obj.IsDirty);
}
}
}
BLToolkit type builder will generate the following for the class above:
[BLToolkitGenerated]
public sealed class TestObject : EditableObjectTest.TestObject, IEditable, IMemberwiseEditable, IPrintDebugState
{
// Note that the internal representation of the properties is EditableValue<string>.
// The EditableValue class provides a mechanism to keep and control the field value state.
//
private EditableValue<string> _firstName;
private EditableValue<string> _lastName;
// PropertyInfo is used for internal purposes.
//
private static PropertyInfo _firstName_propertyInfo =
TypeHelper.GetPropertyInfo(typeof(EditableObjectTest.TestObject), "FirstName", typeof(string), Type.EmptyTypes);
private static PropertyInfo _lastName_propertyInfo =
TypeHelper.GetPropertyInfo(typeof(EditableObjectTest.TestObject), "LastName", typeof(string), Type.EmptyTypes);
// Constructors.
//
public TestObject()
{
this._firstName = new EditableValue<string>("");
this._lastName = new EditableValue<string>("");
}
public TestObject(InitContext ctx)
{
this._firstName = new EditableValue<string>("");
this._lastName = new EditableValue<string>("");
}
// Abstract property implementation.
//
public override string FirstName
{
get
{
return _firstName.Value;
}
set
{
_firstName.Value = value;
// The PropertyChanged event support.
//
((IPropertyChanged)this).OnPropertyChanged(_firstName_propertyInfo);
}
}
public override string LastName
{
get
{
return _lastName.Value;
}
set
{
_lastName.Value = value;
((IPropertyChanged)this).OnPropertyChanged(_lastName_propertyInfo);
}
}
// The IEditable interface implementation.
//
bool IEditable.IsDirty
{
get { return _firstName.IsDirty || _lastName.IsDirty; }
}
void IEditable.AcceptChanges()
{
this._firstName.AcceptChanges();
this._lastName. AcceptChanges();
}
void IEditable.RejectChanges()
{
this._firstName.RejectChanges();
this._lastName. RejectChanges();
}
// The IMemberwiseEditable interface implementation.
//
bool IMemberwiseEditable.AcceptMemberChanges(PropertyInfo propertyInfo, string memberName)
{
return
_firstName.AcceptMemberChanges(_firstName_propertyInfo, memberName) ||
_lastName. AcceptMemberChanges(_lastName_propertyInfo, memberName);
}
void IMemberwiseEditable.GetDirtyMembers(PropertyInfo propertyInfo, ArrayList list)
{
_firstName.GetDirtyMembers(_firstName_propertyInfo, list);
_lastName. GetDirtyMembers(_lastName_propertyInfo, list);
}
bool IMemberwiseEditable.IsDirtyMember(PropertyInfo propertyInfo, string memberName, ref bool isDirty)
{
return
_firstName.IsDirtyMember(_firstName_propertyInfo, memberName, ref isDirty) ||
_lastName. IsDirtyMember(_lastName_propertyInfo, memberName, ref isDirty);
}
bool IMemberwiseEditable.RejectMemberChanges(PropertyInfo propertyInfo, string memberName)
{
return
_firstName.RejectMemberChanges(_firstName_propertyInfo, memberName) ||
_lastName. RejectMemberChanges(_lastName_propertyInfo, memberName);
}
// The IPrintDebugState interface implementation.
//
void IPrintDebugState.PrintDebugState(PropertyInfo propertyInfo, ref string str)
{
_firstName.PrintDebugState(_firstName_propertyInfo, ref str);
_lastName. PrintDebugState(_lastName_propertyInfo, ref str);
}
}