Search code examples
c#vb.netwinformsundoredo

How to implement undo, redo for a WinForms Image/Drawing application efficiently


I have a WinForms image/paint application.

That application allows user to do the followings:

  • load/import image
  • save image
  • rotate, crop image
  • change color for a pixel or a region

How do I implement undo/redo function for multiple actions in an efficient method? Which mouse event should I handle?

I want to avoid saving the entire content in a picture box(where the image is loaded) as it may consume huge memory.

Thank you.


Solution

  • The best way I've seen to implement undo / redo is to use the "Command" pattern.

    Every operation that can be performed should be implemented as a class that either inherits from a base Command class, or implements an ICommand interface. The class should have an Execute and an Undo method, where Execute performs the operation on the data, and stores any information needed to undo the operation. Undo uses the stored information about the change to undo to.

    If you just want a simple system, where you can only undo / redo the last operation, just keep a lastCommand field that you store the last run command in, and call Undo on it when needed.

    If you want something more complex, with multiple levels of undo, keep an ordered list of commands that you sort of treat like a Stack. Each executed Command is executed, and placed on the top of the stack, some variable pointing to the last command. As you undo operations, call Undo on lastCommand, then set it to the Command before it. To Redo the operation, just call Execute again on the next command, and move the pointer up. You will have to make sure that any Command you run when you are not at the "top" of the stack invalidates and removes operations ahead of it.

    You can get even more advanced, with fancy things like commands merging together if you want.


    For an example of the kind of thing you would do for a Command that can change a single pixel (All from memory, without access to Visual Studio, so please forgive any syntax errors).

    Since you didn't specify a language, I'll use C#, but this could easily be done in VB.NET also.

    public class ChangePixelCommand : ICommand
    {    
        private Color _previousColor;
        private int x;
        private int y;
    
        public void Execute(Bitmap image, int x, int y, Color color)
        {
            // record the data needed to undo this operation.
            _previousColor = image.GetPixel(x, y);
            _x = x;
            _y = y;
    
            // update the image.
            image.SetPixel(x, y, color);
        }
    
        public void Undo(Bitmap image)
        {
            //  Use the data we saved earlier to put the pixel back the way it was.
            image.SetPixel(_x, _y, _previousColor);
        }
    }
    

    To use it in the "simple" method above, you would just do something like this:

    // Change a pixel.
    lastCommand = new ChangePixelCommand(myImage, 
                                         Control.MousePosition.X,
                                         Control.MousePosition.Y, 
                                         Colors.Red);
    lastCommand.Execute();
    
    // Undo the change
    lastCommand.Undo();
    

    For changing a region, you can store just the bounds of the rectangle that changed, and a small snapshot of what that region looked like before.

    For Rotation, it would just by the angle of rotation.

    I'm not sure why you anyone would ever want to "undo" a save, but if you really need to, you could store the filename and deleting the file. Restoring the previous contents of the file though... that could get tricky.