Česky   |  Deutsch   |  English   |  Español   |  Français   |  Indonesia   |  日本語   |  한글   |  Polski   |  Português (BR)   |  Türkçe   |  中文   |  正體中文   |  Your Language  
PlanetNetbeans
Planet NetBeans ist eine Sammlung aller NetBeans-relevanten Gedanken aus der ganzen Blogosphäre.
Feeds
[RSS 1.0 Feed] [RSS 2.0 Feed]
[FOAF Subscriptions] [OPML Subscriptions]
Feed Abonnenten

Powered by:    Planet

Last updated:
July 05, 2008 10:11 PM
All times are UTC

sponsored by Sun Microsystems

visit NetBeans website
JNBB - Joschs NetBeans Blog - July 02, 2008 07:23 AM
BeanDev: Branding - Shortcuts oder Keymap?

Der deklarative Ansatz, die NetBeans-Plattform zu konfigurieren ist sehr praktisch, hat aber auch so seine Tücken. Der Vorteil ist unbestreitbar. Ohne die compilierten Class-Dateien anfassen zu müssen, kann man in XML- und Bundle-Dateien alle "Branding"-Arbeiten durchführen. Allerdings gibt es inzwischen recht verstreute Einrichtungsmöglichkeiten und sind teilweise auch als historisch zu bezeichnen.

Ein Bereich sind die Tastenkürzel, die man Aktionen der NetBeans-Oberfläche zuweisen kann. Ein populärer Bereich ist, in der layer.xml, der Shortcuts-Folder.

Zunächst benötigt man natürlich eine Action-Klasse, die selbst im Actions-Abschnitt deklariert sein sollte:

<filesystem>
    <folder name="Actions">
        <folder name="Window">
            <file name="de-sepix-demo-QueryAction.instance"/>
        </folder>
    </folder>

Nun kann man im Shortcuts-Folder ein Tastenkürzel zuweisen:

    <folder name="Shortcuts">
        <file name="CS-Q.instance­">
            <attr name="instanceClass­"
                stringvalue="de.sepix.demo.QueryAction"/>
        </file>
    </folder>

Somit ist Strg+Umschalt+Q der Aktion "QueryAction" zugewiesen. Die eingentümliche Syntax für das Tastenkürzel wird in Utilities.stringToKey beschrieben.

Allerdings hat dieses Verfahren einige Nachteile. Das Tastenkürzel ist global einmalig zugewiesen (kann also nicht Tastatur-Profilen zugewiesen werden) und wird für den Anwender nicht sichtbar im Menü angezeigt. 

Wenn man nämlich die QueryAction (hier z.B.) im Window-Menü einfügt:

    <folder name="Menu">
        <folder name="Window">
            <file name="QueryAction.shadow">
                <attr name="originalFile"
                    stringvalue=
                        "Actions/Window/de-sepix-demo-QueryAction.instance"/>
            </file>

erscheint zwar der Menüeintrag, aber ohne die Tastenkürzel-Information, dass der Anwender die Eintrag auch per Strg+Umschalt+Q aufrufen kann.

Eine schönere Methode ist es, dem Anwender die Tastenkürzel über Keymaps zur Verfügung zu stellen. Damit kann der Nutzer die Tastenkürzel bequem im Options-Dialog verwalten und sieht diese auch immer in den Menüs zugewiesen:

    <folder name="Keymaps">
        <folder name="Sepix">
            <file name="CS-Q.shadow">
                <attr name="originalFile"
               stringvalue="Actions/Window/de-sepix-demo-QueryAction.instance"/>
            </file>

          
Nun erscheint automatisch im Options-Dialog zusätzlich zur NetBeans-Keymap auch die Sepix-Keymap.

Aber der Anwender muss erst explizit die Keymap "Sepix" auswählen. Aber auch das kann man vorgeben:

    <folder name="Keymaps">
<attr name="currentKeymap" stringvalue="Sepix"/>
        <folder name="Sepix">
            <file name="CS-Q.shadow">

Mit dem Attribut "currentKeymap" wird die Standard-Keymap vorbelegt.

Will man beim Brandig die NetBeans-Keymap vor dem Anwender verstecken, nutzt man dafür das Standard-Verfahren:

    <folder name="Keymaps">
<folder name="NetBeans_hidden"/>
<attr name="currentKeymap" stringvalue="Sepix"/>
        <folder name="Sepix">
            <file name="CS-Q.shadow">

