Geertjan is a DZone Zone Leader and has posted 459 posts at DZone. You can read more from them at their website. View Full User Profile

How to Integrate JavaFX into a NetBeans Platform Explorer View

04.28.2013
| 3452 views |
  • submit to reddit

In the NetBeans Platform, "explorer view" has a very specific meaning. It is described in some detail here. In summary, an explorer view displays one or more Nodes. (And a Node is a visualization of an Object.) Since all explorer views work with Nodes, changing from one view to another doesn't require the model (i.e., the Node) to be rewritten, unlike in standard Swing where each UI component has its own model. The most popular explorer views are BeanTreeView and OutlineView.

Toni and I worked on creating a custom explorer view based on the Visual Library, all the way back in 2009, as described here. Using those insights, I created a custom explorer view on top of a JavaFX TreeView, as shown here:

Here's how, mostly using code from the TreeView link above. You can copy the class below and paste it into a module and then use it exactly as you use a BeanTreeView. Wherever you're adding "new BeanTreeView()" to a TopComponent, you can now add "new JFXTreeView()". However, two arguments need to be passed in: the ExplorerManager and a String for the display text of the root node.

In the code below, the call "ExplorerManager.find(this)" is very important. What happens here is that the component (which in this case is the JFXPanel) asks its parent (which is in this case the TopComponent where I added it) if it implements ExplorerManager.Provider. If so, the root Node of the ExplorerManager is obtained, from where the child Nodes are obtained, from which the Lookup can be used, etc. So, via the ExplorerManager all the data is available to the JavaFX TreeView.

public class JFXTreeView extends JFXPanel {

    ExplorerManager em;
    private String rootDisplayName;

    public JFXTreeView(ExplorerManager em, String rootDisplayName) {
        this.em = em;
        this.rootDisplayName = rootDisplayName;
    }

    @Override
    public void addNotify() {
        super.addNotify();
        em = ExplorerManager.find(this);
        if (em != null) {
            Node root = em.getRootContext();
            TreeItem<String> rootItem = new TreeItem<String>(rootDisplayName);
            rootItem.setExpanded(true);
            for (Node node : root.getChildren().getNodes()) {
                TreeItem<String> item = new TreeItem<String>(
                   node.getDisplayName());
                rootItem.getChildren().add(item);
            }
            final TreeView<String> treeView = new TreeView<String>(rootItem);
            treeView.setEditable(true);
            treeView.setCellFactory(
                  new Callback<TreeView<String>, 
                  TreeCell<String>>() {
                @Override
                public TreeCell<String> call(TreeView<String> p) {
                    return new TextFieldTreeCellImpl();
                }
            });
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    StackPane root = new StackPane();
                    root.getChildren().add(treeView);
                    Scene scene = new Scene(root, 300, 250);
                    setScene(scene);
                }
            });
        }
    }

    private final class TextFieldTreeCellImpl extends TreeCell<String> {
        private TextField textField;
        @Override
        public void startEdit() {
            super.startEdit();
            if (textField == null) {
                createTextField();
            }
            for (Node node : em.getRootContext().getChildren().getNodes()) {
                if (node.getLookup().lookup(Person.class).getName().
                    equals(textField.getText())) {
                    try {
                        em.setSelectedNodes(new Node[]{node});
                    } catch (PropertyVetoException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            }
            setText(null);
            setGraphic(textField);
            textField.selectAll();
        }
        @Override
        public void cancelEdit() {
            super.cancelEdit();
            setText((String) getItem());
            setGraphic(getTreeItem().getGraphic());
        }
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(getTreeItem().getGraphic());
                }
            }
        }
        private void createTextField() {
            textField = new TextField(getString());
            textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
                @Override
                public void handle(KeyEvent t) {
                    if (t.getCode() == KeyCode.ENTER) {
                        commitEdit(textField.getText());
                    } else if (t.getCode() == KeyCode.ESCAPE) {
                        cancelEdit();
                    }
                }
            });
        }
        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }
    }

}

Note: Take note of the "startEdit" method, where the selected node in the ExplorerManager is changed when the double-click on an item in the tree takes place. In that way, the selected Node always matches an item in the JavaFX TreeView, so that other parts of the application, such as the Properties Window and Actions, are updated automatically whenever the user clicks a different item. That's the way the JavaFX world can interact with the NetBeans Platform world, via the ExplorerManager, which is the controler of the Node hierarchy.

Finally, the next topic I'm working on, but that could take more time, is the window system, that is, replacing the Swing tabbed pane with a JavaFX equivalent.

Published at DZone with permission of its author, Geertjan Wielenga.