Es funktioniert fast überall in den aktuellen Versionen der NetBeans IDE: Man kann in den Baumansichten die Elemente anklicken und mit gehaltener linker Maustaste an eine andere Position verschieben.
Wenn man das auch für die eigenen BeanTreeView-Komponenten haben will, muss man ein paar Methoden implementieren bzw. überschreiben, damit das funktioniert.
In diesem "Kochrezept" habe ich ein sehr einfaches Modell gewählt, nur einen Root-Node mit einer einfachen Hierachie. Der Root-Node kann sogar ausgeblendet werden.
Man erzeugt ein JPanel oder TopComponent und implementiert ExplorerManager.Provider. Die Methode getExplorerManager liefert einen Standard-ExplorerManager:
public class Test extends ToComponent
implements ExplorerManager.Provider {
private ExplorerManager em = new ExplorerManager();
public Test() {
super();
initComponenents();
getExplorerManager().setRootContextNode (new RootNode());
}
public ExplorerManager getExplorerManager() {
return em;
}
// ...
}
In initComponents() fügt man dieser Komponente z.B. einem BeanTreeView hinzu, damit man überhaupt etwas von diesem Beispiel hat.
Nun benötigen wir die Klasse RootNode, die von AbstractNode abgeleitet wird und ein spezielles Children-Object an AbstractNode weiterreicht. Dies ist die erste Vorraussetzung, damit die untergeordneten Nodes überhaupt eine benutzerdefinierte Reihenfolge erhalten:
public class RootNode extends AbstractNode {
public RootNode() {
super (new RootChildren());
}
}
Zwar ist RootNode noch nicht fertig, aber werfen wir mal einen Blick auf RootChildren:
public class RootChildren extends Index.ArrayChildren{
public RootChildren() {
super();
}
}
Natürlich hätte oben in RootNode auch ein super (new Index.ArrayChildren()); gereicht (zumal RootChildren schon fertig ist), aber wenn man das für eigene Projekte erweitern will, ist es besser man hat eine größere Kontrolle über das Children-Modell.
Index.ArrayChildren implementiert alle Verwaltungsaufgaben, um Positionen von Knoten zu managen. Das Drag and Drop UI der Views benötigen diese Implementation, um die Knoten anhand von Anwenderaktionen anzuordnen.
Bis jetzt wurde also erst die Wurzel des Baumes definiert, und wie die untergeordneten Knoten im Container (Index.ArrayChildren) dieser RootNode gehalten werden.
Jetzt also zu den ChildNodes:
public class ChildNode extends AbstractNode {
private InstanceContent content;
public ChildNode (NodeData data) {
this (data, new InstanceContent());
}
public ChildNode (NodeData data, InstanceContent content) {
super (Children.LEAF, new AbstractLookup (content));
this.content = content;
content.add(data);
}
public NodeData getData() {
return getLookup().lookup(NodeData.class);
}
}
Die obigen Konstruktoren entsprechen meinem Lieblingsaufbau von Nodes, die nur im Arbeitsspeicher gehalten werden. Das ist keine Vorraussetzung, um die Knoten per Drag and Drop zu verschieben.
Allerdings muss nun noch folgende Methode ChildNode hinzugefügt werden:
@Override
public boolean canCut() {
return true;
}
Wenn ChildNode bei canCut immer false zurückgibt (und das ist in AbstractNode so festgelegt), funktioniert schon der erste Drag-Schritt nicht. In NetBeans wird Drag and Drop im Prinzip als Cut and Paste abgebildet.
Damit wir später auch die Nodes unterscheiden können, implementieren wir getName() neu:
@Override
public String getName() {
return getData().getName();
}
Um die Sache lauffähig zu machen, brauchen wir noch NodeData:
public class NodeData {
private String name;
public NodeData (String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Jetzt können wir endlich unsere Knoten im Constructor von der Klasse Test anlegen:
public Test() {
super();
initComponenents();
Node root = new RootNode();
getExplorerManager().setRootContextNode (root);
root.getChildren().add (new Node[] {
new ChildNode (new NodeData ("Erster Knoten")),
new ChildNode (new NodeData ("Zweiter Knoten")),
new ChildNode (new NodeData ("Dritter Knoten")),
new ChildNode (new NodeData ("Letzter!!")),
});
}
Zwar wird es nicht gern gesehen die add()-Methode zu verwenden, im Falle von Index.Children und diesem Test ist es aber durchaus legitim.
Wenn man nun daraus ein fertiges Beispielprogramm gebastelt hat, wird man feststellen, dass das UI schon grundsätzlich reagiert. Man kann die Knoten per DnD ziehen und dabei zuschauen, wie das BeanTreeView am Zielort eine passende "Einfügemarke" anzeigt.
Tja, aber das Fallenlassen funktioniert noch nicht. Der Mauszeiger straft uns auch mit einem Verbotszeichen.
Damit das Drag and Drop schlussendlich wirklich funktioniert, muss man dem BeanTreeView über das Cookie-Set des übergeordneten Knotens den Children-Container bekanntgeben. In diesem Fall ist der übergeordnete Knoten natürlich unsere RootNode-Klasse. Dort fügen wir folgende Methode noch ein:
@Override
public Cookie getCookie (Class clazz) {
Children ch = getChildren();
if (clazz.isInstance(ch)) {
// hier den Children-Container bekannt machen:
return (Cookie) ch;
}
return super.getCookie(clazz);
}
Das hätte AbstractNode auch selber machen können...
Jetzt funktioniert es endlich auch mit dem Drop-Teil von Drag and Drop.
Hier nochmal die Zusammenfassung:
Der Children-Container muss ein Index-ArrayChildren sein, damit benutzerdefinierte Positionen festgelegt werden können. Ansonsten wären die Nodes nur nach festen Kriterien sortiert.
Damit der Drag-Teil überhaupt startet, muss in den Nodes canCut überschrieben werden und true zurückgeben.
Und für den Drop-Teil muss das View (hier BeanTreeView) auf den Children-Container zugreifen können, um anhand des Interfaces überhaupt ermitteln zu können, dass Nodes angeordnet werden können.
Das war es auch schon. Viel Spaß mit dem Kochrezept und dem Ergebnis eines modernen UIs.
Beste Grüße,
Josch.