在目前的GEF版本(3.1M6)里,可用的LayoutManager還不是很多,在新聞組里經常會看到要求增加更多布局的帖子,有人也提供了自己的實現,例如這個GridLayout,相當于SWT中GridLayout的Draw2D實現,等等。雖然可以肯定GEF的未來版本里會增加更多的布局供開發者使用(可能需要很長時間),然而目前要用GEF實現表格的操作還沒有很直接的辦法,這里說說我的做法,僅供參考。
實現表格的方法決定于模型的設計,初看來我們似乎應該有這些類:表格(Table)、行(Row)、列(Column)和單元格(Cell),每個模型對象對應一個EditPart,以及一個Figure,TablePart應該包含RowPart和ColumnPart,問題是RowFigure和ColumnFigure會產生交叉,想象一下你的表格該使用什么樣的布局才能容納它們?使用這樣的模型并非不能實現(例如使用StackLayout),但我認為這樣的模型需要做的額外工作會很多,所以我使用基于列的模型。
在我的表格模型里,只有三種對象:Table、Column和Cell,但Column有一個子類HeaderColumn表示第一列,同時Cell有一個子類HeaderCell表示位于第一列里的單元格,后面這兩個類的作用主要是模擬實現對行的操作--把對行的操作都轉換為對HeaderCell的操作。例如,創建一個新行轉換為在第一列中增加一個新的單元格,當然在這同時我們要讓程序給其余每一列同樣增加一個單元格。
圖1 表格編輯器
現在的問題就是怎樣讓用戶察覺不到我們是在對單元格而不是對行操作。需要修改的地方有這么幾處:一是創建新行或改變行位置時顯示與行寬一致的插入提示線,二是在用戶點擊位于第一列中的單元格(HeaderCell)時顯示為整個行被選中,三是允許用戶通過鼠標拖動改變行高度,最后是在改變行所在位置或大小的時候顯示正確的回顯(Feedback)圖形。下面依次介紹它們的實現方法。
調整插入線的寬度
在我們的調色板里有一個Row工具項,代表表格中的一個行,它的作用是創建新的行。注意這個工具項的名字雖然叫Row,實際上用它創建的是一個HeaderCell對象,創建它的代碼如下:
tool = new CombinedTemplateCreationEntry("Row", "Create a new Row", HeaderCell.class, new SimpleFactory(HeaderCell.class), CbmPlugin.getImageDescriptor(IConstants.IMG_ROW), null);創建新行的方式是從調色板里拖動它到想要的位置。在拖動過程中,隨著鼠標所在位置的變化,編輯器應該能顯示一條直線,用來表示如果此時放開鼠標新行將插入的位置。由于這個工具代表的是一個單元格,所以缺省情況下GEF會顯示一條與單元格長度相同的插入線,為了讓用戶感覺到是在插入行,我們必須改變插入線的寬度。具體的方法是在HeaderColumnPart的負責Layout的那個EditPolicy(繼承FlowLayoutEditPolicy)中覆蓋showLayoutTargetFeedback()方法,修改后的代碼如下:












其中p2代表插入線中右邊的那個點,我們將它的橫坐標加上一個量即可增加這條線的長度,這個量和表格當前列的數目有關,和列間距也有關,計算的方法看上面的代碼很清楚。這樣修改后的效果如下圖所示,拖動行到新的位置時也會使用同樣的插入線。
圖2 與表格同寬的插入線
選中整個行
缺省情況下,鼠標點擊一個單元格會在這個單元格四周產生一個黑色的邊框,用來表示被選中的狀態。為了讓用戶能選中整個行,要修改HeaderCell上的EditPolicy。在前面一篇帖子里已經專門講過,單元格作為列的子元素,要修改它的EditPolicy就要在ColumnPart的EditPolicy的createChildEditPolicy()方法里返回自定義的EditPolicy,這里我返回的是自己實現的DragRowEditPolicy,它繼承自GEF內置的ResizableEditPolicy類,它將被HeaderColumnPart加到子元素HeaderCellPart的EditPolicy列表。現在就來修改DragRowEditPolicy以實現整個行的選中。
首先要說明,在GEF里一個圖形被選中時出現的黑邊和控制點稱為Handle,其中黑邊稱為MoveHandle,用于移動圖形;而那些控制點稱為ResizeHandle,用于改變圖形的尺寸。要改變黑邊的尺寸(由單元格的寬度擴展為整個表格的寬度),我們得繼承MoveHandle并覆蓋它的getLocator()方法,下面的代碼是我的實現:

























在getLocator()方法里,我們調用了HeaderCellPart的getRowBound()方法用于得到選中行的位置和尺寸,這個方法的代碼如下(放在HeaderCellPart里是因為在Handle里通過getOwner()可以很容易得到EditPart對象),行尺寸的計算方法與前面插入線的情況類似:










有了這個RowMoveHandle,只要把它代替原來缺省的MoveHandle加到HeaderColumnCell上即可,具體的方法就是覆蓋DragRowEditPolicy的createSelectionHandles()方法,ResizableEditPolicy對這個方法的缺省實現是加一個黑框和八個控制點,而我們要改成下面這樣:











