随笔 - 59, 文章 - 4, 评论 - 184, 引用 - 7
数据加载中……

为Swing应用增加undo/redo功能(三)(翻译)

理解Swing GUI组件如何最大限度的使用Command模式获得undo/redo功能

By Tomer Meshorer

翻译:flyingbug

实例:

我们将构建一个简单的程序来演示Swingundo/redo机制。下面列出我们的applet小程序,允许用户在一个JList组件中添加和删除元素。当用户添加或者删除一个原色,这个程序储存相关操作的效果--在一个AddEdit 或者RemoveEdit对象中--以便以后进行undo操作。

废话少说,让我们开始code

下面这些类组成这个简单的程序:

下面我们从AddEdit开始


001 class AddEdit extends AbstractUndoableEdit {
002
003     private Object element_;
004
005     private int index_;
006
007     private DefaultListModel model_;
008
009     public AddEdit(DefaultListModel model, Object element, int index) {
010          model_=model;
011
012          element_ = element;
013
014          index_=index;
015     }
016
017     public void undo() throws CannotUndoException {
018
019         model_.removeElementAt(index_);
020
021     }
022
023     public void redo() throws CannotRedoException {
024          model_.insertElementAt(element_,index_);
025     }
026
027     public boolean canUndo() { return true; }
028
029     public boolean canRedo() { return true; }
030
031     public String getPresentationName() { return "Add"; }
032
034 }

在构造函数(009-015)中我们保存了一个添加动作进行取消/重做需要的所有信息,其中包括:

  • 添加到list中的元素
  • 元素在list中的索引
  • 动作的接受者Listmodel本身:list默认的modelDefaultListModel,它是一个类似与Vector的接口,可用来访问JList组件

Undo()方法(017021)从list中删除这个元素,而redo()023025)方法把它重新插入。getPresentationName()方法返回被undoredo的名字。需要注意的是:如果你的类继承自AbstractUndoableEditSwing将通过在getPresentationName()的返回值前加上”undo””redo”的方法来处理getUndoPresentationName()getRedoPresentationName()方法的返回值。

现在让我们来测试添加操作。下面的片段将Action对象添加到add按钮上。ActionSwing的一个新的接口,用来实现UI的控制器。也就是说,一个action能够被直接添加到一个工具栏(作为一个新按钮)或者一个菜单(作为一个新的菜单项)。当这个action的一个属性发生改变(比如:变为可操作或者不可操作),UI的组件被通知并改变自身状态与Action保持一致。比如:当CutAction变成不可操作,Cut工具栏按钮和Cut菜单选项都同时变为不可操作。无论如何,Action接口继承ActionListener接口来处理ActionEvent事件,并且Action接口是自描述的。

当添加如JToolBar或者JMenu这样的组件到一个支持Action的容器当中时,Action项目被用来决定以何种方式来生成这个组件,并且被自动注册到UI事件中。容器将新的组件注册为action的一个PropertyListener

AbstractAction是一个实现Action接口的具体类,它提供了所有方法的缺省实现。在这个例子中,我们只使用ActionListener


001 private class AddAction extends AbstractAction {
002
003     public void actionPerformed(ActionEvent evt) {
005         // always add to the end of the JList
006         int NumOfElements = elementModel_.getSize();
007         // however, give the element its ID number
008         Object element = new String("Foo " + _lastElementID);
009        
010         // record the effect
011         UndoableEdit edit = new AddEdit(elementModel_,
012                                    element, NumOfElements);
013         // perform the operation
014         elementModel_.addElement(element);
015        
016         // notify the listeners
017         undoSupport_.postEdit(edit);
017          
018         // increment the ID
019          _lastElementID ++ ;
020
021     }
022
023 }

AddAction类:

  • 添加一个新的元素 (008)
  • 添加一个新的AddEdit对象并且传给它三个参数:ListModelaction的接受者),新元素的索引,新元素本身。 (011)
  • 执行实际添加操作 (014)
  • 通过调用undoSupport对象的postEdit方法通知undo listeners (017)

 

