Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement a simple undo/redo for actions in java?

I've created an XML editor and I'm stuck at the last phase: adding undo/redo functionality.

I've only got to add undo/redo for when users add elements, attributes, or text to the JTree.

I'm still quite new at this but in school today I attempted (unsuccessfully) to create two stack object []'s called undo and redo and to add the actions performed into them.

For instance, I have:

Action AddElement() {

// some code
public void actionPerformed(ActionEvent e) {

                    performElementAction();
                }
}

the performElementAction just actually adds an Element to the JTree.

I want to add a way to add this action performed to my undo stack. is there a simple way to just undo.push( the entire action performed) or something?

like image 948
Chea Indian Avatar asked Jul 17 '12 20:07

Chea Indian


People also ask

How do you implement undo and redo in Java?

If “UNDO” string is encountered, pop the top element from Undo stack and push it to Redo stack. If “REDO” string is encountered, pop the top element of Redo stack and push it into the Undo stack. If “READ” string is encountered, print all the elements of the Undo stack in reverse order.

How do I undo an action in Java?

AddElementAction could have a Do() and Undo() method which would add/remove elements accordingly. You can then keep two stacks of Actions for undo/redo, and just call Do()/Undo() on the top element before popping it.

How is undo/redo implemented?

One way to implement a basic undo/redo feature is to use both the memento and command design patterns. Memento aims at keeping the state of an object to be restored later for example. This memento should be as small as possible in an optimisation purpose.

How will you implement undo and redo option using the data structure?

Which data structure is used in redo-undo feature? Explanation: Stack data structure is most suitable to implement redo-undo feature. This is because the stack is implemented with LIFO(last in first out) order which is equivalent to redo-undo feature i.e. the last re-do is undo first.


2 Answers

TL;DR: You can support undo and redo actions by implementing the Command and Memento patterns (Design Patterns - Gama et. al).

The Memento Pattern

This simple pattern allows you to save the states of an object. Simply wrap the object in a new class and whenever its state changes, update it.

public class Memento
{
    MyObject myObject;

    public MyObject getState()
    {
        return myObject;
    }

    public void setState(MyObject myObject)
    {
        this.myObject = myObject;
    }
}

The Command Pattern

The Command pattern stores the original object (that we want to support undo/redo) and the memento object, which we need in case of an undo. Moreover, 2 methods are defined:

  1. execute: executes the command
  2. unExecute: removes the command

Code:

public abstract class Command
{
    MyObject myObject;
    Memento memento;

    public abstract void execute();

    public abstract void unExecute();
}

The define the logical "Actions" that extend Command (e.g. Insert):

public class InsertCharacterCommand extends Command
{
    //members..

    public InsertCharacterCommand()
    {
        //instantiate 
    }

    @Override public void execute()
    {
        //create Memento before executing
        //set new state
    }

    @Override public void unExecute()
    {
        this.myObject = memento.getState()l
    }
}

Applying the patterns:

This last step defines the undo/redo behaviour. They core idea is to store a stack of commands that works as a history-list of the commands. To support redo, you can keep a secondary pointer whenever an undo command is applied. Note that whenever a new object is inserted, then all the commands after its current position get removed; that's achieved by the deleteElementsAfterPointer method defined below:

private int undoRedoPointer = -1;
private Stack<Command> commandStack = new Stack<>();

private void insertCommand()
{
    deleteElementsAfterPointer(undoRedoPointer);
    Command command =
            new InsertCharacterCommand();
    command.execute();
    commandStack.push(command);
    undoRedoPointer++;
}

private void deleteElementsAfterPointer(int undoRedoPointer)
{
    if(commandStack.size()<1)return;
    for(int i = commandStack.size()-1; i > undoRedoPointer; i--)
    {
        commandStack.remove(i);
    }
}

 private void undo()
{
    Command command = commandStack.get(undoRedoPointer);
    command.unExecute();
    undoRedoPointer--;
}

private void redo()
{
    if(undoRedoPointer == commandStack.size() - 1)
        return;
    undoRedoPointer++;
    Command command = commandStack.get(undoRedoPointer);
    command.execute();
}

Conclusion:

What makes this design powerful is the fact that you can add as many commands as you like (by extending the Command class) e.g., RemoveCommand, UpdateCommand and so on. Moreover, the same pattern is applicable to any type of object, making the design reusable and modifiable across different use cases.

like image 181
Menelaos Kotsollaris Avatar answered Sep 18 '22 18:09

Menelaos Kotsollaris


You have to define undo(), redo() operations along with execute() in Command interface itself.

example:

interface Command {

    void execute() ;

    void undo() ;

    void redo() ;
}

Define a State in your ConcreteCommand class. Depending on current State after execute() method, you have to decide whether command should be added to Undo Stack or Redo Stack and take decision accordingly.

Have a look at this undo-redo command article for better understanding.

like image 28
Ravindra babu Avatar answered Sep 19 '22 18:09

Ravindra babu