代碼里用到的RowResizeHandle類是控制點的自定義實現,在下面很快會講到。現在,用戶可以看到整個行被選中的效果了。
圖3 選中整個行
改變行的高度
改變行高度比較自然的方式是讓用戶選中行后自由拖動下面的邊。前面說過,GEF里的ResizeHandle具有調整圖形尺寸的功能,美中不足的是ResizeHandle表現為黑色(或白色,非主選擇時)的小方塊,而我們希望它是一條線就好了,這樣鼠標指針只要放在選中行的下邊上就會變成改變尺寸的樣子。這就需要我們實現剛才提到的RowResizeHandle類了,它是ResizeHandle的子類,代碼如下:









































這樣,我們就把控制點拉成了控制線,因為它的位置與選擇框(RowMoveHandle)的一部分重合,所以在界面上感覺不到它的存在,但用戶可以通過它控制行的高度,見下圖。
圖4 改變行高的提示
正確的回顯圖形
我們知道,在拖動圖形和改變圖形尺寸的時候,GEF會顯示一個"影圖"(Ghost Shape)作為回顯,也就是顯示圖形的新位置和尺寸信息。因為操作行時目標對象實際是單元格,所以在缺省情況下回顯也是單元格的樣子(寬度與列寬相同)。為此,在DragRowEditPolicy里要覆蓋getInitialFeedbackBounds()方法,這個方法返回的Rectangle決定了鼠標開始拖動時回顯圖形的初始狀態,見以下代碼:






這時的回顯見下圖,在拖動行時也使用同樣的回顯。
圖5 改變行高時的回顯
經過上面的修改,對HeaderCell的操作在界面上已經完全表現為對表格行的操作了。這些操作的結果會轉換為一些Command,包括CreateHeaderCellCommand(創建新行,你也可以命名為CreateRowCommand)、MoveHeaderCellCommand(移動行)、DeleteHeaderCellCommand(刪除行)和ChangeHeaderCellHeightCommand(改變行高)等,在這些類里要對所有列執行同樣的操作(例如改變HeaderCell的高度的同時改變同一行中其他單元格的高度),這樣在界面上才能保持表格的外觀,詳細的代碼沒有必要貼在這里了。
P.S.曾經考慮過另一種實現表格的方法,就是模型里只有Table和Cell兩種對象,然后自己寫一個TableLayout負責單元格的布局。同樣是因為修改的工作量相對比較大而沒有采用,因為那樣的話行和列都要使用自定義方式處理,而這篇貼子介紹的方法只關心行的處理就可以了。當然,這里說的也不是什么標準實現,不過效果還是不錯的,而且確實可以實現,如果你有類似的需求可以作為參考。
評論
protected void setInput(IEditorInput input) {
super.setInput(input);
ShapesEditorInput in = (ShapesEditorInput)input;
diagram = in.getModel();
}
和OpenEidtor
ShapesEditorInput shapesIn = new ShapesEditorInput();
shapesIn.setModel(new ShapesDiagram());
try {
configurer.getWindow().getActivePage().openEditor(shapesIn, ShapesEditor.ID);
}
其它我也將ide和ui的相關依賴項去掉了,但出現這個錯誤
java.lang.NoClassDefFoundError: org/eclipse/gef/ui/parts/GraphicalEditorWithFlyoutPalette
和 Unable to instantiate editor,unable to load class。
能給點建議么?謝謝了。
EClass eClass = null;
eClass = ACMPackage.eINSTANCE.getApplicationComponent();
combined = new CombinedTemplateCreationEntry(
"ApplicationComponent",
"Create a new ApplicationComponent",
eClass,
new EObjectTemplateCreationFactory(eClass),
ImageDescriptor.createFromURL(BlueprintMMEditPlugin.getPlugin().getBundle().getEntry("icons/full/obj16/ApplicationComponent.gif")),
ImageDescriptor.createFromURL(BlueprintMMEditPlugin.getPlugin().getBundle().getEntry("icons/full/obj16/ApplicationComponent.gif")));
entries.add(combined);
eClass = ACMPackage.eINSTANCE.getApplicationComponent();
combined = new CombinedTemplateCreationEntry(
"ApplicationComponentGroup",
"Create a new ApplicationComponentGroup",
eClass,
new EObjectTemplateCreationFactory(eClass),
ImageDescriptor.createFromURL(BlueprintMMEditPlugin.getPlugin().getBundle().getEntry("icons/full/obj16/ApplicationComponent.gif")),
ImageDescriptor.createFromURL(BlueprintMMEditPlugin.getPlugin().getBundle().getEntry("icons/full/obj16/ApplicationComponent.gif")));
entries.add(combined);
只是顯示的label不同,我想在editpart 或policy中得到不同的label名字 (ApplicationComponent, applicationComponentGroup)我應該怎么寫 ,謝謝高手幫我解答
今天debug了一天 頭都大了 。。。