在桌面系统中,拖拽是一个用户很喜欢的功能。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拖拽。