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

时间:2012-07-17 20:20:45

标签: java user-interface undo command-pattern

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

我只需要在用户向JTree添加元素,属性或文本时添加undo / redo。

我还是很新,但今天在学校我试图(不成功)创建两个堆栈对象[],称为撤消和重做,并添加执行的动作。

例如,我有:

Action AddElement() {

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

                    performElementAction();
                }
}

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

我想添加一种方法来将此操作添加到我的撤消堆栈中。是否只有一种简单的方法来实现undo.push(执行的整个动作)或什么?

抱歉听起来像坏人,但那就是我:(

5 个答案:

答案 0 :(得分:7)

查看Command Pattern,其用途包括实现撤消/重做功能。

答案 1 :(得分:6)

TL; DR:您可以通过实施Command和Memento模式( Design Patterns - Gama et. al )来支持撤消和重做操作。

Memento模式

这个简单的模式允许您保存对象的状态。只需将对象包装在一个新类中,只要状态发生变化,就更新它。

public class Memento
{
    MyObject myObject;

    public MyObject getState()
    {
        return myObject;
    }

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

命令模式

Command模式存储原始对象(我们想要支持undo / redo)和memento对象,这是撤消时我们需要的。此外,还定义了两种方法:

  1. 执行:执行命令
  2. unExecute :删除命令
  3. 代码:

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

    定义扩展Command的逻辑“Actions”(例如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
        }
    }
    

    应用模式:

    最后一步定义了撤消/重做行为。它们的核心思想是存储一堆命令,这些命令用作命令的历史列表。要支持重做,只要应用撤消命令,就可以保留辅助指针。请注意,无论何时插入新对象,都会删除其当前位置之后的所有命令;这是通过以下定义的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();
    }
    

    结论:

    这种设计的强大之处在于您可以添加任意数量的命令(通过扩展Command类),例如RemoveCommandUpdateCommand等等。此外,相同的模式适用于任何类型的对象,使设计可重用可修改跨不同的用例。

答案 2 :(得分:1)

这个tutorial解释了命令模式的基本原理和Swing的撤销/重做机制。希望它有所帮助。

答案 3 :(得分:0)

我会尝试创建一个Action类,其中AddElementAction类继承Action。 AddElementAction可以有一个Do()和Undo()方法,它可以相应地添加/删除元素。然后你可以为undo / redo保留两堆Actions,在弹出它之前只调用top元素上的Do()/ Undo()。

答案 4 :(得分:0)

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

示例:

interface Command {

    void execute() ;

    void undo() ;

    void redo() ;
}

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

请查看此undo-redo命令文章,以便更好地理解。