Change Property Command

February 11, 2009

Many applications requires history support (undo/redo). I found class ChangPropertyCommand very helpful for such tasks, because it allows you to specify any property of the object to be changed, so you don’t need to write many classes.

public class ChangePropertyCommand : Command
{

    private var Target_;
    private var NewValue_;
    private var OldValue_;
    private string PropertyName_;
    public ChangePropertyCommand(object Target, string PropertyName, object Value)
    {
        Target_ = Target;
        NewValue_ = Value;
        PropertyName_ = PropertyName;
        OldValue_ = GetValue();
    }

    public override void Execute()
    {
        base.Execute();
        SetValue(NewValue_);
    }

    public override void UnExecute()
    {
        base.UnExecute();
        SetValue(OldValue_);
    }

    private void SetValue(object Value)
    {
        object tmp = Target_;
        var type = Target_.GetType();
        string[] paths = PropertyName_.Split(".");
        PropertyInfo prop = type.GetProperty(paths(0));
        for (int i = 0; i <= paths.Length - 2; i++) {
            prop = type.GetProperty(paths(i));
            tmp = prop.GetValue(tmp, null);
            type = tmp.GetType;
        }
        prop = type.GetProperty(paths(paths.Length - 1));
        prop.SetValue(tmp, Value, null);
    }

    private object GetValue()
    {
        object ret = Target_;
        var type = Target_.GetType();
        string[] paths = PropertyName_.Split(".");
        foreach (string path in paths) {
            var prop = type.GetProperty(path);
            ret = prop.GetValue(ret, null);
            if (ret != null) {
                type = ret.GetType;
            }
        }
        return ret;
    }
}

Imports System.Reflection

Public Class ChangePropertyCommand
Inherits Command

Private Target_
Private NewValue_
Private OldValue_
Private PropertyName_ As String
Sub New(ByVal Target As Object, ByVal PropertyName As String, ByVal Value As Object)
Target_ = Target
NewValue_ = Value
PropertyName_ = PropertyName
OldValue_ = GetValue()
End Sub

Public Overrides Sub Execute()
MyBase.Execute()
SetValue(NewValue_)
End Sub

Public Overrides Sub UnExecute()
MyBase.UnExecute()
SetValue(OldValue_)
End Sub

Private Sub SetValue(ByVal Value As Object)
Dim tmp As Object = Target_
Dim type = Target_.GetType()
Dim paths As String() = PropertyName_.Split(".")
Dim prop As PropertyInfo = type.GetProperty(paths(0))
For i As Integer = 0 To paths.Length – 2
prop = type.GetProperty(paths(i))
tmp = prop.GetValue(tmp, Nothing)
type = tmp.GetType
Next
prop = type.GetProperty(paths(paths.Length – 1))
prop.SetValue(tmp, Value, Nothing)
End Sub

Private Function GetValue() As Object
Dim ret As Object = Target_
Dim type = Target_.GetType()
Dim paths As String() = PropertyName_.Split(".")
For Each path As String In paths
Dim prop = type.GetProperty(path)
ret = prop.GetValue(ret, Nothing)
If ret IsNot Nothing Then
type = ret.GetType
End If
Next
Return ret
End Function

End Class

Here some example of using this class:

    ChangePropertyCommand width = new ChangePropertyCommand(CurrentElement, "Width", Current.Width);
    ChangePropertyCommand height = new ChangePropertyCommand(CurrentElement, "Height", Current.Height);

Dim width As New ChangePropertyCommand(CurrentElement, "Width", Current.Width)
Dim height As New ChangePropertyCommand(CurrentElement, "Height", Current.Height)

As you can see, this code creates two commands, they will change properties Width and Height of the CurrentElement object, by values from Current object.
Often, Execute and Unexecute methods are not called directly, but called by History class.