Domain model + Undo framework

May 14, 2010 at 12:41 PM

Dear Kirill,

Thanks for sharing your framework with us. It's well documented and easy to use.

I've started to play with it and I've found one confusing thing in documentation:

 

 /// <summary>
    /// Action Manager is a central class for the Undo Framework.
    /// Your domain model (business objects) will have an ActionManager reference that would 
    /// take care of executing actions.
    /// 
    /// Here's how it works:
    /// 1. You declare a class that implements IAction
    /// 2. You create an instance of it and give it all necessary info that it needs to know
    ///    to apply or rollback a change
    /// 3. You call ActionManager.RecordAction(yourAction)
    /// 
    /// Then you can also call ActionManager.Undo() or ActionManager.Redo()
    /// </summary>
    public class ActionManager

 

As I understand my domain model in MVVM pattern is my ViewModel/DataModel, e.g.:

 public class Person:INotifyPropertyChanged
    {
        private string firstName;

        public string FirstName
        {
            get { return firstName; }
            set 
            {
                if (value == firstName) return;
                SetValue("FirstName",value);
            }
        }

        private void SetValue(string propertyName,string newValue)
        {
            var undo = new PropertyChangedUndoEvent(propertyName, firstName, newValue, this);
            actionManager.RecordAction(undo);
        }

I can't figure out how to implement PropertyChangedUndoEvent to avoid throwing of an exception from ActionManager.

"ActionManager.RecordActionDirectly: the ActionManager is currently running or undoing an action (App.PropertyChangedUndoEvent), and this action (while being executed) attempted to recursively record another action"

My current implementation is:

 public class PropertyChangedUndoEvent:AbstractAction
    {
        private string propertyName;
        private string oldValue;
        private string newValue;
        private object target;
        private bool reentrencyGuard;
        public PropertyChangedUndoEvent(string propertyName,string oldValue,string newValue,object target)
        {
            this.propertyName = propertyName;
            this.oldValue = oldValue;
            this.newValue = newValue;
            this.target = target;
            AllowToMergeWithPrevious = true;
        }

        private void SetValue(string value)
        {
            var property = target.GetType().GetProperty(propertyName);
            property.SetValue(target, value,null);
        }


        protected override void ExecuteCore()
        {
            SetValue(newValue);
        }

        protected override void UnExecuteCore()
        {
            SetValue(oldValue);
        }
    }
Kirill, can you help me to deal with the problem, please?

Yours sincerely,

Maxim Filimonov

Coordinator
May 14, 2010 at 4:10 PM

Maxim, what happens is that you have a loop: calling the FirstName setter calls RecordAction which in turn calls FirstName setter again using Reflection - this will be an infinite loop, but the Undo manager detects that and gives you a warning.
 
Instead, you should try either:
 
SetValue("firstName",value);
...
        private void SetValue(string value)
        {
            var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            field.SetValue(target, value, null);
        }
 
Or have another property like FirstNameInternal, and call SetValue("FirstNameInternal", value); from within FirstName setter. This will break the loop.
 
Hope this helps,
Kirill

May 14, 2010 at 4:16 PM
Dear Kirill, Thanks for your response. I will try your suggestion I'm also thinking about forwarding undo framework to underlying model which will also break the loop. Yours sincerely, Maxim