专题:Swing库中的命令撤消和恢复

来源:互联网 发布:17173捏脸数据站 编辑:程序博客网 时间:2024/06/10 01:53
 
专题: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();
            }
        } );
    }
}
    
 
 
原创粉丝点击