Dienstag, 6. März 2012

Eclipse RCP - TreeViewer drag and drop (DnD)

Eclipse Version 3.7 (Indigo)


In diesem Artikel (Tutorial) wird ein einfaches 'Drag and Drop' Beispiel mit einem TreeViewer gezeigt.

Problem:
In einem TreeViewer sollen die Knoten per 'Drag and Drop' umgeordnet werden. Es soll möglich sein, einen Knoten des TreeViewer gezielt vor oder hinter einen anderen Knoten einzufügen. Es ist nicht möglich die Knoten eines TreeViewer zwischen zwei Fenstern zu schieben. Sollte ein Interesse an solch einem Tutorial bestehen, dann schickt mir bitte eine kurze Info per Mail.

Schritt 1:
RCP Applikation mit dem Namen com.blogspot.javadingsda.dndfirst aus der Vorlage 'RCP application with a view' erstellen.

Schritt 2:
Entferne alle inneren Klassen, Methoden und Attribute aus der View Klasse und ersetze sie durch den unten aufgeführten Code. Die View Klasse sollte wie folgt aussehen.

package com.blogspot.javadingsda.dndfirst;

import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

public class View extends ViewPart {
 public static final String ID = "com.blogspot.javadingsda.dndfirst.view";

 private TreeViewer viewer;
 Model model = new Model();

 public void createPartControl(Composite parent) {
  viewer = new TreeViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
  viewer.setContentProvider(new ViewContentProvider());
  Transfer[] transferTypes = new Transfer[] { 
                                                    TextTransfer.getInstance() };
  viewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, 
                                      transferTypes,
          new NodeDragListener(viewer));
  viewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY, 
                                      transferTypes,
          new NodeDropListener(this, viewer));
  viewer.setInput(model.getRoot());
 }

 public void setFocus() {
  viewer.getControl().setFocus();
 }
}

Schritt 3:
Erzeuge die Klasse ViewContentProvider. Die Funktionalität dieser Klasse (bzw. der Klasse ITreeContentProvider) wird im Artikel Eclipse RCP / SWT - Einfaches TreeViewer Beispiel erläutert.

package com.blogspot.javadingsda.dndfirst;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;

public class ViewContentProvider implements ITreeContentProvider {

 public void inputChanged(Viewer v, Object oldInput, Object newInput) {
 }

 public void dispose() {
 }

 public Object[] getElements(Object parent) {
  TreeNode ret = (TreeNode) parent;
  return ret.getChildren();
 }

 @Override
 public Object[] getChildren(Object parentElement) {
  TreeNode node = (TreeNode) parentElement;
  return node.getChildren();
 }

 @Override
 public Object getParent(Object element) {
  TreeNode node = (TreeNode) element;
  return node.getParent();
 }

 @Override
 public boolean hasChildren(Object element) {
  TreeNode node = (TreeNode) element;
  return node.getChildren().length > 0;
 }
}


Schritt 4:
Erzeuge die Klasse TreeNode. Diese repräsentiert die Knotenpunkte im Baum. Abgesehen von den standard Methoden wie getParent und addChild besitzt diese Klasse die Methoden insertAfter und insertBefore. Die Methode insertAfter fügt einen Knoten hinter einen anderen Knoten hinzu. Die Methode insertBefore fügt einen Knoten vor einen anderen Knoten.









package com.blogspot.javadingsda.dndfirst;

import java.util.LinkedList;
import java.util.List;

public class TreeNode {

 private List<TreeNode> children = new LinkedList<TreeNode>();
 private String name; // visible name
 private TreeNode parent;

 public TreeNode(String name) {
  this.name = name;
 }

 public void addChild(TreeNode child) {
  if (child.getParent() != null) {
   ((TreeNode) child.getParent()).children.remove(child);
  }
  child.setParent(this);
  children.add(child);
 }

 public void removeChild(TreeNode node) {
  children.remove(node);
  node.setParent(null);
 }

 public void insertAfter(Object targetObj) {
  TreeNode target = convertToTreeNode(targetObj);
  List<TreeNode> childrenList = target.parent.children;
  int targetIndex = childrenList.indexOf(target);
  int thisIndex;
  int index;
  if (target.parent == this.parent) {
   thisIndex = childrenList.indexOf(this);
   if (thisIndex < targetIndex)
    index = targetIndex;
   else
    index = targetIndex + 1;
  } else {
   index = targetIndex + 1;
  }
  insertChild(target, index);
 }

 public void insertBefore(Object targetObj) {
  TreeNode target = convertToTreeNode(targetObj);
  int index;
  int thisIndex;
  int targetIndex = target.parent.children.indexOf(target);
  if (target.parent == this.parent){
   thisIndex = target.parent.children.indexOf(this);
   if (thisIndex < targetIndex) {
    index = targetIndex - 1;
   } else {
    index = targetIndex;
   }
  } else {
   index = targetIndex;
  }
  insertChild(target, index);
 }

 private void insertChild(TreeNode target, int index) {
  if (parent != null)
   parent.removeChild(this);
  target.parent.children.add(index, this);
  this.setParent(target.parent);
 }

 private TreeNode convertToTreeNode(Object obj) {
  if (obj instanceof TreeNode) {
   return (TreeNode) obj;
  }
  return null;
 }

 private void setParent(TreeNode parent) {
  this.parent = parent;
 }

 public String toString() {
  return name;
 }

 public Object[] getChildren() {
  return children.toArray();
 }

 public Object getParent() {
  return parent;
 }
}
Schritt 5:
Erzeuge die Model Klasse. Die Model Klasse erzeugt einen Baum in dem die Knoten durchnummeriert sind. Die Reihenfolge ist willkürlich.

package com.blogspot.javadingsda.dndfirst;

import java.util.HashMap;

public class Model {