Mit diesem Verfahren kann man für den Anwender bequem zu verwaltende Tastaturkürzel einrichten.

Beste Grüße,
  Josch.

 

JNBB - Joschs NetBeans Blog - June 27, 2008 06:05 AM
BeanDev: NodeAction und Selection Management

Eine nette Klasse, um Nodes mit Aktionen zu versehen ist die NodeAction-Klasse. Und soweit man sich an die Standard-Tutorials hält, wird man erstmal keine Probleme haben - insbesondere wenn sich alles in einem View innerhalb TopComponent läuft.

NodeAction funktioniert aber nur, wenn das zugeordnete Modell des ExplorerManager mit dem Selection Management der NetBeans Plattform verknüpft ist. Dabei ist es letztendlich irrelevant, ob z.B. ein BeanTreeView-Eintrag visuell markiert ist. Wenn der ExplorerManager von seiner Umwelt abgeschnitten ist, klappt es nicht mit einer NodeAction.

Sehr schnell bekommt man das mit, wenn man ein ExplorerManager und zugehöriges View in einer schlichten Panel-Komponente hat. Zwar funktioniert alles erstmal wie erwartet, sogar NodeActions die per

  @Override
  public Action[] getActions(boolean context) {
    return new Action[] {myNodeAction}
  }

an einen Knoten gebunden werden, zeigen sich im Popup, sind aktiv und werden sogar ausgeführt.

Leider stellt man dann im Debugger fest, dass das übergebene Node-Array in der performAction-Methode immer leer ist.

    @Override
    protected void performAction(Node[] nodes) {
      for (Node node : nodes) {
        if ( node.canDestroy() ) {
          Node parent = node.getParentNode();
          parent.getChildren().remove(new Node[]{node});
        }
      }
    }

Obige performAction-Methode würde nie etwas ausführen.

Wie bekommt man aber nun NodeAction dazu, zu erkennen, welche Knoten im ExplorerManager ausgewählt wurden?

Hier eine Möglichkeit:

Das Panel sollte das Interface Lookup.Provider implementieren und, ähnlich wie in den Tutorials zu TopComponents, ein Lookup (generiert aus einem ExplorerManager) zurückgeben:

public class MyTestPanel extends JPanel 
          implements ExplorerManager.Provider, Loopup.Provider {
  private ExplorerManager myNodeModel = new ExplorerManager();
  private BeanTreeView tree = new BeanTreeView();
  private Lookup lookup;
  public MyTestPanel () {
    initComponents();
    myNodeModel = new ExplorerManager();
    lookup = ExplorerUtils.createLookup(explorerManager, new ActionMap());
    myNodeModel .setRootContext(new MyRootNode());
  }

  public ExplorerManager getExplorerManager() {
    return myNodeModel;
  }

  public Lookup getLookup() {
    return lookup;
  }
  [...]
}

Das BeanTreeView benötigt das Interface ExplorerManager.Provider, ansonsten hat das View kein Model. Die Implementation von Lookup.Provider nutzen wir nun in der TopComponent, in der wir unser TestPanel einfügen:

public class TestTopComponent extends TopComponent {
  TestPanel tp = new TestPanel();
  public TestTopComponent() {
    add (tp);
    associateLookup(tp.getLookup()));
  }
}

Das war es auch schon, der Rest läuft automatisch. Die Methode associateLookup reicht das erzeugte Lookup aus unserem Panel durch die getLookup-Methode von TestTopComponent heraus (getLookup ist schon in TopComponent implementiert). 

Es wird aber die Regel sein, dass TopComponents eigene Lookups benötigen oder gar mehrere Panels mit Lookups nutzen. Dabei hilft aber ProxyLookup:

public class TestTopComponent extends TopComponent {
TestPanel tp = new TestPanel();
  InstanceContent content = new InstanceContent();
  public TestTopComponent() {
    add (tp);
    associateLookup(
      new ProxyLookup (
        new AbstractLookup (instanceContent),
        tp.getLookup())
    );
  }
}

Jetzt kann man selber in content seine Objekte speichern, die man global zur Verfügung stellen will und verliert nicht die Verbindung zu den selektierten Nodes in TestPanel.

Beste Grüße,
  Josch.

JNBB - Joschs NetBeans Blog - June 26, 2008 08:45 AM
BeanDev: Minimaler Einsatz für Drag and Drop von Nodes

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.