http://blog.csdn.net/chosen0ne/archive/2009/08/16/4453510.aspx
前面文章提到過,許多複合資料型組件不僅僅需要展現資料,還需要編輯資料。比如JTable的某些儲存格可能需要編輯,JComboBox除了選擇外還可以直接編輯資料,有些JTree有時也需要直接編輯節點。Swing中解決此類問題的方案叫做所謂的in-place
editor。Swing綜合in-place
editor和Renderer原理賦予了Swing擴充複雜組件功能的能力。注意in-place
editor其實只是普通的Swing組件,並不是新的Swing元素,它們往往結合Renderer完成組件的擴充,所以我把它們和Renderer放在一起講了。
Swing可以使用In-place editor的兩個前提條件是:
- Swing組件需要能脫離容器組件獨立存在;
- Swing組件樹能在運行時動態地被修改,包括添加和刪除。
Swing組件樹就像瀏覽器的HTML
DOM樹一樣,可以通過指令碼語言JavaScript進行動態修改,從而達到瀏覽器頁面的局部更新的功能,Swing通過動態往容器組件中添加或者刪除編輯組件實現複合類型組件的in-place
editor功能。
所有Swing組件都是直接或者間接繼承自JComponent,而JComponent是Container類的一個子類,因此所有的Swing組件都
可看作一個容器組件。複合資料型別的組件也是如此,它們內部往往擁有自己的子結構、布局管理器、為實現某種功能而隱藏了的子組件(比如前篇文章中所提到的
CellRendererPane)等。複合資料型別的組件通過動態修改自己內部組件樹結構實現in-place editor。
In-place
editor的生命週期包括啟用、編輯、停止編輯、取消編輯、刪除編輯器等。下面以JTable為例詳細瞭解In-place
editor的工作過程。
首先要瞭解JTable的in-place
editor介面TableCellEditor的定義:
public interface TableCellEditor
extends CellEditor {
/*該方法返回JTable當前表格的編輯組件,該組件根據提供的參數table、value、isSelected、row、column進行配置。注
意,對該組件的調用有可能會導致停止編輯,丟失尚未編輯完的資料。該組件將作為JTable的子組件添加到組件樹上,之後就可以接受使用者的輸入了。*/
Component getTableCellEditorComponent(JTable table,
Object value,
boolean
isSelected,
int
row, int column);
}
public interface CellEditor
{
/*返回編輯器內的值,必須是編輯後有效值,用在JTable中作為擷取編輯結果用*/
public Object
getCellEditorValue();
/*讓編輯器判斷anEvent是否能啟用編輯動作,anEvent使用的是JTable組件座標系,這兒編輯器不能假設
getCellEditorComponent返回的組件已經安裝在JTable中。如果當前事件觸發編輯表現方式,則返回true,否則false。*/
public boolean isCellEditable(EventObject
anEvent);
/*該方法同前一方法類似,只不過是判斷該事件是否允許選中當前格。大多數情況下,這兒直接返回true,因為一般如果可編輯,就應該允許被選中。然而有
些情況下編輯表現方式發生時,並不想改變表格的選中狀態。比如,使用者可能想改變checkbox編輯器的值,卻並不想因為要點擊checkbox而改變
JTable當前選中的其他行。*/
public boolean shouldSelectCell(EventObject
anEvent);
/*停止編輯器的編輯,接受部分編輯的值作為編輯結果值。如果編輯不能停止,則返回false,這對於編輯器檢查正在編輯的值是否有效,是否能接受無效值時很有效。*/
public boolean
stopCellEditing();
/*告訴編輯器取消編輯,不要使用任何部分編輯過的值*/
public void
cancelCellEditing();
/*添加CellEditorListener,當編輯器停止或者取消編輯表現方式時觸發該處理器*/
public void addCellEditorListener(CellEditorListener
l);/*刪除CellEditorListener*/
public void removeCellEditorListener(CellEditorListener
l);
}
再看一下CellEditorListener的定義:
public interface
CellEditorListener extends java.util.EventListener
{
/*通知編輯器已經停止編輯,編輯效果生效。*/
public
void editingStopped(ChangeEvent
e);
/*通知編輯器已經取消編輯,編輯效果被取消*/
public
void editingCanceled(ChangeEvent e);
}
當滑鼠雙擊之類引起編輯的事件發生時,JTable的啟用編輯器的過程可用下面的圖來示意:
首先檢查當前是否正在編輯。如果正在編輯,則調用TableCellEditor的stopCellEditing來結束編輯。
stopCellEditing如果不能正常結束,則返回false,這時便把焦點重新置放給該編輯器,並結束。如果當前不是正在編輯,或者雖然正在編
輯,但是stopCellEditing正常結束了,則判斷當前的滑鼠事件是否是滑鼠拖動行為。如果是則調用mousePressedDND方法處理。否
則說明該事件有可能引發編輯功能。接著調用TableModel.isCellEditable判斷該目前的儲存格是否允許編輯。如果允許編輯,則獲得當前
儲存格的TableCellEditor,調用其isCellEditable判斷當前滑鼠事件是否意味著啟用編輯(比如規定雙擊意味著啟用編輯、單擊並
不啟用等)。如果是啟用編輯事件,則從TableCellEditor.getTableCellEditorComponent擷取編輯組件,最後將該
組件添加到組件樹上,添加適當的處理器、有效化之後等待使用者輸入。
TableCellEditor的stopCellEditing方法通常會刪除當前編輯器。上面過程中,如果JTable正在處於編輯中,滑鼠的點擊則
會導致調用TableCellEditor的stopCellEditing方法刪除編輯器,並開始新的編輯器的安裝過程。
stopCellEditing在這個過程中擷取編輯器的值並把它賦予JTable。其工作過程如下:
tableCellEditing的stopCellEditing(左邊)先檢查目前編輯組件的值是否有效(比如輸入的整數是否越界)。如無效則
(有時需要提示使用者)返回false。如果有效則將該值作為editor的最終值儲存起來,以備JTable通過
editor.getCellEditorValue調用。然後觸發編輯停止事件fireEditingStopped。由於JTable在安裝編輯器
時,總把自己註冊為它的CellEditorListener處理器,因此當JTable能接到該通知。之後JTable就在其
editingStopped(右邊)方法中處理該事件。JTable首先使用editor.getCellEditorValue獲得該
editor編輯好的值,並調用setValueAt將該值更新到目前的儲存格,最後調用removeEditor刪除該編輯器。控制返回到
TableCellEditor的stopCellEditing(左邊圖)後,stopCellEditing返回true結束整個過程。
TableCellEditor還會發出另一個事件fireEditingCanceled。這經常出現在編輯器本身有所謂取消功能編輯組件上。比如想在
按下ESC鍵時取消當前正在編輯的值,就可以調用TableCellEditor的cancelEditing來取消。cancelEditing的工作
過程比較簡單,往往是直接通過fireEditingCanceled觸發取消動作,偵聽的JTable會在其editingCanceled方法中簡單
將編輯器刪除去,繼續保留以前的值。
理解TableCellEditor介面的方法之間的關係對於編寫自己的Editor很重要,它們之間除了上面注釋所描述的含義外,還有一些暗示的關係編寫程式時需要注意:
1.
在自訂編輯器的stopCellEditing中要檢查編輯組件正在編輯資料的有效性。如有效,要通過fireEditingStopped之類方法通
知註冊在此編輯器上的介面。如果沒有觸發,JTable將不能正常工作,編輯的值也會被丟失。最後要返回true作為成功的標識;如果資料沒有效,一般做
法是提示使用者錯誤,使用者確認錯誤提示後要要把焦點重新置放到編輯組件上,並返回false作為失敗的標識。
2.注意在fireEditingStopped調用之前,getCellEditorValue返回的值一定要是當前有效值。因為JTable會緊跟其後,調用該方法將編輯好的值填入表中並刪除當前編輯器。
3.cancelCellEditing一般什麼都不做,簡單的fireEditingCanceled就行了。JTable響應該事件僅僅是簡單的刪除編輯器。
4.
要實現方法addCellEditorListener和removeCellEditorListener,而不能空著它們,並且要定義
fireEditingStopped和fireEditingCanceled兩個方法以便在stopCellEditing和
cancelCellEditing方法中使用它們觸發事件。
5.getTableCellEditorComponent
返回的組件需要添加適當的事件處理器,該處理器在使用者編輯確認時(比如JTextField在敲斷行符號引發ActionPerformed時),應該
stopCellEditing以此來通知這次編輯過程已經完成,需要更新到JTable中。
TableCellEditor介面暗含許多內部關係,如果不能正確建立它們之間的關係,實現往往並不能複合你的需求。
JTree的TreeCellEditor介面和TableCellEditor除了擷取編輯組件的方法的參數有所不同外,其他完全相同,其工作過程也類似。
除了JTable和JTree外,JComboBox也有in-place
editor,它的editor介面相對簡單一些:
public
interface ComboBoxEditor {
/*返回編輯的組件*/
public
Component
getEditorComponent();
/*設定要編輯的值,如果需要終止正在編輯的其他值*/
public
void setItem(Object
anObject);
/*返回當前正在編輯的值*/
public Object
getItem();
/*全選*/
public void
selectAll();
/*當編輯的值得到確認時或者發生變化時將會調用註冊上面的ActionListener,改方法添加ActionListener*/
public
void addActionListener(ActionListener
l);
/*刪除ActionListener*/
public void
removeActionListener(ActionListener l);
}
由於JComboBox只有一處可以編輯,因此的編輯過程相當簡單。JComboBox將
ComboBoxEditor 提供組件作為編輯器添加到組件樹上,自己註冊為該 ComboBoxEditor
的ActionListener。當編輯組件的值發生變化,或使用者確認編輯結果時,通知JComboBox。JComboBox發出適當的事件通知JComboBox的外來事件偵聽者。
當使用者從列表中選擇某項進行編輯時,JComboBox使用
ComboBoxEditor.setItem設定編輯器的初始值。當使用者請求JComboBox擷取當前編輯值時,JComboBox調用
ComboBoxEditor.getItem擷取正在編輯的值,另外JComboBox可以通過ComboBoxEditor的selectAll來請
求全選操作,這時允許如JTextField為編輯組件的編輯器實現全選。
ComboBoxEditor介面的方法也暗含幾個關係:
1.setItem、getItem、selectAll的操作對象應該是getEditorComponent返回的同一組件。
2.同樣要實現addActionListener和removeActionListener,一般要實現fireActionPerformed,要偵聽編輯組件的事件,使用fireActionPerformed通知JComboBox做出響應。
編輯器工作過程相對比Renderer要複雜一些,主要是因為編輯過程是和使用者進行互動的過程,不像Renderer那樣只是簡單的渲染過程。這個過程是
非過程性的,所以它們的介面也就複雜的多,介面方法之間也暗含這一定的關係。
但要正確理解也不難,關鍵要清楚JTable、JTree和JComboBox編輯器工作的過程。知道這些過程並根據以及前所學的事件及事件器處理模型知
識,就應該很容易理解各個介面方法的含義以及它們之間暗含的關係,也就不會在實現自己的Editor時一頭霧水,無所適從。由於篇幅的原因,今天文章就到
此為止了。明天的文章將舉一些具體例子加深對in-place editor的理解。