21.5 使用外部对象管理Undo状态
在前面的示例中,我们自定义的UndoableEdit实现要负责维护可撤销对象的之前与之后状态。Swing Undo框架同时支持使用可撤销编辑实现之外的对象来管理状态的能力。当使用一个外部对象用于状态管理时,我们并不需要实现UndoableEdit接口。相反,我们可以使用StateEdit类作为UndoableEdit实现。然后StateEdit类依赖一个类实现StateEditable接口来管理可撤销对象(在Hashtable中)之前与之后的状态存储。
21.5.1 StateEditable接口
StateEditable接口由两个方法与一个无意义的字符串常量组成。
public interface StateEditable { public final static String RCSID; public void restoreState(Hashtable state); public void storeState(Hashtable state); }
支持操作撤销的对象使用storeState(Hashtable)方法存储其状态。这是关于可以修改的对象的状态的全部信息。然后使用restoreState(Hashtable)方法重新载入对象的状态。
为了进行演示,我们来看一下如何重新实现前面的UndoableDrawingPanel示例。在更新的版本中使用这个接口,可撤销的绘制面板需要实现此接口,并且存储与获取前面图21-5中所示的多边形。这是因为多边形是我们关心撤销的唯一状态信息。源码显示在列表21-7中。
package swingstudy.ch21; import java.awt.Polygon; import java.util.Hashtable; import javax.swing.JPanel; import javax.swing.undo.StateEditable; public class UndoableDrawingPanel2 extends JPanel implements StateEditable { private static String POLYGON_KEY = "Polygon"; @Override public void storeState(Hashtable state) { // TODO Auto-generated method stub state.put(POLYGON_KEY, getPolygon()); } @Override public void restoreState(Hashtable state) { // TODO Auto-generated method stub Polygon polygon = (Polygon)state.get(POLYGON_KEY); if(polygon != null) { setPolgyon(polygon); } } }
restoreState()方法返回的Hashtable只包含可以修改的键/值对。也有可能Hashtable的get()方法会为我们使用put()方法显式放入散列表中的内容返回null。所以,如列表21-7所示,我们需要在由散列表获取状态信息之后添加一个if-null检测语句。
21.5.2 StateEdit类
在我们实现了StateEditable接口之后,我们可以使用StateEdit类作为UndoableEdit实现。其中前面的UndoableDrawingPanel示例创建了一个自定义的UndoableDrawingEdit,新类创建了一个StateEdit实例。
StateEdit构造函数接受一个我们将要修改的StateEditable对象与一个可选的表示名字。在创建StateEdit对象之后,修改StateEditable对象然后通知StateEdit来end() StateEditable对象的修改。当StateEdit对象被通知修改已经结束时,他会对比状态可编辑对象的之前与之后状态并且由散列表中移除没有变化的键/值对。然后我们可以通过由UndoableEditSupport类维护的一个列表将UndoabelEdit发送到UndoableEditListener对象的列表。
StateEdit stateEdit = new StateEdit(UndoableDrawingPanel2.this); // Change state of UndoableDrawingPanel2 polygon.addPoint(mouseEvent.getX(), mouseEvent.getY()); // Done changing state stateEdit.end(); undoableEditSupport.postEdit(stateEdit);
在编辑被发送之后,UndoManager管理UndoableEdit的StateEdit实例,类似于其他的可撤销编辑对象。然后UndoManager请求StateEdit对象来通知其StateEditable对象来载入其前一个状态。对于其他的UndoableEdit对象也是如此。所以,其他的代码不需要修改。
21.5.3 一个完整的StateEditable/StateEdit示例
改写的UndoableDrawingPanel示例显示在列表21-8中,与列表21-5的不同之处以粗体显示。这个版本使用我们刚刚所描述的StateEditable/StateEdit组合。前面的测试程序被包含在main()方法来保持示例的完整。除了修改了绘制面板的类名,测试并没有进行修改并且将会得到与我们在图21-5中所见到的相同的结果,假定多边形具有相同的点集合。
package swingstudy.ch21; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Polygon; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Hashtable; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.event.UndoableEditListener; import javax.swing.undo.StateEdit; import javax.swing.undo.StateEditable; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEditSupport; public class UndoableDrawingPanel2 extends JPanel implements StateEditable { private static String POLYGON_KEY = "Polygon"; UndoableEditSupport undoableEditSupport = new UndoableEditSupport(this); Polygon polygon = new Polygon(); public UndoableDrawingPanel2() { MouseListener mouseListener = new MouseAdapter() { public void mouseReleased(MouseEvent event) { StateEdit stateEdit = new StateEdit(UndoableDrawingPanel2.this); polygon.addPoint(event.getX(), event.getY()); stateEdit.end(); undoableEditSupport.postEdit(stateEdit); repaint(); } }; addMouseListener(mouseListener); } public void addUndoableEditListener(UndoableEditListener undoableEditListener) { undoableEditSupport.addUndoableEditListener(undoableEditListener); } public void removeUndoableEditListener(UndoableEditListener undoableEditListener) { undoableEditSupport.removeUndoableEditListener(undoableEditListener); } @Override public void storeState(Hashtable state) { // TODO Auto-generated method stub state.put(POLYGON_KEY, getPolygon()); } @Override public void restoreState(Hashtable state) { // TODO Auto-generated method stub Polygon polygon = (Polygon)state.get(POLYGON_KEY); if(polygon != null) { setPolygon(polygon); } } public void setPolygon(Polygon newValue) { polygon = newValue; repaint(); } public Polygon getPolygon() { Polygon returnValue; if(polygon.npoints == 0) { returnValue = new Polygon(); } else { returnValue = new Polygon(polygon.xpoints, polygon.ypoints, polygon.npoints); } return returnValue; } protected void paintComponent(Graphics g) { super.paintComponent(g); g.drawPolygon(polygon); } public static void main(String[] args) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Drawing Sample2"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); UndoableDrawingPanel2 drawingPanel = new UndoableDrawingPanel2(); UndoManager manager = new UndoManager(); drawingPanel.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(drawingPanel, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
21.6 小结
在本章我们简单了解并且探讨了javax.swing.undo包中的Undo框架以及javax.swing.evnet的支持。我们了解框架在Swing文本组件中的支持。另外,我们了解了如何在我们自己的类中构建支持。通过使用Undo框架中的接口与类,我们可以使得任意可编辑的类同时支持撤销与重做功能。
下一章将会介绍Swing组件集合的可访问性支持。到时我们将会了解组件的辅助技术与声音支持。