专题:Swing库中的命令撤消和恢复
一、在视窗系统中使用命令模式
在视窗系统中使用命令模式,有如下特点
(1)
使命令所代表的操作与用户GUI界面分开
(2)
可以把相关的操作与单个的命令对象结合在一起
(3)
每一个按键都应当有它的单独的类
(4)
由于命令对象要与用户GUI发生关系,所以命令类要么是用户GUI界面类的内部类,要么是外部类。如果是内部类,那么命令类当然可以得到GUI界面类的私有属性
如果命令类是独立的外部类,那么为使命令类得到GUI界面类的内部子段,可以有2种方法:第一是提供公开的方法,以便外界能够得到内部子段;第二是通过命令类的构造方法传入。第一种方法打破了封装的原则,把本该私有的变量向所有的外部类公开。第二种方法是推荐的方法。
命令的撤消(undo)和恢复(redo)对很多系统,特别是文字或图像文件系统来说,都是非常重要的。Swing在javax.swing.undo库提供了撤消和恢复的系统化处理方法。
javax.swing.undo
虽然是Swing库的一部分,但它即可以与Swing构件一起使用,也可以与AWT构件一起使用。甚至可以在根本没有视窗构件的系统里使用。
二、Swing的基本撤消功能
每一个命令或操作在Swing都叫做一个编辑(Edit)。每一个可撤消的命令或操作在Swing里都叫做一个可撤消编辑(UndoableRdit)。Swing的撤消和恢复功能是通过下面的接口和类实现的。
1
)接口UndoableEdit
:一个类如果需要撤消和恢复功能的话,就需要实现UndoableEdit接口
2
)抽象类AvstractUndoableEdit
:是对UndoableEdit接口的最小实现,也是avax.swing.undo库里其它类的基类
3
)接口StateEditable
:是所有可以编辑的类必须实现的接口
4
)StateEdit类
:代表一个可编辑类的状态
5
)UndoManager类
:负责管理所有的对一个可编辑类的编辑。
1
、UndoableEdit接口的源代码
这个接口规定了判断一个编辑是否可以撤消,以及将一个编辑撤消掉的方法。
package javax.swing.undo;
import javax.swing.event.*;
public interface UndoableEdit {
//
撤消编辑
public void undo() throws CannotUndoException;
//
如果操作是可撤消的,返回true,否则返回false
public boolean canUndo();
//
恢复一个已撤消的编辑
public void redo() throws CannotRedoException;
//
如果一个操作是可以恢复的,返回true,否则返回false
public boolean canRedo();
//
将一个编辑永久取消(而这个取消操作是不能撤消的)
public void die();
//
将编辑anEdit吸收合并,如果吸收合并成功,返回true,否则返回false
public boolean addEdit(UndoableEdit anEdit);
//
将当前编辑替换为anEdit
public boolean replaceEdit(UndoableEdit anEdit);
public boolean isSignificant();
public String getPresentationName();
public String getUndoPresentationName();
public String getRedoPresentationName();
}
2
、AbstractUndoableEdit类
这是一个缺省适配模式的应用,有了这个类作为UndoableEdit接口的最小实现,它的子类就可以省略掉不需要的方法。
如果能够重新设计这个类的话,应该把它设计成一个抽象类。这个类不应当被直接实例化,被实例化的应当是它的子类
package javax.swing.undo;
import java.io.Serializable;
import javax.swing.UIManager;
public class AbstractUndoableEdit implements UndoableEdit, Serializable {
//
getUndoPresentationName()
所返回的字符串
protected static final String UndoName = "Undo";
//
getRedoPresentationName()
所返回的字符串
protected static final String RedoName = "Redo";
//默认值是true,当此编辑被撤消后,此变量变为false,恢复后又变为true
boolean hasBeenDone;
//如果此编辑没被取消,返回true
boolean alive;
public AbstractUndoableEdit() {
super();
hasBeenDone = true;
alive = true;
}
//
将一个编辑永久取消(而这个取消操作是不能撤消的)
public void die() {
alive = false;
}
//
撤消编辑
public void undo() throws CannotUndoException {
if (!canUndo()) {
throw new CannotUndoException();
}
hasBeenDone = false;
}
//返回true,如果此编辑是活的,并且hasBeenDone是true
public boolean canUndo() {
return alive && hasBeenDone;
}
//
恢复一个已撤消的编辑
public void redo() throws CannotRedoException {
if (!canRedo()) {
throw new CannotRedoException();
}
hasBeenDone = true;
}
//返回true,如果此编辑是活的并且hasBeenDone是false
public boolean canRedo() {
return alive && !hasBeenDone;
}
public boolean addEdit(UndoableEdit anEdit) {
return false;
}
public boolean replaceEdit(UndoableEdit anEdit) {
return false;
}
public boolean isSignificant() {
return true;
}
public String getPresentationName() {
return "";
}
public String getUndoPresentationName() {
String name = getPresentationName();
if (!"".equals(name)) {
name = UIManager.getString("AbstractUndoableEdit.undoText") +
" " + name;
} else {
name = UIManager.getString("AbstractUndoableEdit.undoText");
}
return name;
}
public String getRedoPresentationName() {
String name = getPresentationName();
if (!"".equals(name)) {
name = UIManager.getString("AbstractUndoableEdit.redoText") +
" " + name;
} else {
name = UIManager.getString("AbstractUndoableEdit.redoText");
}
return name;
}
public String toString()
{
return super.toString()
+ " hasBeenDone: " + hasBeenDone
+ " alive: " + alive;
}
}
3
、StateEditable接口
这个接口要求2个方法。分别用来存储和取出一个状态。
package javax.swing.undo;
import java.util.Hashtable;
public interface StateEditable {
/ /此类的Resource ID
public static final String RCSID = "$Id: StateEditable.java,v 1.2 1997/09/08 19:39:08 marklin Exp $";
//调用此方法将传入的状态存储到state里面
public void storeState(Hashtable<Object,Object> state);
//调用此方法从state取出状态
public void restoreState(Hashtable<?,?> state);
} // End of interface StateEditable
4
、StateEdit类
这个类扩展了AbstractUndoableEdit类
package javax.swing.undo;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
public class StateEdit extends AbstractUndoableEdit {
//此类的Resource ID
protected static final String RCSID = "$Id: StateEdit.java,v 1.6 1997/10/01 20:05:51 sandipc Exp $";
//被编辑的对象
protected StateEditable object;
//编辑前的状态信息存储在此处
protected Hashtable<Object,Object> preState;
//编辑后的状态信息存储在此处
protected Hashtable<Object,Object> postState;
undo/redo的表象名
protected String undoRedoName;
//构造函数,创建一个新的StateEdit对象
public StateEdit(StateEditable anObject) {
super();
init (anObject,null);
}
public StateEdit(StateEditable anObject, String name) {
super();
init (anObject,name);
}
protected void init (StateEditable anObject, String name) {
this.object = anObject;
this.preState = new Hashtable(11);
this.object.storeState(this.preState);
this.postState = null;
this.undoRedoName = name;
}
//得到StateEditable对象编辑后的状态并结束编辑
public void end() {
this.postState = new Hashtable(11);
this.object.storeState(this.postState);
this.removeRedundantState();
}
//恢复编辑前的状态
public void undo() {
super.undo();
this.object.restoreState(preState);
}
//将被编辑的对象恢复为编辑后的状态
public void redo() {
super.redo();
this.object.restoreState(postState);
}
//返回此编辑的表象名
public String getPresentationName() {
return this.undoRedoName;
}
//一些内部支持的方法
protected void removeRedundantState() {
Vector uselessKeys = new Vector();
Enumeration myKeys = preState.keys();
// Locate redundant state
while (myKeys.hasMoreElements()) {
Object myKey = myKeys.nextElement();
if (postState.containsKey(myKey) &&
postState.get(myKey).equals(preState.get(myKey))) {
uselessKeys.addElement(myKey);
}
}
// Remove redundant state
for (int i = uselessKeys.size()-1; i >= 0; i--) {
Object myKey = uselessKeys.elementAt(i);
preState.remove(myKey);
postState.remove(myKey);
}
}
} // End of class StateEdit
5
、UndoManager类
实现了UndoableEditListener接口,并扩展了CompoundEdit类
package javax.swing.undo;
import javax.swing.event.*;
import javax.swing.UIManager;
import java.util.*;
public class UndoManager extends CompoundEdit implements UndoableEditListener {
int indexOfNextAdd;
int limit;
public UndoManager() {
super();
indexOfNextAdd = 0;
limit = 100;
edits.ensureCapacity(limit);
}
//
返回可存储的总的编辑数,默认是100
public synchronized int getLimit() {
return limit;
}
//
将所存储的编辑全清除
public synchronized void discardAllEdits() {
Enumeration cursor = edits.elements();
while (cursor.hasMoreElements()) {
UndoableEdit e = (UndoableEdit)cursor.nextElement();
e.die();
}
edits = new Vector(limit);
indexOfNextAdd = 0;
}
//
设置所能存储的编辑的数目,默认适100
public synchronized void setLimit(int l) {
if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
limit = l;
trimForLimit();
}
//undo
或redo,那个合适用那个
public synchronized void undoOrRedo() throws CannotRedoException,
CannotUndoException {
if (indexOfNextAdd == edits.size()) {
undo();
} else {
redo();
}
}
//
是否可以undo或redo
public synchronized boolean canUndoOrRedo() {
if (indexOfNextAdd == edits.size()) {
return canUndo();
} else {
return canRedo();
}
}
}
三、一个可以撤消和恢复的文字框的例子
这个系统由一个视窗类Itsukyu、一个可撤消的文字框UndoableTextArea、抽象命令角色Command,以及具体命令角色UndoCommand和RedoCommand组成。
1
、视窗类的源代码
import java.util.Hashtable;
import java.awt.*;
import java.awt.event.*;
public class Itsukyu extends Frame
{
/**
* @link aggregation
*/
private static UndoableTextArea text ;
/**
* @link aggregation
*/
static ItsukyuQuotation panel;
public Itsukyu(String title)
{
super(title);
}
public static void main(String[] s)
{
// create an undoable text area
text = new UndoableTextArea("Your text here.");
// Create app panel
panel = new ItsukyuQuotation(text);
// Create a frame for app
Itsukyu frame = new Itsukyu("Itskyu talks on Zen");
// Add a window listener for window close events
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e) { System.exit(0);}
} );
// Add app panel to content pane
frame.add(panel);
// Set initial frame size and make visible
frame.setSize (300, 300);
frame.setVisible(true);
}
}
2
、视窗类上的Panel类的源代码,它同时实现了AWT的事件监听接口ActionListener
import java.util.Hashtable;
import java.awt.*;
import java.awt.event.*;
import javax.swing.undo.*;
class ItsukyuQuotation extends Panel implements ActionListener
{
/**
* @link aggregation
*/
private final Command undo ;
/**
* @link aggregation
*/
private final Command redo ;
// Constructor
public ItsukyuQuotation(UndoableTextArea text)
{
setLayout(new BorderLayout());
// create a toolbar for buttons
Panel toolbar = new Panel();
// create undo and redo buttons
// add listeners for buttons
undo = new UndoCommand(text);
redo = new RedoCommand(text);
undo.addActionListener (this);
redo.addActionListener (this);
// add buttons to toolbar
toolbar.add(undo);
toolbar.add(redo);
// add toolbar and text area to panel
add(toolbar, "North");
add(text, "Center");
}
//-----------------------------------------
public void actionPerformed(ActionEvent e)
{
Command cmd = (Command)e.getSource();
cmd.execute();
}
}
3
、抽象命令类Command
abstract public class Command extends Button
{
public Command(String caption)
{
super(caption);
}
public void execute(){}
}
4
、撤消命令类:它适抽象命令类的子类,代表的是对编辑的撤消
public class UndoCommand extends Command {
UndoableTextArea text;
public UndoCommand(UndoableTextArea text)
{
super("Undo");
this.text = text;
}
public void execute()
{
text.undo();
}
}
5
、恢复命令类的源代码:它代表对编辑的恢复。一个编辑被撤消后,可以通过这个恢复命令加以恢复
UndoableTextArea
继承自java.awt.TextArea,实现了StateEditable接口。因此,UndoableEditTextArea类需要实现StateEditable接口所要求的方法。storeState()及restoreState()方法分别负责存储一个新的状态和恢复一个状态。
public class RedoCommand extends Command {
UndoableTextArea text;
public RedoCommand(UndoableTextArea text)
{
super("Redo");
this.text = text;
}
public void execute()
{
text.redo();
}
}
6
、可撤消的文字框类:它是系统的关键部分
class UndoableTextArea extends TextArea implements StateEditable
{
private final static String KEY_STATE = "UndoableTextAreaKey"; // hash key
private boolean textChanged = false;
private UndoManager undoManager;
private StateEdit currentEdit;
public UndoableTextArea()
{
super();
initUndoable();
}
public UndoableTextArea(String string)
{
super(string);
initUndoable();
}
public UndoableTextArea(int rows, int columns)
{
super(rows, columns);
initUndoable();
}
public UndoableTextArea(String string, int rows, int columns)
{
super(string, rows, columns);
initUndoable();
}
public UndoableTextArea(String string, int rows, int columns, int scrollbars)
{
super(string, rows, columns, scrollbars);
initUndoable();
}
// method to undo last edit
public boolean undo()
{
try
{
undoManager.undo();
return true;
}
catch (CannotUndoException e)
{
System.out.println("Can't undo");
return false;
}
}
// method to redo last edit
public boolean redo()
{
try
{
undoManager.redo();
return true;
}
catch (CannotRedoException e)
{
System.out.println("Can't redo");
return false;
}
}
// Implementation of StateEditable interface
//
// save and restore data to/from hashtable
public void storeState(Hashtable state)
{
state.put(KEY_STATE, getText());
}
public void restoreState(Hashtable state)
{
Object data = state.get(KEY_STATE);
if (data != null)
{
setText((String)data);
}
}
// Snapshots current edit state
private void takeSnapshot()
{
// snapshot only if text changed
if (textChanged)
{
// end current edit and notify undo manager
currentEdit.end();
undoManager.addEdit(currentEdit);
// reset text changed semaphore and create a new current edit
textChanged = false;
currentEdit = new StateEdit(this);
}
}
// Helper method to initialize object to be undoable
private void initUndoable ()
{
// create an undo manager to manage undo operations
undoManager = new UndoManager();
// create a StateEdit object to represent the current edit
currentEdit = new StateEdit(this);
// add listeners for various edit-related events
// use these events to determine when to snapshot current edit
// key listener looks for action keys (non-character keys)
addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent event)
{
if (event.isActionKey())
{
// snapshot on any action keys
takeSnapshot();
}
}
} );
// focus listener looks for loss of focus
addFocusListener(new FocusAdapter()
{
public void focusLost(FocusEvent event)
{
// snapshot when control loses focus
takeSnapshot();
}
} );
// text listener looks for text changes
addTextListener(new TextListener()
{
public void textValueChanged(TextEvent event)
{
textChanged = true;
// snapshot on every change to text
// might be too granular to be practical
//
// this will shapshot every keystroke when typing
takeSnapshot();
}
} );
}
}