注意:AddAction类是undo小程序的一个私有内部类。这个方式保证它可以直接访问此程序的私有变量成员(比如:undoSupport_)。一般来说,我倾向于将actions定义为使用它们的类的内部类,这样做是既防止了接口体系膨胀也避免了打破封装(通过暴露对象的内部结果--举个例子:一个外部的添加动作的action可能会想要能够操作ListModel

连接到UI

当然,为了一个简单的undo系统,你很可能愿意注册一个UndoManager类作为UndoableEvents事件的唯一listener,提供一个只提供一般undo标签并且调用manager类的undo()方法的UndoAction

无论如何,为了使UI更健壮,你一定会愿意为用户提供取消最后一次操作的功能。

下面的代码片断展示了如何制作一个UndoAdaptor类,用来更新支持undo的组件的状态使其恢复到undo历史列表中的最新状态(在这个例子中是undoredo按钮)。


001 private class UndoAdaptor implements UndoableEditListener {
002
003     public void undoableEditHappened (UndoableEditEvent evt) {
004
005         UndoableEdit edit = evt.getEdit();
006
007         undoManager_.addEdit(edit);
008
009         refreshUndoRedo();
010     }
011 }

这个UndoAdaptor在程序装入时被注册到UndoableEditSupport。每次一个可undo的事件发生,这个适配器会做以下动作:

  • 从事件中取出操作 (005)
  • 添加到UndoManager(007)
  • 刷新undo相关的GUI状态(009)

一个代替的方法是通过实现一个UndoManager的子类并且覆写addEdit()方法以便自动刷新用户界面。(即每次add后在addEdit()方法中刷新界面),不用在客户端代码中刷新

下面是refreshUndoRedo()方法:


001 public void refreshUndoRedo() {
002
003     //refresh undo
004
005     undoBtn_.setText(undoManager_.getUndoPresentationName());
006     undoBtn_.setEnabled(undoManager_.canUndo());
007
008     // refresh redo
009
010     redoBtn_.setText(undoManager_.getRedoPresentationName());
011     redoBtn_.setEnabled(undoManager_.canRedo());
012
013 }

这个方法刷新执行undoredo后的用户界面。并从undoManager中取得目前编辑状态信息。

The Undo 操作

当用户点击Undo按钮,undo action被调用,如下所示:


001 private class UndoAction extends AbstractAction {
002
003     public void actionPerformed(ActionEvent evt ) {
004
005         undoManager_.undo();
006
007         refreshUndoRedo();
008     }
009 }

所有的管理undo历史列表的细节都由UndoManager类来管理。Undo操作只是简单的调用manager类的undo()方法并刷新程序的界面。Redo的代码与此类似,这里就不列出了。

将各部分串起来

在程序的构造函数中,我们建立这个系统:


001 public UndoPanel () {
002    // construct the actions
003    ActionListener undoAction = new undoAction();
004    ActionListener redoAction = new redoAction();
005  
006    // register the listener
007    undoBtn_ = new JButton("undo");
008    undoBtn_.addActionListener(undoAction);
009    redoBtn_ = new JButton("redo");
010    redoBtn_.addActionListener(redoAction);
011    
012
013    // initialize the undo.redo system
014    undoManager_= new UndoManager();
015    undoSupport_ = new UndoableEditSupport();
016    undoSupport_.addUndoableEditListener(new UndoAdapter());
017
018 }

总结:

Command模式在保存用户的行为在单独的类中非常有用。它允许我们将undo/redo工具的实现局部化,这样单独的类可执行和取消自身状态的改变。这个方法极大的简化了代码的维护;当我们改变一个操作的行为,undo/redo操作的代码几乎完全独立与发布给用户的接口,使客户代码不依赖于undo/redo机制的实现细节。

在应用程序中支持undo/redo功能,使客户在学习如何操作程序时更有信心。这篇文章展示了使用Swing来实现这样的功能是如何简单。对于开发者来说,它可能只是一个小小的进步,但对于如何创建完善友好的应用来说,这确实是一个巨大的进步。

posted on 2005-05-25 11:34 fisher 阅读(626) 评论(1)  编辑  收藏 所属分类: Open Source

评论

# re: 为Swing应用增加undo/redo功能(三)(翻译)   回复  更多评论   

good!顶
2008-11-17 16:25 | 田杰

只有注册用户登录后才能发表评论。


网站导航: