9

I tried to use Command Pattern for implementing Undo and Redo in my project

public abstract class Command
{
    protected Form Receiver { set; get; }
    protected HtmlElement Element { set; get; }
    abstract public void ReDo();
    abstract public void UnDo();
    public Command(Form receiver)
    {
        this.Receiver = receiver;
    }
}
class AddElementCmd : Command
{        
    public AddElementCmd(HtmlElement elem, Form receiver)
        : base(receiver)
    {
        Element = elem;
    }
    public override void ReDo()
    {
        ((FormEdit)Receiver).AddElement(Element,false);
    }
    public override void UnDo()
    {
        ((FormEdit)Receiver).DelElement(Element, false);
    }
}
class DelElementCmd : Command
{
    public DelElementCmd(HtmlElement elem, Form receiver)
        : base(receiver)
    {
        Element = elem;
    }
    public override void ReDo()
    {
        ((FormEdit)Receiver).DelElement(Element, false);
    }
    public override void UnDo()
    {
        ((FormEdit)Receiver).AddElement(Element, false);
    }
}

Implementation of AddElement command in FormEdit.

public void AddElement(HtmlElement elem, bool isNew = true)
{
    IHTMLElement2 dom = elem.DomElement as IHTMLElement2;
    if (isNew)
    {
        Command cmd = new AddElementCmd(elem, this);
        Undo.Push(cmd);
        Redo.Clear();
    }    
    // some codes here....
    if (showAlltoolStripButton.Checked)
    {
        dom.runtimeStyle.visibility = "hidden";
    }
    else if (showSelectionToolStripButton.Checked)
    {
        dom.runtimeStyle.visibility = "visible";
    }
 }
...

the Undo and Redo stacks are stored in the FormMain class and are passed to the editor form.

public Stack<Command> Undo = new Stack<Command>();
public Stack<Command> Redo = new Stack<Command>();

....
FormEdit editor = new FormEdit ();
editor.Browser = webBrowser1;
editor.addedElements = addedElements;
editor.restoreElements = restoreElements;
editor.Undo = Undo;
editor.Redo = Redo;

When in a new FormEdit the user clicks on Redo or Undo button, the corresponding function in the FormEdit is executed, but as I checked this receiver of the command is the form in which the command was first created and now may has been disposed. I expect that the program raise an error, but it seems the Command object stores a reference to the old form and this leads to misbehavior.

Therefore, I think that I must find a consistent receiver for the commands, either the main form or the webBrowser control, which has the same life time as the commands themselves. But yet I should have access to some controls related to the commands.

Where is the best place to implement the command functions as the receiver of Command objects? Or any other way to associate the new form to a command popped from stack.

  • I think this decision is on you. We can't help you because we don't know the specification or functional requirements of your application. – Euphoric Dec 22 '15 at 14:45
  • 8
    I believe Command objects should only contain serializable data (i.e., no references to other objects) since common uses for them include sending their serialized forms across networks, saving them to a file for later, or replaying them on a different receiver (if you want your changes to show up on my screen in real time, for instance). That may mean you want to pass in the Receiver to each command method, or maybe give the Receiver executeCommand()/undoCommand() methods that let it pass itself in, or maybe use command objects that only contain method names/arguments instead of code. – Ixrec Dec 22 '15 at 14:47
  • An implementation example. And, an SO discussion – radarbob Dec 22 '15 at 15:22
  • @Ixrec Thank you for your advice, then you mean I should be able to set the Receiver of each command object, I am going to do this. – Ahmad Dec 22 '15 at 15:46
  • Consider using the memento pattern instead. – P. Roe Feb 8 '16 at 18:24
1

The Command pattern should apply to the model, and not the UI. In your case, make it

protected HtmlDocument Receiver { set; get; }
protected HtmlElement Element { set; get; }

To update the UI, use the Observer pattern, so all open forms and their controls can react to changes in the underlying model.

Your code will become clearer and more decoupled because Command can take care of only changing the document, and the observers in the UI only have to update the controls without regard to exactly what changed.

When a form closes, it will unregister itself as an observer, and no references to it will be kept.

If a new form is opened after a change to the document, it will be notified after an undo even if it wasn't present when the original change was made.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.