在桌面系統中,拖拽是一個用戶很喜歡的功能。Eclipse主要由view和Editor組成,相互之間的拖拽需求很常見,一般主要是將view(tree/table)的東西拖到text/graphical editor。我寫一個簡單的例子,將一個view里的對象拖到text editor和graphical editor完成插入,其中text editor使用CDT提供的C++ Editor,而graphical editor使用shapes example(GEF SDK) 提供的shapes editor(為方便稍加改造)。
第一步:建立domain model,這個model里只包含block,一個GenericBlock和它的兩個子類ConstantBlock和LoopBlock。在C++編輯器拖拽中,ConstantBlock用來插入
const int XX = 0;之類的語句,LoopBlock用來插入
for循環;圖形模式下,ConstantBlock插入一個矩形,而LoopBlock插入一個橢圓形,正好對應shaps example的兩種圖形。
Generic Block
public abstract class GenericBlock implements IAdaptable {
protected String name;
//略
abstract protected String getNativeStatement();
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
}
ConstantBlock常量塊:
public class ConstantBlock extends GenericBlock {
.//略
protected String getNativeStatement() {
return "const int "+name+" = 999;";
}
}
LoopBlock循環塊:
public class LoopBlock extends GenericBlock {
.//略
protected String getNativeStatement() {
return "for (int i = 0; i < 100; i++) \n\t for (int j = i; j > 0; j--) \n\t\tprintf(\"i+j=%d\\n\",i*j);";
}
}
第二步:通過Eclipse adapter factory,將block適配成text editor和graphica editor想要的對象,分別為string和產生shape對象的CreationFactory。
Extension:
<extension
id="com.lifesting.scratch.blockadapter"
name="BLOCkAdapter"
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="com.lifesting.scratch.views.GenericBlock"
class="com.lifesting.scratch.ExtractCAdapterFactory">
<adapter
type="com.lifesting.scratch.views.IRetriveCStructure">
</adapter>
<adapter
type="org.eclipse.gef.requests.CreationFactory">
</adapter>
</factory>
</extension>
Adapter Factory:
public class ExtractCAdapterFactory implements IAdapterFactory {
@Override
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IRetriveCStructure.class)
return new DspExtractAdapter((GenericBlock) adaptableObject);
if (adapterType == CreationFactory.class)
return new BlockCreationFactoryAdapter((GenericBlock)adaptableObject);
return null;
}
@Override
public Class[] getAdapterList() {
return new Class[]{IRetriveCStructure.class,CreationFactory.class};
}
}
DspExtractAdatper只是簡單調用block.getNativeStatement,而傳遞給GEF Editor的將是CreationFactory,它被TemplateTransferDropTargetListener用來完成模型插入/圖形更新。
DspExtractAdatper
//IRetriveCStructure只定義了一個getStructure操作,用來得到C代碼
public class DspExtractAdapter implements IRetriveCStructure {
private GenericBlock block;
public DspExtractAdapter(GenericBlock block) {
super();
this.block = block;
}
@Override
public String getStructure() {
return block.getNativeStatement();
}
}
BlockCreationFactoryAdatper:(常量塊--矩形,循環塊--橢圓形)
public class BlockCreationFactoryAdapter implements CreationFactory {
private GenericBlock block;
public BlockCreationFactoryAdapter(GenericBlock adaptableObject) {
block = adaptableObject;
}
@Override
public Object getNewObject() {
Shape shape;
if (block instanceof ConstantBlock)
shape = new RectangularShape();
else
shape = new EllipticalShape();
shape.setName(block.getName());
return shape;
}
@Override
public Object getObjectType() {
if (block instanceof ConstantBlock)
return RectangularShape.class;
else
return EllipticalShape.class;
}
}
第三步:建一個view,完成拖拽的源,這個view里面包含一個tree viewer,使用的是一個簡單的tree input(見下效果圖)。首先是把它顯示出來:
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.setContentProvider(new TreeNodeContentProvider(){});
viewer.setLabelProvider(new LabelProvider(){
@Override
public String getText(Object element) {
Object v = ((TreeNode)element).getValue();
if (v instanceof String)
return (String) v;
return ((GenericBlock)v).getName();
}
});
viewer.setInput(getTreeModel());
hookDrag(viewer);
}
在swt中,拖拽(drag-drop)有三要素 drag source, transfer, drop target,下面依次定義:
drag(使用了兩個transfer,分別給text和graphics使用的,一般drag過程中應保持domain model即block的純潔性,然后根據不同目標適配):
private void hookDrag(final TreeViewer viewer2) {
viewer2.addDragSupport(DND.DROP_COPY |DND.DROP_DEFAULT, new Transfer[]{BlockTransfer.getInstance(),TemplateTransfer.getInstance()}, new DragSourceListener(){
@Override
public void dragFinished(DragSourceEvent event) {
}
@Override
public void dragSetData(DragSourceEvent event) {
TreeNode object = (TreeNode) ((IStructuredSelection)viewer2.getSelection()).getFirstElement();
event.data = object.getValue();
}
@Override
public void dragStart(DragSourceEvent event) {
TreeNode object = (TreeNode) ((IStructuredSelection)viewer2.getSelection()).getFirstElement();
boolean drag_block = object.getValue() instanceof GenericBlock;
event.doit = drag_block;
}});
}
Transfer,沒有什么特殊的,所有Transfer的寫法都是一個套路。
public class BlockTransfer extends ByteArrayTransfer {
.
@Override
protected Object nativeToJava(TransferData transferData) {
if (!isSupportedType(transferData)) return null;
byte[] bts = (byte[]) super.nativeToJava(transferData);
//略,將byte[]轉化為Java對象
}
@Override
protected void javaToNative(Object object, TransferData transferData) {
if (!(object instanceof GenericBlock))
return;
GenericBlock block = (GenericBlock) object;
//略,將block轉化為byte[]
}
.
}
要使用drop,首先必須得在target(text editor/graphical editor)上注冊才能使用。這兒使用Eclipse提供PartListener,每當一個編輯器打開或者激活是,判斷能不能成為drop target,能的話就把drop注冊上。
1 private IPartListener listener = new IPartListener(){
2 @Override
3 public void partActivated(IWorkbenchPart part) {
4 if (part instanceof ITextEditor)
5 {
6 ITextEditor editor = (ITextEditor) part;
7 Control editor_control = (Control) editor.getAdapter(Control.class);
8 DropTarget dropTarget= (DropTarget)editor_control.getData(DND.DROP_TARGET_KEY);
9 if (dropTarget == null)
10 dropTarget= new DropTarget(editor_control, DND.DROP_DEFAULT | DND.DROP_COPY );
11 if (Boolean.TRUE != dropTarget.getData(KEY))
12 hookDrop(dropTarget);
13 }
14 }
15 @Override
16 public void partBroughtToTop(IWorkbenchPart part) {}
17 @Override
18 public void partClosed(IWorkbenchPart part) {}
19 @Override
20 public void partDeactivated(IWorkbenchPart part) {}
21 void hookDrop(DropTarget dropTarget)
22 {
23 Transfer[] currentTransfers= dropTarget.getTransfer();
24 int currentLength= currentTransfers.length;
25 Transfer[] newTransfers= new Transfer[currentLength + 1];
26 System.arraycopy(currentTransfers, 0, newTransfers, 0, currentLength);
27 newTransfers[currentLength]= BlockTransfer.getInstance();
28 dropTarget.setTransfer(newTransfers);
29 dropTarget.addDropListener(drop_listener);
30 dropTarget.setData(KEY, Boolean.TRUE);
31 }
32 @Override
33 public void partOpened(IWorkbenchPart part) {
34 if (part instanceof ITextEditor)
35 {
36 ITextEditor editor = (ITextEditor) part;
37 Control editor_control = (Control) editor.getAdapter(Control.class);
38 DropTarget dropTarget= (DropTarget)editor_control.getData(DND.DROP_TARGET_KEY);
39 if (dropTarget == null)
40 dropTarget= new DropTarget(editor_control, DND.DROP_DEFAULT | DND.DROP_COPY );
41 hookDrop(dropTarget);
42 }
43 }
44 };
29行加了一個drop listener,即target響應drop操作,最終實現拖拽效果。
1 private DropTargetListener drop_listener = new DropTargetAdapter(){
2 @Override
3 public void drop(DropTargetEvent event) {
4 if (!BlockTransfer.getInstance().isSupportedType(event.currentDataType)) return;
5 GenericBlock block = (GenericBlock) event.data;
6 IRetriveCStructure cs = (IRetriveCStructure) block.getAdapter(IRetriveCStructure.class);
7 if (cs != null)
8 {
9 Control ctrl = ((DropTarget)event.widget).getControl();
10 if (ctrl instanceof StyledText)
11 {
12 ((StyledText)ctrl).insert(cs.getStructure());
13 }
14 }
15 }
16 @Override
17 public void dragOver(DropTargetEvent event) {
18 event.feedback = DND.FEEDBACK_SELECT;
19 }
20 };
注意18行的feedback,沒有它就不能完成在text editor的插入。
等等,怎么drop listener里面沒有關于shapes edtior的東西,怎樣在shapes editor里面插入shapes呢?
第四步:改造shapes example。GEF SDK里面提供了一個很好的drop listener-- TemplateTransferDropTargetListener,當從palette 往diragam拖拽得時候使用的就是它,而這里從自定義view往diagram拖拽還是要用到它,為了更形象,在shape里面加了一個屬性name,把name顯示在每個shape的中央。
public abstract class Shape extends ModelElement {
private static IPropertyDescriptor[] descriptors;
//略
protected String name="Null";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//略
}
class ShapeEditPart extends AbstractGraphicalEditPart {
//略
//修改這個方法,加入shape name
private IFigure createFigureForModel() {
IFigure figure;
if (getModel() instanceof EllipticalShape) {
figure = new Ellipse();
} else if (getModel() instanceof RectangularShape) {
figure = new RectangleFigure();
} else {
// if Shapes gets extended the conditions above must be updated
throw new IllegalArgumentException();
}
figure.setLayoutManager(new BorderLayout());
figure.add(new Label(((Shape)getModel()).getName()),BorderLayout.CENTER);
return figure;
//略
}
為了讓shapes editor使用block適配的creation factory,還需要修改一下shapes editor。
public class ShapesEditor
extends GraphicalEditorWithFlyoutPalette
{
//略
private TransferDropTargetListener createTransferDropTargetListener() {
return new TemplateTransferDropTargetListener(getGraphicalViewer()) {
protected CreationFactory getFactory(Object template) {
if (template instanceof IAdaptable)
{
CreationFactory factory = (CreationFactory) ((IAdaptable)template).getAdapter(CreationFactory.class);
if (factory != null) return factory;
}
return new SimpleFactory((Class) template);
}
};
//略
}
這樣一個非常完整的例子就完成了。效果圖如下,其中曲線表示由某一端拖拽而成,可以看出,自定義view并不影響palette拖拽。