如何在java中实现操作的简单撤消/重做?

2022-09-03 06:47:23

我已经创建了一个XML编辑器,但我陷入了最后一个阶段:添加撤消/重做功能。

我只需要在用户向JTree添加元素,属性或文本时添加撤消/重做。

我对此还很陌生,但在今天的学校里,我试图(不成功)创建两个名为撤消和重做(undo and redo)的堆栈对象[],并将执行的操作添加到其中。

例如,我有:

Action AddElement() {

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

                    performElementAction();
                }
}

performElementAction实际上只是向JTree添加了一个元素。

我想添加一种方法来将执行的操作添加到我的撤消堆栈中。有没有一种简单的方法可以撤消.push(执行的整个操作)或其他东西?


答案 1

TL;DR:您可以通过实现命令 (p.233) 和 Memento (p.283) 模式(设计模式 - Gamma 等)来支持撤消和重做操作。

纪念品图案

此简单模式允许您保存对象的状态。只需将对象包装在新类中,每当其状态更改时,请更新它。

public class Memento
{
    MyObject myObject;
    
    public MyObject getState()
    {
        return myObject;
    }
    
    public void setState(MyObject myObject)
    {
        this.myObject = myObject;
    }
}

命令模式

Command 模式存储原始对象(我们希望支持撤消/重做)和 memento 对象,在撤消时需要它们。此外,还定义了 2 种方法:

  1. 执行:执行命令
  2. 取消执行:删除命令

法典:

public abstract class Command
{
    MyObject myObject;
    Memento memento;
    
    public abstract void execute();
    
    public abstract void unExecute();
}

它们定义了扩展命令的逻辑“操作”(例如插入):

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
    }
}

应用模式:

最后一步定义撤消/重做行为。核心思想是存储一堆命令,该命令用作命令的历史记录列表。若要支持重做,可以在应用撤消命令时保留辅助指针。请注意,每当插入新对象时,都会删除其当前位置之后的所有命令;这是通过下面定义的方法实现的:deleteElementsAfterPointer

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();
}

结论:

这种设计之所以强大,是因为您可以根据需要添加任意数量的命令(通过扩展类),例如 ,等等。此外,相同的模式适用于任何类型的对象,使设计在不同的用例中可重用修改CommandRemoveCommandUpdateCommand


答案 2

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

例:

interface Command {

    void execute() ;

    void undo() ;

    void redo() ;
}

在 ConcreteCommand 类中定义一个状态。根据 execute() 方法后的当前状态,您必须决定是否应将命令添加到撤消堆栈或重做堆栈,并做出相应的决策。

请查看此撤消-重做命令文章,以便更好地理解。


推荐