Undo框架(一)

在第20章中,我们通过探讨可插拨的观感体系结构支持了解了如何自定义基于Swing的程序。在本章中,我们将会探讨由作为Swing包一部分的JFC所提供的Undo框架。

Sun的Swing包包含一个支持我们程序中撤销操作支持的实用程序。他允许我们支持修改我们数据状态的撤销与重做操作。尽管这个框架是Swing包层次结构的一部分,他却可以用在任意的程序中,而不仅是基于组件的程序。


21.1 使用Undo框架

Undo框架位于javax.swing.undo包中,包含五个类,两个接口以及两个异常。要支持Undo框架,在javax.swing.event包中还有一个相关的癌与事件。在其顶部是UndoableEdit接口。这个接口构成了用于封装使用Command设计模式可以撤销或是重做的操作。

可撤销命令的根实现类是AbstractUndoableEdit类。不要叫这个类名迷惑我们,他并不是抽象的。根命令的子命令是CompundEdit与StateEdit命令类。

CompoundEdit类可以使得我们组合多个可撤销的操作,其中一些可撤销的操作则是存储状态变化的StateEdit对象。Swing文本组件会在他们的内容发生变化时创建DefaultDocumentEvent命令。这个命令是CompundEdit的子类,同时也是AbstractDocument的内联类。另一个封装的命令是UndoManager,他是CompundEdit的子类。

UndoManager通过承担UndoableEditListener的角色并且响应UndoableEditEvent的创建来管理可编辑对象上的编辑操作。当UndoableEdit不可撤销时,则会抛出CannotUndoException。另外,当一个UndoableEdit不可以重做时,则会抛出CannotRedoException。

如查我们希望创建支持撤销与重做操作的对象,对象需要实现StateEditable接口,并且他们可以使用UndoableEditSupport类来帮助管理UndoableEdit对象的列表。

在深入单个Undo框架的片段细节之前,我们先来了解一下如何在Swing文本组件中使用Undo框架。如果这些就是我们所需要的,我们就不需要理解其他的工作了。


21.2 配合Swing文本组件使用Undo框架

Swing文本组件已经支持了必须的撤销与重做功能。我们并不需要使用UndoManager来进行管理并且通知管理器何时撤销/重做。

作为一个示例,考虑一个包含一个JTextArea与两个用于撤销与重做的工具栏按钮,如图21-1所示。


要使得图21-1中的JTextArea支持撤销操作,我们必须将一个UndoableEditListener关联到组件的Document。我们需要做的就是将UndoManager作为我们的监听器。首先,我们创建管理器,然后关联。

 
    
UndoManager manager = new UndoManager();
textArea.getDocument().addUndoableEditListener(manager);

一旦管理器被关联到JTextArea的文档,他就会监听文本域内容的所有变化。因为每一个Swing文本组件都有一个Document数据模型,我们可以直接将UndoManager与这些组件的文档相关联。

在将管理器关联到文本组件之后,我们必须提供一些方法来通知管理器撤销或是重做某一操作。通常这是通过菜单选择或是工具栏按钮选择来实现的。在图21-1中,这是借助于JToolBar上的按钮来实现的,而另一个按钮用于实现重做。对于Undo按钮,我们希望管理撤销一个操作。所以,按钮的ActionListener应该调用UndoManager的public void undo()方法。Redo按钮的ActionListener应该调用管理器的public void redo()方法。undo()方法与redo()方法都会抛出必须处理的异常。

因为Undo与Redo按钮所必须的功能对于所用的管理器都是相同的,我们将会在列表21-1中包含一个助手类UndoManagerHelper来为我们创建Action对象。这些对象可以为JMenuBar,JToolBar,或是其他可以使用ActionListener响应用于处理撤销与重做操作的组件所用。我们必须向助手类请求Action,然后将Action关联到相应的组件。例如,下面的五行代码将会获取前面所创建的UndoManager,并且为JToolBar添加必要的按钮:

 
    
JToolBar toolbar = new JToolBar();
JButton undoButton = new JButton(UndoManagerHelper.getUndoAction(manager));
toolbar.add(undoButton);
JButton redoButton = new JButton(UndoManagerHelper.getRedoAction(manager));
toolbar.add(redoButton);

使用Swing文本组件的撤销功能就是如此简单。UndoManagerHelper类的定义显示在列表21-1中。如果我们不喜欢默认的按钮标签(显示在图21-1中),还有一些支持自定义的其他方法。另外,如果在撤销或是重做过程中抛出异常,则会弹出一个警告信息。警告信息与弹出容器标题也是可以自定义的。

 
    
package swingstudy.ch21;
 
import java.awt.Component;
import java.awt.event.ActionEvent;
 
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
 
public class UndoManagerHelper {
 
	public static Action getUndoAction(UndoManager manager, String label) {
		return new UndoAction(manager, label);
	}
 
