三, UndoManager
要实现多次的Undo,Redo,必须要有一个数据结构管理多个UndoableCommand, 这个数据结构可以有多种选择, ArrayList, LinkedList, Stack等都可以。这里用下标访问元素的操作要多一些,所以采用ArrayList。另外,还要考虑多线程的情况,Java Swing 的UndoManager采用的是Vector, 但是有些情况下,操作只是在一个线程上工作,这时候用
ArrayList能获得更好的性能。这里的UndoManager提供了单线程和多线程两种选择。
private List<UndoableCommand> commandList;
private UndoManager(List<UndoableCommand> list) {
commandList = list;
}
public static UndoManager getInstance() {
return new UndoManager(new ArrayList<UndoableCommand>());
}
public static UndoManager getSynchronizedInstance() {
List list = Collections.synchronizedList(new ArrayList<UndoableCommand>());
return new UndoManager(list);
}
仔细考察commandList,可以发现只有在第一个或者最后一个元素时执行undo() 和redo()时是同一个元素外,其他情况下,执行undo() 和redo()功能是两个不同的但是相邻的元素。而且,第一个元素执行undo() 后整个commandList就不能undo()了,同理,最后一个元素执行redo() 后整个commandList就不能redo()了,其他情况下,commandList是既可以undo() 也可以redo()的。因此,这里采用一个变量 undoIndex 来记住执行undo() 的元素的位置。
undoIndex 的范围在-1(这时commandList不能undo())和最后一个元素下标(这时commandList不能redo())之间, redo() 的元素的位置应该是undoIndex + 1。
private static final int CAN_NOT_UNDO_INDEX = -1;
private static final int REDO_UNDO_INDEX_INTERVAL = 1;
public boolean canUndo() {
return undoIndex > CAN_NOT_UNDO_INDEX;
}
public boolean canRedo() {
return undoIndex < getLastIndex();
}
private int getLastIndex() {
return commandList.size() - 1;
}
private int getRedoIndex() {
return undoIndex + REDO_UNDO_INDEX_INTERVAL;
}
下面是完成UndoManager功能的3个主要function了:
manageCommand(UndoableCommand command) 做3件事
执行传来的UndoableCommand, 把它加到commandList的尾部,并把undoIndex指向这个尾部的位置
public void manageCommand(UndoableCommand command) {
command.execute();
commandList.add(command);
setUndoIndex(getLastIndex());
}
undo(): 当commandList不能undo()时调用会抛出异常,如果当前undoIndex的元素可以undo(),则执行这个元素的undo(),undoIndex指向commandList上一个位置,这时还要找到下一次commandList undo() 元素的位置。主要要处理2种情况:可以一起执行的元素要依次执行,使得看上去像一起执行;不能执行undo()的元素要从commandList里删除,微软的WORD就是这样的,你在WORD里先输入一个A,执行撤销,这时再输入B,再执行撤销,你会发现已经不可能撤销到输入A这一步了。
public void undo() {
if (!canUndo()) {
throw new CannotUndoException();
}
UndoableCommand current = commandList.get(undoIndex);
undoCommand(current);
for (int i = undoIndex; i >= 0; i--) {
UndoableCommand temp = commandList.get(i);
if (!temp.canUndo()) {
removeCommand(temp);
} else if (current.canAppendWith(temp)) {
undoCommand(temp);
current = temp;
} else {
setUndoIndex(i);
break;
}
}
}
redo(): 处理commandList 的 redo(), 跟undo()处理过程相反,只是相对简单一点。
下面是完整的UndoManager源码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
public class UndoManager {
private static final int CAN_NOT_UNDO_INDEX = -1;
private static final int REDO_UNDO_INDEX_INTERVAL = 1;
private List<UndoableCommand> commandList;
private int undoIndex;
private UndoManager(List<UndoableCommand> list) {
commandList = list;
}
public static UndoManager getInstance() {
return new UndoManager(new ArrayList<UndoableCommand>());
}
public static UndoManager getSynchronizedInstance() {
List list = Collections.synchronizedList(new ArrayList<UndoableCommand>());
return new UndoManager(list);
}
public void manageCommand(UndoableCommand command) {
command.execute();
commandList.add(command);
setUndoIndex(getLastIndex());
}
private void removeCommand(UndoableCommand command) {
commandList.remove(command);
undoIndex--;
}
private void undoCommand(UndoableCommand command) {
command.undo();
undoIndex--;
}
private void redoCommand(UndoableCommand command) {
command.redo();
undoIndex++;
}
public boolean canUndo() {
return undoIndex > CAN_NOT_UNDO_INDEX;
}
public boolean canRedo() {
return undoIndex < getLastIndex();
}
public void undo() {
if (!canUndo()) {
throw new CannotUndoException();
}
UndoableCommand current = commandList.get(undoIndex);
undoCommand(current);
for (int i = undoIndex; i >= 0; i--) {
UndoableCommand temp = commandList.get(i);
if (!temp.canUndo()) {
removeCommand(temp);
} else if (current.canAppendWith(temp)) {
undoCommand(temp);
current = temp;
} else {
setUndoIndex(i);
break;
}
}
}
public void redo() {
if (!canRedo()) {
throw new CannotRedoException();
}
int redoIndex = getRedoIndex();
UndoableCommand current = commandList.get(redoIndex);
redoCommand(current);
for (int i = getRedoIndex(); i < commandList.size(); i++) {
UndoableCommand temp = commandList.get(i);
if (temp.canAppendWith(current)) {
redoCommand(current);
current = temp;
} else {
return;
}
}
}
public void reset() {
commandList.clear();
setUndoIndex(CAN_NOT_UNDO_INDEX);
}
private void setUndoIndex(int index) {
undoIndex = index;
}
private int getLastIndex() {
return commandList.size() - 1;
}
private int getRedoIndex() {
return undoIndex + REDO_UNDO_INDEX_INTERVAL;
}
}
经测试,这个Undomanager类是可以工作的。
至此,这里提供的UndoableCommand抽象类和UndoManager类,已经封装了一些底层的Undo和Redo状态的切换,当实际项目中需要Undo/Redo功能时,只需要继承UndoableCommand实现自己的Undo/Redo逻辑并让UndoManager来manageCommand(UndoableCommand command),然后由UndoManage undo() 和 redo() 就可以了。
程序中Undo/Redo一般是菜单项或者是工具栏按钮,因此可以在UndoManager的基础上封装一个更为适用的UndoUtility类,它提供 getRedoAction() 和getUndoAction() function。
当使用时:
toolBar.add(undoUtility.getUndoAction());
toolBar.add(undoUtility.getRedoAction());
是不是很简单呢?
在本文的最后一部分,将给出这个类的代码,和本文提到的测试代码,这里先给出这个可无限次Undo/Redo的画,擦除直线的jar文件。
分享到:
相关推荐
实现Java中编辑器的Undo、Redo操作,功能比较强大
是一款Javascript电子表格控件,顺表的源代码开放,可以用于Web报表设计器,Web电子表格编辑器,中国特色的电子表单设计器等,该Web Excel控件可以和Java/Jsp/Asp.Net集成, 实现更强大的电子表格在线编辑功能。...
是一款Javascript电子表格控件,顺表的源代码开放,可以用于Web报表设计器,Web电子表格编辑器,中国特色的电子表单设计器等,该Web Excel控件可以和Java/Jsp/Asp.Net集成, 实现更强大的电子表格在线编辑功能。...
综上所述,本文主要围绕Java Swing框架下的Undoable功能展开,通过分析常见应用程序中Undo/Redo行为的特点,介绍了Java Swing提供的Undoable APIs以及如何使用它们来实现自己的撤销/重做功能。此外,还简要提及了...
Other features include Hex Viewer, HTML toolbar, user tools, line number, ruler, URL highlighting, auto completion, cliptext, column selection, powerful search and replace, multiple undo/redo, spell ...
Other features include Hex Viewer, HTML toolbar, user tools, line number, ruler, URL highlighting, auto completion, cliptext, column selection, powerful search and replace, multiple undo/redo, spell ...
在功能上与Sun公司所公布的JDK等文字模式程序工具相较之下来得容易操作,还允许使用者自订义操作窗口界面及无限Undo /Redo等功能。 注册信息: Jcreator的版本:2.50.05 Name:javafans company:java.com.cn ...
在功能上与Sun公司所公布的JDK等文字模式程序工具相较之下来得容易操作,还允许使用者自订义操作窗口界面及无限Undo /Redo等功能。 注册信息: Jcreator的版本:2.50.05 Name:javafans company:java.com.cn...
Estées elcódigodel Mastermind con latécnicadel doble despacho,Sobre el que agregaremos los patrones y funcionalida necesaria para und Undo / Redo con undiseñoCorrecto。 先决条件再录取通知书...
Other features include HTML toolbar, user tools, line number, ruler, URL highlighting, auto completion, cliptext, column selection, powerful search and replace, multiple undo/redo, spell checker, ...
* 它可以实现 Undo/Redo 操作,例如文本编辑器的撤销和重做操作。 * 它可以用于游戏存档、数据库rollback 等场景。 备忘录模式的缺点 虽然备忘录模式非常有用,但它也有一些缺点,例如: * 它可能会增加系统的...
在Java中实现命令模式,我们可以按照以下步骤进行: 1. **定义接口**: 首先,我们需要创建一个命令接口,这个接口定义了所有命令都需要执行的操作。例如,可以定义一个名为`Command`的接口,其中包含一个`execute...
拥有无限制的Undo/Redo、英文拼字检查、自动换行、列数标记、搜寻取代、同时编辑多文件、全屏幕浏览功能。而它还有一个好用的功能,就是它有监视剪贴簿的功能,能够同步于剪贴簿自动将文字贴进EditPlus的编辑窗口中...
拥有无限制的Undo/Redo、英文拼字检查、自动换行、列数标记、搜寻取代、同时编辑多文件、全屏幕浏览功能。而它还有一个好用的功能,就是它有监视剪贴簿的功能,能够同步于剪贴簿自动将文字贴进EditPlus的编辑窗口中...
在Java编程中,实现撤销(Undo)操作是增强应用程序用户体验的重要功能,特别是在涉及用户交互和图形编辑的应用中。Swing库提供了一套完整的框架来帮助开发者轻松实现这一特性。主要涉及的类和接口位于`javax.swing....
在实际编码过程中,撤消重做堆栈的实现可以采用多种语言,如C++、Java、Python或JavaScript。在面向对象语言中,通常会定义一个接口或抽象类来规定所有可撤销操作的共同行为,然后让具体操作类继承或实现这个接口。...
这一功能通常基于undo/redo堆栈,每次编辑操作都会在堆栈上留下一个状态,用户可以通过撤销和重做命令在这些状态之间切换。 最后,文本查找功能让开发者能够快速搜索代码中的特定字符串或模式。这个功能通常支持...
拥有无限制的 Undo/Redo、英文拼字检查、自动换行、列数标记、搜索替代、同时编辑多文件、全屏幕浏览等更加完善的功能,程序员可以把EditPlus当做(X)HTML、php、C/C++、Java、Perl等程序语言的简单的IDE来使用。...
最后,我们可以使用链栈来实现一些实际的应用,例如解析语法树、实现 undo/redo 功能等。 ```java public class Demo { public static void main(String[] args) { LinkStack<Integer> ls = new LinkStack(); ...