public interface UndoableEdit { // Properties public String getPresentationName(); public String getRedoPresentationName(); public boolean isSignificant(); public String getUndoPresentationName(); // Other Methods public boolean addEdit(UndoableEdit anEdit); public boolean canRedo(); public boolean canUndo(); public void die(); public void redo() throws CannotRedoException; public boolean replaceEdit(UndoableEdit anEdit); public void undo() throws CannotUndoException;}這個介面定義了應該支援撤銷與重做功能的對象所要完成的操作。除了描述所支援的操作,介面隱式定義了可撤銷的操作可以位於的三個狀態,21-2所示。
狀態之間的轉換關係如下:
可撤銷狀態:當UndoableEdit命令第一次被建立時,操作位於可撤銷狀態。die()方法的目的就在於在垃圾收集器決定清理並且將命令置於完成狀態之前釋放UndoableEdit的資源。調用undo()方法或者會拋出一個CannotUndoException或者使得命令撤銷完成並且狀態變化可重做。調用redo()方法或者會拋出CannotRedoException或是使得命令再次完成並且狀態保持為可撤銷狀態。
可重做狀態:當操作位於可重做狀態時,命令已經撤銷完成。調用die()方法會釋放所有的資源並且將命令置於完成狀態。調用undo()方法或者拋出CannotUndoException或是使得命令再次撤銷完成並且狀態保持為可重做狀態。調用redo()方法或者拋出CannotRedoException或是使得命令重做完成,並且將狀態返回可撤銷狀態。
完成狀態:當操作位於完成狀態時,調用undo(),redo()或是die()方法會使得操作保持為完成狀態。
某些狀態變化並不會發生;然而,所有的狀態變化都被支援。特殊情況留給我們正在使用Command。例如,如果功能有意義,Microsoft Word會允許我們持續重做上一個命令,例如格式化段落或是輸入段落。
21.4.2 AbstractUndoableEdit類AbstractUndoableEdit類為UndoableEdit介面的所有方法提供了預設實現。儘管由其名字我們也許會猜測該類是一個抽象類別,事實上並不是。然而,開發人員會使用該類的子類,而不是直接使用該類的執行個體。
預設情況下,AbstractUndoableEdit命令是significant(其isSignificant()會返回true)。我們在significance屬性設定上所設定的意義依賴於我們命令的使用。另外,類限制了可撤銷狀態變化的重複。除非通過子類重寫,如果我們嘗試在可撤銷狀態中重做或在可重做狀態中撤銷時會拋出異常。該類並不支援添加或是替換UndoableEdit操作。
undoPresentationName與redoPresentationName屬性的預設表現名字為Undo與Redo。這可以通過尋找表21-1中的UIResource相關屬性查到。對於presentationName屬性並沒有預設值。子類應該至少提供表現名來提供有意義的名字,而不是預設設定。
21.4.3 CompoundEdit類CompoundEdit類使得我們可以將多個可撤銷操作組合為一個可撤銷操作。例如,也許我們希望將輸入一個完整單詞的所有的按鍵組合為一個單個的CompoundEdit命令。這可以使得我們連續撤銷在多個位置所輸入的完整單詞。如果沒有組合單個的按鍵,重做上一個命令僅是重做上一次按鍵。
CompoundEdit類使用一個唯讀inProgress屬性來報告一個命令是否仍然被組合。初始時,該屬性為true。當位於過程中時,可以使用addEdit(UndoableEdit)將其他的命令添加到組合命令中。要標識命令集合的結束,我們可以調用end()方法。只在我們組合所有的命令之後才可以撤銷或是重做。21-3所示。
使用CompundEdit,如果我們undo()編輯,所有添加的命令都會被撤銷。redo()也是如此:集合中的所有命令都會被重做。
21.4.4 UndoManager類UndoManager類是一個跟蹤編輯命令曆史的特殊CompoundEdit子類,主要用於整個程式。管理器可以跟蹤的可撤銷命令數目是由一個可配置的limit屬性來定義的,其初始值為100.
當isInProgress()方法對於特定的CompoundEdit報告true時,UndoManager在某種程度上類似於退步的CompoundEdit,其中單個命令可以撤銷完成或是重做完成。一旦調用end()方法,UndoManager的作用類似於CompoundEdit,但是沒有撤銷或是重做單個編輯命令的功能。另外,UndoManager還有另外一個狀態-Undoable或是Redoable-用於管理器已經撤銷完成至少一個命令,仍然可以撤銷更多命令,但是同時可以重做已撤銷完成的命令。
除了能夠直接使用addEdit()方法添加可編輯的操作外,管理同時承擔UndoableEditListener的任務。當UndoableEditEvent發生時,監聽器使用addEdit()方法將事件的UndoableEdit命令添加到管理器。另外,我們可以使用public void discardAllEdits()方法來清除編輯隊列。在管理器接收end()方法以外,序列回到圖21-3所示的狀態,只剩表中底部所顯示的三個狀態(Undoable,Redoable,Done)。整個序列21-4所示。
記住,特定的undo()與redo()調用可以拋出異常。另外,當我們請求UndoManager撤銷或是重做一個編輯命令時,請求會撤銷(或是重做)至到上一個有意義命令的所有命令。
對於某些使用者來說UndoManager到CompoundEdit的轉換也許會感到迷惑。這個轉換可以使得我們有另一個UndoManager用於特定的子操作,也就是一旦完成就會變為一個CompundEdit,並傳遞給主UndoManager。
]21.4.5 UndoableEditListener介面與UndoableEditEvent類UndoManager實現了UndoableEditListener介面,從而當可撤銷的操作發生時,他可以得到通知。該監聽器的定義如下:
public interface UndoableEditListener extends EventListener { public void undoableEditHappened(UndoableEditEvent undoableEditEvent);}當一個可撤銷的命令發生時,UndoableEditListener使用UndoableEditEvent來通知所有感興趣的對象。UndoableEditEvent類包含一個屬性,edit,他會返回事件的UndoableEdit對象:public UndoableEdit getEdit()。
在Swing相關的所有包中,只有AbstractDocument類(定義在Document介面中)帶有內建的添加這些監聽器的支援。當建立我們自己的支援可撤銷操作的類時,我們需要藉助於UndoableEditSupport類來維護我們自己的監聽器列表,如下所述。
21.4.6 UndoableEditSupport類UndoableEditSupport類似於PropertyChangeSupport與VetoableChangeSupport的JavaBean相關類。所有這三個類會管理一個特定類型的監聽器列表。在UndoableEditSupport類的例子中,監聽器的類型為UnableEditListener。我們使用public void addUndoableListener(UndoableEditListener)添加監聽器,使用public void removeUndoableListener(UndoableEditListener)移除監聽器。
當我們希望通知一個UndoableEdit操作發生時,我們調用public void postEdit(UndoableEdit)方法,他會建立一個UndoableEditEvent並且調用每一個監聽器的undoableEditHappened()方法。
該使用的基本架構顯示在列表21-3中。通常情況下,我們將可撤銷的事件關聯到某些其他動作。在這個樣本中,他被關聯到ActionEvent發生的時刻,從而所有註冊的ActionListener對象需要得到通知。
package swingstudy.ch21; import java.awt.AWTEventMulticaster;import java.awt.event.ActionEvent;import java.awt.event.ActionListener; import javax.swing.event.UndoableEditListener;import javax.swing.undo.AbstractUndoableEdit;import javax.swing.undo.UndoableEditSupport; public class AnUndoableComponent { UndoableEditSupport undoableEditSupport = new UndoableEditSupport(this);ActionListener actionListenerList = null; public void addActionListener(ActionListener actionListener) {actionListenerList = AWTEventMulticaster.add(actionListener, actionListenerList);} public void removeActionListener(ActionListener actionListener) {actionListenerList = AWTEventMulticaster.remove(actionListener, actionListenerList);} public void addUndoableEditListener(UndoableEditListener undoableEditListener) {undoableEditSupport.addUndoableEditListener(undoableEditListener);} public void removeUndoableEditListener(UndoableEditListener undoableEditListener) {undoableEditSupport.removeUndoableEditListener(undoableEditListener);} protected void fireActionPerformed(ActionEvent event) {if(actionListenerList != null) {actionListenerList.actionPerformed(event);}// Need to create your custom type of undoable operationundoableEditSupport.postEdit(new AbstractUndoableEdit());}}
21.4.7 一個完整的可撤銷程式樣本現在我們已經瞭解了Swing Undo架構的主要類,下面我們來看一下定義自訂可撤銷類的完整樣本。這個可撤銷類是一個繪製面板,其中每一次滑鼠點擊定義了一個要在地區中繪製的點。圖21-5顯示了在撤銷操作之前與之後的可繪製面板。
列表21-4中的主程式看起來與前面列表21-2中的在Swing文本組件中支援撤銷與重做操作的樣本相同。我們只需要簡單的的建立一個UndoManager來管理可撤銷的操作並且將其關聯到可撤銷的對象。在這裡Undo架構的使用與此相同,所不同的是可撤銷的對象是將要建立的UndoableDrawingPanel類。
package swingstudy.ch21; import java.awt.BorderLayout;import java.awt.EventQueue; import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JToolBar;import javax.swing.undo.UndoManager; public class UndoDrawing { /** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub Runnable runner = new Runnable() {public void run() {JFrame frame = new JFrame("Drawing Sample");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); UndoableDrawingPanel drawingPanel = new UndoableDrawingPanel(); 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);} }UndoableDrawingPanel類是一個依據地區內的點集合在其內部繪製多邊形的組件。當滑鼠被釋放時新點就被添加到多邊形中。如果我們不希望組件支援可撤銷的操作,則除了收集用於繪製面板的點以外我們不需要做任何事情。
為使得面板支援可撤銷的操作,他需要做兩件事情:
- 他必須包含一個UndoableEditListener對象列表。這可以很容易的通過UndoableEditSupport類實現,如前面的列表21-3所示。
- 第二個任務需要建立一個UndoableEdit對象,依據狀態變化將其發送到註冊的監聽器。因為繪製面板的狀態是多連形,這個屬性必須在繪製類中公開。
列表21-5顯示了UndoableDrawingPanel類的定義。這個類不需要特別解釋。當定義一個可撤銷的類時需要記住的一件重要事情就是可撤銷的事情必須在組件狀態變化之前建立。(UndoableEdit介面的實作類別,UndoableDrawEdit,在稍後的列表21-6中顯示。)
package swingstudy.ch21;import java.awt.Graphics; import java.awt.Polygon; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener;import javax.swing.JPanel; import javax.swing.event.UndoableEditListener; import javax.swing.undo.UndoableEditSupport;public class UndoableDrawingPanel extends JPanel {UndoableEditSupport undoableEditSupport = new UndoableEditSupport(this); Polygon polygon = new Polygon();public UndoableDrawingPanel() { MouseListener mouseListener = new MouseAdapter() { public void mouseReleased(MouseEvent event) { undoableEditSupport.postEdit(new UndoableDrawEdit(UndoableDrawingPanel.this)); polygon.addPoint(event.getX(), event.getY()); repaint(); } }; addMouseListener(mouseListener); }public void addUndoableEditListener(UndoableEditListener undoableEditListener) { undoableEditSupport.addUndoableEditListener(undoableEditListener); }public void removeUndoableEditListener(UndoableEditListener undoableEditListener) { undoableEditSupport.removeUndoableEditListener(undoableEditListener); }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); } } 當定義自訂的UndoableEdit介面實現時,我們可以選擇實現完整的介面,或是我們可以繼承AbstractUndoableEdit類並且重寫相應的方法。更為通常的情況是,我們只是繼承AbstractUndoableEdit。儘管要重寫的最小方法就是undo()與redo(),但是我們也可以選擇重寫getPresentationName()方法來為可撤銷的操作提供一個更好的名字。
因為Command設計需要Concrete Command(也就是UndoableEdit實現)叫用作業,建構函式必須儲存使得操作可撤銷所必需的資訊。在繪製面板的例子中,我們需要儲存到面板及其當前多邊形的引用。那麼當請求操作撤銷時,原始的多邊形可以重新載入。要支援重做與撤銷操作,undo()方法必須同時儲存新多邊形;否則,redo()操作將不會知道如何將事物回複到以前狀態。這聽起來需要大量的工作,但是實際上並不是這樣。UndoableEdit實現的完整類定義顯示在列表21-6中。
package swingstudy.ch21; import java.awt.BorderLayout;import java.awt.EventQueue; import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JToolBar;import javax.swing.undo.UndoManager; public class UndoDrawing { /** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub Runnable runner = new Runnable() {public void run() {JFrame frame = new JFrame("Drawing Sample");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); UndoableDrawingPanel drawingPanel = new UndoableDrawingPanel(); 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);} }這就是全部的代碼。最後兩個類使得UndoDrawing樣本類可以工作。當建立我們自己的可撤銷類時,我們需要繼承一個不可撤銷的類,然後添加必要的支援使其可撤銷。另外,我們需要定義一個UndoableEdit實現來支援我們的特定類。