【问题标题】:How do I implement a simple undo/redo for actions in java?如何在 java 中为操作实现简单的撤消/重做?
【发布时间】:2012-07-16 20:00:15
【问题描述】:

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

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

我对此还是很陌生,但今天在学校我尝试(未成功)创建两个堆栈对象 [],称为 undo 和 redo,并将执行的操作添加到其中。

例如,我有:

Action AddElement() {

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

                    performElementAction();
                }
}

performElementAction 实际上只是将一个元素添加到 JTree。

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

【问题讨论】:

  • 看看Command Pattern,它的用途包括实现撤消/重做功能。
  • 一定要看看内置的撤销支持;我从未使用过它,也找不到它的 Swing 教程,但 here 是经理。

标签: java user-interface undo command-pattern


【解决方案1】:

TL;DR:您可以通过实现命令和备忘录模式来支持撤消和重做操作 (Design Patterns - Gama et. al)。

纪念品模式

这个简单的模式可以让你保存一个对象的状态。只需将对象包装在一个新类中,只要其状态发生变化,就对其进行更新。

public class Memento
{
    MyObject myObject;

    public MyObject getState()
    {
        return myObject;
    }

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

命令模式

命令模式存储原始对象(我们希望支持撤消/重做)和备忘录对象,我们在撤消时需要这些对象。此外,还定义了两种方法:

  1. 执行:执行命令
  2. unExecute:删除命令

代码:

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

结论:

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

【讨论】:

  • 您将如何限制撤消/重做步骤的数量?无法调整堆栈的大小。
  • @Atlas2k 你可以有一个阈值(例如,int threshhold = 30),然后根据当前堆栈大小删除所有“旧”命令。例如,如果您调用 insertCommand() 并且您的 commandStack.size() 大于 30,则删除第一个元素 (commandStack.remove(0))。主要思想是将您的堆栈限制在某个阈值大小。是的,更复杂的用例,但绝对可行:)
  • 我按照这个设置了我的最大堆栈大小:ntsblog.homedev.com.au/index.php/2010/05/06/…
【解决方案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() 方法后的当前状态,您必须决定是否应将命令添加到 Undo Stack 或 Redo Stack 并做出相应的决定。

请查看此undo-redo 命令文章以获得更好的理解。

【讨论】:

    【解决方案3】:

    我会尝试创建一个Action 类,AddElementAction 类继承Action。 AddElementAction 可以有一个 Do() 和 Undo() 方法,可以相应地添加/删除元素。然后,您可以保留两个操作堆栈用于撤消/重做,并在弹出之前在顶部元素上调用 Do()/Undo()。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 2013-11-14
    • 2015-08-10
    • 2016-02-18
    • 1970-01-01
    • 2018-08-23
    相关资源
    最近更新 更多