 private TreeNode root = new TreeNode("root");
 private HashMap<String, TreeNode> map = new HashMap<String, TreeNode>();

 public Model() {

  TreeNode node1 = new TreeNode("Node1");
  TreeNode node2 = new TreeNode("Node2");
  TreeNode node3 = new TreeNode("Node3");
  TreeNode node4 = new TreeNode("Node4");
  TreeNode node5 = new TreeNode("Node5");
  TreeNode node6 = new TreeNode("Node6");
  TreeNode node7 = new TreeNode("Node7");
  TreeNode node8 = new TreeNode("Node8");
  TreeNode node9 = new TreeNode("Node9");
  TreeNode node10 = new TreeNode("Node10");
  TreeNode node11 = new TreeNode("Node11");
  TreeNode node12 = new TreeNode("Node12");
  TreeNode node13 = new TreeNode("Node13");

  map.put("root", root);
  map.put("Node1", node1);
  map.put("Node2", node2);
  map.put("Node3", node3);
  map.put("Node4", node4);
  map.put("Node5", node5);
  map.put("Node6", node6);
  map.put("Node7", node7);
  map.put("Node8", node8);
  map.put("Node9", node9);
  map.put("Node10", node10);
  map.put("Node11", node11);
  map.put("Node12", node12);
  map.put("Node13", node13);

  root.addChild(node1);
  root.addChild(node2);
  node1.addChild(node3);
  node1.addChild(node4);
  node1.addChild(node5);
  node2.addChild(node6);
  node2.addChild(node7);
  node2.addChild(node8);
  node2.addChild(node9);
  node2.addChild(node10);
  root.addChild(node11);
  root.addChild(node12);
  root.addChild(node13);
 }

 public TreeNode getRoot() {
  return root;
 }

 public TreeNode get(String nodeName) {
  return map.get(nodeName);
 }
}

Schritt 6:
Erzeuge die Klasse NodeDragListener. Die vom DragScourceAdapter geerbte Methode dragSetData(DragSourceEvent event) hat die Aufgabe den Text des beim 'draggen' selektierten Knotens im data Attribut abzulegen. Sollte die Selektion nicht auf einem TreeViewer statt finden, dann wird die 'Drag and Drop' Aktion abgebrochen.

package com.blogspot.javadingsda.dndfirst;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;

public class NodeDragListener extends DragSourceAdapter {

  private TreeViewer treeViewer;

  public NodeDragListener(TreeViewer treeViewer) {
    this.treeViewer = treeViewer;
  }

  @Override
  public void dragSetData(DragSourceEvent event) {
    ISelection selection = treeViewer.getSelection();

    if (selection instanceof TreeSelection) {
      TreeSelection treeSelection = (TreeSelection) selection;
      event.data = ((TreeNode) treeSelection.getFirstElement())
                                  .toString();
    } else {
      event.doit = false;
    }
  }
}


Schritt 7:
Erzeuge die Klasse NodeDropListener. Diese Klasse steuert den 'Drop', d.h. die Ereignisse wenn der Benutzer einen Knoten wieder im Baum losläßt. Die Methode validateDrop prüft ob ein Element auf der definierten stelle losgelassen ('gedropt') werden kann. Diese wird vom ViewDropAdapter benutzt. Die drop Methode kümmert sich um das Verschieben der TreeNode im Baum, indem sie die Aufgabe an die richtige Methode der Ziel TreeNode delegiert. Das Attribut operation zeigt an, in welcher Relation die TreeNode zur Ziel TreeNode hinzugefügt werden soll.

package com.blogspot.javadingsda.dndfirst;

import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TransferData;

class NodeDropListener extends ViewerDropAdapter {

  private View view;
  private TreeViewer treeViewer;
  private TreeNode target;
  private int operation;

  public NodeDropListener(View view, TreeViewer treeViewer) {
    super(treeViewer);
    this.view = view;
    this.treeViewer = treeViewer;
  }

  @Override
  public void drop(DropTargetEvent event)  {

    if (event.data instanceof String) {
      operation = this.determineLocation(event);
      TreeNode dropedNode = this.view.model.get((String) event.data);
      TreeNode parentDropedNode = (TreeNode) dropedNode.getParent();
      if (dropedNode == target)
        return;
      switch (this.operation) {
        case ViewerDropAdapter.LOCATION_AFTER:
          dropedNode.insertAfter(target);
          System.out.println(dropedNode + " wurde hinter " + target + " eingefügt.");
          break;
        case ViewerDropAdapter.LOCATION_BEFORE:
          dropedNode.insertBefore(target);
          System.out.println(dropedNode + " wurde vor " + target + " eingefügt.");
          break;
        case ViewerDropAdapter.LOCATION_ON:
          if (dropedNode != target.getParent())
               target.addChild(dropedNode);
          System.out.println(dropedNode + " wurde zur " + target + " eingefügt.");
          break;
        case ViewerDropAdapter.LOCATION_NONE:
          break;
        default:
          break;
      }
      super.drop(event);
      treeViewer.refresh(this.target.getParent(), true);
      treeViewer.refresh(parentDropedNode, true);
    }
  }

  @Override
  public boolean performDrop(Object data) {
    return true;
  }

  @Override
  public boolean validateDrop(Object target, int operation,
                              TransferData transferType) {
    if (target instanceof TreeNode) {
      this.target = (TreeNode) target;
      this.operation = operation;
      return true;
    } else {
      this.target = null;
      this.operation = 0;
      return false;
    }
  }
}

Wird die Anwendung ausgeführt, dann wird in der Standardkonsole die Position des einzufügenden Knotens angezeigt.
Dieser Artikel baut auf dem Artikel Eclipse Drag and Drop. von Lars Vogel

Keine Kommentare:

Kommentar veröffentlichen