	public static Action getUndoAction(UndoManager manager) {
		return new UndoAction(manager, (String)UIManager.get("AbstractUndoableEdit.undoText"));
	}
 
	public static Action getRedoAction(UndoManager manager, String label) {
		return new RedoAction(manager, label);
	}
 
	public static Action getRedoAction(UndoManager manager) {
		return new RedoAction(manager, (String)UIManager.get("AbstractUndoableEdit.redoText"));
	}
 
	private abstract static class UndoRedoAction extends AbstractAction {
		UndoManager undoManager = new UndoManager();
		String errorMessage = "Cannot undo";
		String errorTitle = "Undo Problem";
 
		protected UndoRedoAction(UndoManager manager, String name) {
			super(name);
			undoManager = manager;
		}
 
		public void setErrorMessage(String newValue) {
			errorMessage = newValue;
		}
 
		public void setErrorTitle(String newValue) {
			errorTitle = newValue;
		}
 
		protected void showMessage(Object source) {
			if (source instanceof Component) {
				JOptionPane.showMessageDialog((Component)source, errorMessage, errorTitle, JOptionPane.WARNING_MESSAGE);
			}
			else {
				System.err.println(errorMessage);
			}
		}
	}
 
	public static class UndoAction extends UndoRedoAction {
		public UndoAction(UndoManager manager, String name) {
			super(manager, name);
			setErrorMessage("Cannot undo");
			setErrorTitle("Undo Problem");
		}
 
		public void actionPerformed(ActionEvent event) {
			try {
				undoManager.undo();
			}
			catch(CannotUndoException cannotUndoException) {
				showMessage(event.getSource());
			}
		}
	}
 
	public static class RedoAction extends UndoRedoAction {
		public RedoAction(UndoManager manager, String name) {
			super(manager, name);
			setErrorMessage("Cannot redo");
			setErrorTitle("Redo Problem");
		}
 
		public void actionPerformed(ActionEvent event) {
			try {
				undoManager.redo();
			}
			catch(CannotRedoException cannotRedoException) {
				showMessage(event.getSource());
			}
		}
	}
}

图21-1中所示示例的其余源代码显示在列表21-2中。借助于新创建的UndoManagerHelper类,在Swing文本组件中使用Undo框架的最大困难已经得到了极大的简化。

 
    
package swingstudy.ch21;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.undo.UndoManager;
 
public class UndoSample {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("Undo Sample");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				JTextArea textArea = new JTextArea();
				JScrollPane scrollPane = new JScrollPane(textArea);
 
				UndoManager manager = new UndoManager();
				textArea.getDocument().addUndoableEditListener(manager);
 
				JToolBar toolbar = new JToolBar();
				JButton undoButton = new JButton(UndoManagerHelper.getUndoAction(manager));
				toolbar.add(undoButton);
				JButton redoButton = new JButton(UndoManagerHelper.getRedoAction(manager));
				toolbar.add(redoButton);
 
				frame.add(toolbar, BorderLayout.NORTH);
				frame.add(scrollPane, BorderLayout.CENTER);
				frame.setSize(300, 150);
				frame.setVisible(true);
			}
		};
		EventQueue.invokeLater(runner);
	}
 
}

如果我们只是希望在Swing文本组件中使用Undo框架,我们可以跳过本章的其他部分。相反,如果我们希望在其他的组件中使用该框架,或者甚至是在非组件设置中使用,我们需要阅读其余部分,我们将会在余下的内容中深入探讨框架的内部实现。


21.3 Command设计模式

javax.swing.undo包的撤销功能实现了Command设计模式,其包括如下组成部分:

  • Command:UndoableEdit接口定义了用于执行撤销与重做操作的接口。
  • Concrete Command:AbstractUndoableEdit类的实例,或者是更为特定的子类,实现了必须的Command接口。他们将命令绑定到接收者(Document)来修改其内容。
  • Client:在Swing文本组件的例子中,Document执行了实际的AbstractUndoableEdit子类的创建,默认为AbstractDocument.DefaultDocumentEvent。
  • Invoker:UndoManager扮演UndoableEdit命令调用者的角色。通常情况下,其他的参与者会通知调用者何时执行调用。然而,调用者会通知特定的UndoableEdit实例何时撤销或是重做命令。
  • Receiver:Document是实际的AbstractUndoabelEdit子类中命令的接收者。他知道如何执行请求。

如果我们要Swing文本组件之外使用Undo框架,Document元素将会为特定于我们客户程序的接收者所替代。我们需要创建我们自己的UndoableEdit接口实现来承担模式中Concrete Command的任务。无需直接实现接口,我们只需要继承AbstractUndoableEdit类来封装关于我们命令的特定信息。

Command设计模式十分强大。无论我们在模式中使用哪种命令,我们都可以设置功能,例如用于执行自动测试的宏,因为调用会顺序执行这些命令。

你可能感兴趣的:(Java)