EditableObject class

BLToolkit.NET

Business Logic Toolkit for .NET www.bltoolkit.net
 

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);
    }
}
 
© 2010 www.bltoolkit.net
[email protected]