sven

5 posts
Tired of JavaFX Scene Builder being run in a separate process? Fed up with no real integration between your favorite IDE and JavaFX Scene Builder? There may be a solution heading towards you. Follow this small series of blog entries to join me on my journey towards an embedded JavaFX Scene Builder in NetBeans.  Welcome back (you did read the first three parts of this series?)! Ok, having done a bit of magic to Scene Builder 2.0 in the first two part of this series, let's go for some more integration. Goal of this part is to get change detection and undo/redo support working. Change detection is important to get the "Save" feature of NetBeans to work. Because the integration is actually based on reusing the fxml as a FXMLDataObject it is necessary to detect a change done in the Scene Builder and apply the result back to the FXMLDataObject. But how to detect the change. This can be done using the observable propertyrevision from the JobManager which in turn can be retrieved from the EditorController. TheJobManager tracks every action applied to the scene as a "Job" and increments the revision with each change. With this change detection can be done with the following code fragmenteditorController.getJobManager().revisionProperty().addListener((ov, oldValue, newValue) -> { try { String updatedFXMLText = editorController.getFxmlText(); EditorCookie editorCookie = dao.getLookup().lookup(EditorCookie.class); editorCookie.getDocument().remove(0, editorCookie.getDocument().getLength()); editorCookie.getDocument().insertString(0, updatedFXMLText, null); } catch (BadLocationException ex) { Exceptions.printStackTrace(ex); } }); This just reads the complete FXML as a pure text from Scene Builder and puts it back into the document managed by NetBeans. This triggers a change on the FXMLDataObjectside of the world and the save logic is active. Knowing about theJobManager makes undo/redo integration quite simple. What we need from NetBeans perspective is an UndoRedo implementation, which is returned from the appropriate method of our SBFxmlMultiViewElement @Override public UndoRedo getUndoRedo() { return null != undoRedo ? undoRedo : UndoRedo.NONE; } What about the required implementation? Looking at the API's of UndoRedo andJobManager they are quite similiar. So just creating aSceneBuilderUndoRedoBridge should be fairly simple:public class SceneBuilderUndoRedoBridge implements UndoRedo { private final JobManager manager; private final ChangeSupport changeSupport = new ChangeSupport(this); SceneBuilderUndoRedoBridge(JobManager manager) { this.manager = manager; this.manager.revisionProperty().addListener((observable) -> { EventQueue.invokeLater(() -> { changeSupport.fireChange(); }); }); } @Override public boolean canUndo() { return manager.canUndo(); } @Override public boolean canRedo() { return manager.canRedo(); } @Override public void undo() throws CannotUndoException { Platform.runLater(()-> manager.undo()); } @Override public void redo() throws CannotRedoException { Platform.runLater(()-> manager.redo()); } @Override public void addChangeListener(ChangeListener cl) { changeSupport.addChangeListener(cl); } @Override public void removeChangeListener(ChangeListener cl) { changeSupport.removeChangeListener(cl); } @Override public String getUndoPresentationName() { return manager.getUndoDescription(); } @Override public String getRedoPresentationName() { return manager.getRedoDescription(); } } Now just make sure that the new UndoRedo support is availble by adding just one more line of code to thegetVisualRepresentation()method ofSBFxmlMultiViewElement undoRedo = new SceneBuilderUndoRedoBridge(editorController.getJobManager());That's it - change detection and undo/redo integration working! I am sure the code can be improved - just let me know and create a pull request ;-) Now you ask - can I download this magic plugin somewhere? The answer is simple: Get it from the NetBeans Plugin Portal by downloading it from here or install it via your Plugin Manager from inside NetBeans. What you will need in addition to the plugin is a fairly recent (latest) JDK8_u20 ea build. There are some issues with DnD and JavaFX-Swing-Integration which have been fixed in latest builds. Stay tuned for the next part of this series, showing .. whatever I come up with next. Any feature requests? Head over to NbSceneBuilderand let me know.  
Tired of JavaFX Scene Builder being run in a separate process? Fed up with no real integration between your favorite IDE and JavaFX Scene Builder? There may be a solution heading towards you. Follow this small series of blog entries to join me on my journey towards an embedded JavaFX Scene Builder in NetBeans.  Welcome back (you did read the first two parts of this series?)!  Ok, having done a bit of magic to Scene Builder 2.0 ea in the first two part of this series, let's go for some more integration. Goal of this part is to get the inspector view, the css panel and the library panel from Scene Builder integrated - looking like this
To achieve this we create a new TopComponent for each panel to show. The major thing is trying to get a reference to the EditorController via the selected Node. The following example code registers a listener so that lookup changes will be propagated to our TopComponent.     @Override     public void componentOpened() {         nodeResult = Utilities.actionsGlobalContext().lookupResult(Node.class);         LookupListener nodeLkpL = (event) -> {             final Optional<? extends Node> optionalNode = nodeResult.allInstances().stream().findFirst();             if (optionalNode.isPresent()) {                 editorControllerResult = optionalNode.get().getLookup().lookupResult(EditorController.class);                 resultChanged(new LookupEvent(editorControllerResult));             }         };         nodeResult.addLookupListener(nodeLkpL);         nodeLkpL.resultChanged(new LookupEvent(nodeResult));     } Now that we are aware of changing EditorControllers - what to do with the EditorController retrieved from the lookup? We use is to create our own special panel via e.g. the LibraryController and add it to the TopComponent. If there is no EditorController anymore, only a message is shown, that there is actually no Scene Builder content availble to which this TopComponent can attach to.     @Override     public void resultChanged(LookupEvent le) {             final Optional optionalController = editorControllerResult.allInstances().stream().findFirst();         if (optionalController.isPresent()) {             Platform.runLater(() -> {                 LibraryPanelController h = new LibraryPanelController(optionalController.get());                 final BorderPane pane = new BorderPane();                 pane.setCenter(h.getPanelRoot());                 Scene scene = new Scene(pane);                 jfxPanel.setScene(scene);             });         } else {             Platform.runLater(() -> {                 Label label = new Label ("No Scene Builder Content");                 BorderPane borderPane = new BorderPane();                 borderPane.setCenter(label);                 Scene scene = new Scene(borderPane);                 jfxPanel.setScene(scene);             });         }     } All panels we want to show can be implemented using this strategy.  All of them follow the pattern
  • create the according controller
  • call controller.getRootPanel()
  • add the pane to a layout pane
  • add the layout pane as root to the scene
I am sure the code can be improved - just let me know and create a pull request ;-)  Now you ask - can I download this magic plugin somewhere? The answer is simple: Get it from the NetBeans Plugin Portal by downloading it from here or install it via your Plugin Manager from inside NetBeans.  What you will need in addition to the plugin is a fairly recent (latest) JDK8_u20 ea build. There are some issues with DnD and JavaFX-Swing-Integration which have been fixed in latest builds.  Stay tuned for the next part of this series, showing how to get change detection and undo/redo support integrated.

Tired of JavaFX Scene Builder being run in a separate process? Fed up with no real integration between your favorite IDE and JavaFX Scene Builder? There may be a solution heading towards you. Follow this small series of blog entries to join me on my journey towards an embedded JavaFX Scene Builder i ]]> Welcome back (you did read the first part of this series?)! Ok, having done a bit of magic to Scene Builder 2.0 ea in the last part of this series, let's go for some more integration. Goal of this part is to get the hierarchy view from Scene Builder running inside the NetBeans navigator view- looking like this

First thing you will need is to publicize the EditorController so it will be available in the global lookup and can be used from the upcoming NavigatorPanel. The following code does this. private MultiViewElementCallback callback; private DataObject dao; private EditorController editorController; private Lookup lkp; private InstanceContent ic; private JFXPanel jfxPanel; public SBFxmlMultiViewElement(Lookup lookup) { Platform.setImplicitExit(false); dao = lookup.lookup(DataObject.class); assert dao != null; ic = new InstanceContent(); lkp = new AbstractLookup(ic); } @Override public JComponent getVisualRepresentation() { if (null == jfxPanel) { jfxPanel = new JFXPanel(); try { final File fxmlFile = FileUtil.toFile(dao.getPrimaryFile()); final String fxmlText = readContentFromFile(fxmlFile); final URL fxmlLocation = fxmlFile.toURI().toURL(); Platform.runLater(new Runnable() { @Override public void run() { try { editorController = new EditorController(); Node node = new AbstractNode(Children.LEAF, Lookups.fixed(editorController)); ic.add(node); ContentPanelController contentPanelController = new ContentPanelController(editorController); final BorderPane pane = new BorderPane(); pane.setCenter(contentPanelController.getPanelRoot()); Scene scene = new Scene(pane); jfxPanel.setScene(scene); editorController.setFxmlTextAndLocation(fxmlText, fxmlLocation); } catch (IOException ex) { Exceptions.printStackTrace(ex); } } }); } catch (IOException ex) { Exceptions.printStackTrace(ex); } } return jfxPanel; } @Override public Lookup getLookup() { return lkp; } Since we do not want the Scene Builder navigator view to show up in the normal XML based MultiViewElement, it is necessary to create a replacment NavigatorLookupHint. The following code (living in the same file as above code) creates a hint for the a synthetic content type called "text/scenebuilder" - I am sure there is a better content type name (names are hard), but it does what is necessary. class SBTypeLookupHint implements NavigatorLookupHint { public String getContentType () { return "text/scenebuilder"; } } Now add an instance of this hint to the lookup. Change the constructor as follows: public SBFxmlMultiViewElement(Lookup lookup) { Platform.setImplicitExit(false); dao = lookup.lookup(DataObject.class); assert dao != null; ic = new InstanceContent(); ic.add(new SBTypeLookupHint()); lkp = new AbstractLookup(ic); } Based on this we can now create the NavigatorPanel @NavigatorPanel.Registration(displayName = "Hierarchy", mimeType = "text/scenebuilder") public class FxmlNavigator implements NavigatorPanel, LookupListener { private JFXPanel panel = new JFXPanel(); private Lookup lkp; private InstanceContent ic; private Lookup.Result editorControllerResult; public FxmlNavigator() { ic = new InstanceContent(); lkp = new AbstractLookup(ic); } @Override public String getDisplayName() { return "Hierarchy"; } @Override public String getDisplayHint() { return "Hierarchy Hint"; } @Override public JComponent getComponent() { return panel; } @Override public void panelActivated(Lookup lkp) { editorControllerResult = lkp.lookupResult(EditorController.class); editorControllerResult.addLookupListener(this); resultChanged(new LookupEvent(editorControllerResult)); } @Override public void panelDeactivated() { } @Override public Lookup getLookup() { return lkp; } @Override public void resultChanged(LookupEvent le) { final Optional optionalController = editorControllerResult.allInstances().stream().findFirst(); if (optionalController.isPresent()) { Platform.runLater(new Runnable() { @Override public void run() { HierarchyPanelController h = new HierarchyPanelController(optionalController.get()); final BorderPane pane = new BorderPane(); pane.setCenter(h.getPanelRoot()); Scene scene = new Scene(pane); panel.setScene(scene); } }); } } } I am sure the code can be improved - just let me know and create a pull request ;-) Now you ask - can I download this magic plugin somewhere? The legal disclaimer from Oracle keeps me from redistributing the necessary SceneBuilderKit.jar. Sorry, you have to do it on your own. Get the sources from Bitbucket and try it out. The one thing you have to do is get Scene Builder 2.0 Early Access and put SceneBuilderKit.jar into the right directory (hint there is a file indicating, which one it is). Stay tuned for the next part of this series, showing further integration steps - maybe palette or property sheet.

Tired of JavaFX Scene Builder being run in a separate process? Fed up with no real integration between your favorite IDE and JavaFX Scene Builder? There may be a solution heading towards you. Follow this small series of blog entries to join me on my journey towards an embedded JavaFX Scene Builder in NetBeans.  Finally, with the new developer preview builds of JavaFX Scene Builder 2.0 a new important change was introduced. The new version contains the so called "JavaFX Scene Builder Kit". The Release notes state that "JavaFX Scene Builder Kit is an API that allows the integration of Scene Builder panels and functionalities directly into the GUI of a larger application, or a Java IDE, such as NetBeans, IntelliJ, and Eclipse."   Having read this during Devoxx, I started looking into the  documentation how to get started with the integration. Really excited about the possibilities I fired up my IDE and started throwing together some lines of code to just open up the main view of JavaFX SceneBuilder inside NetBeans. After fiddling a bit with the threading (a lot of things still have to be executed on the JavaFX Thread) I finally got to the point where the view should open. Yes should - bummer some exception "No such Method" was blocking me from seeing the result.  I asked about this problem on the OpenJFX mailing list (remember to join, if you are interested in JavaFX - it is a great source for information on what is going on). I got the confirmation that my JDK 8 ea build was too new and due to a small API change incompatible with Scene Builder 2.0 Early Access. So some more waiting....   Finally, I  got pinged a couple of days ago, that with a new build of the developer peview this problem should now be gone. Great! So back to where I left the prototype, some more fiddling and I had my first TopComponent showing the JavaFX Scene Builder main view.   But somehow this still was a bit of a mess, so I decided to start going the full nine yards. NetBeans has some nice feature, it is called a MultiView. It allows you to register special views called MultiViewElement's for the same file type. So I registered my completely rewritten dirty prototype code as a MultiViewElement - and (drumroll) - see the result
The necessary code is quite simple (once you figure out some of the details ;-) ) @MultiViewElement.Registration(displayName = "#LBL_SceneBuilder",         mimeType = "text/x-fxml+xml",         persistenceType = TopComponent.PERSISTENCE_NEVER,         preferredID = "SceneBuilder",         position = 3000) @NbBundle.Messages({     "LBL_SceneBuilder=Visual" }) public class SBFxmlMultiViewElement implements MultiViewElement {      private MultiViewElementCallback callback;     private DataObject dao;      public SBFxmlMultiViewElement(Lookup lkp) {         dao = lkp.lookup(DataObject.class);         assert dao != null;     }      @Override     public JComponent getVisualRepresentation() {         final JFXPanel jfxPanel = new JFXPanel();         try {             final File fxmlFile = FileUtil.toFile(dao.getPrimaryFile());             final String fxmlText = readContentFromFile(fxmlFile);             final URL fxmlLocation = fxmlFile.toURI().toURL();             Platform.runLater(new Runnable() {                  @Override                 public void run() {                     try {                         final EditorController editorController = new EditorController();                         ContentPanelController contentPanelController = new ContentPanelController(editorController);                         final BorderPane pane = new BorderPane();                         pane.setCenter(contentPanelController.getPanelRoot());                         Scene scene = new Scene(pane);                         jfxPanel.setScene(scene);                         editorController.setFxmlTextAndLocation(fxmlText, fxmlLocation);                     } catch (IOException ex) {                         Exceptions.printStackTrace(ex);                     }                 }             });           } catch (IOException ex) {             Exceptions.printStackTrace(ex);         }         return jfxPanel;     }      @Override     public JComponent getToolbarRepresentation() {         return new JPanel();     }      @Override     public Action[] getActions() {         return new Action[]{};     }      @Override     public Lookup getLookup() {         return Lookup.EMPTY;     }      @Override     public void componentOpened() {     }      @Override     public void componentClosed() {     }      @Override     public void componentShowing() {     }      @Override     public void componentHidden() {     }      @Override     public void componentActivated() {     }      @Override     public void componentDeactivated() {     }      @Override     public UndoRedo getUndoRedo() {         return UndoRedo.NONE;     }      @Override     public void setMultiViewCallback(MultiViewElementCallback callback) {         this.callback = callback;     }      @Override     public CloseOperationState canCloseElement() {         return CloseOperationState.STATE_OK;     }      private static String readContentFromFile(File file) throws IOException {         final byte[] buffer;          buffer = new byte[(int) file.length()];         try (DataInputStream is = new DataInputStream(new FileInputStream(file))) {             is.readFully(buffer);         }          return new String(buffer, Charset.forName("UTF-8"));     }  } Now you ask - can I download this magic plugin somewhere? The legal disclaimer from Oracle keeps me from redistributing the necessary SceneBuilderKit.jar. Sorry, you have to do it on your own. Get the sources from Bitbucket and try it out. The one thing you have to do is  get Scene Builder 2.0 Early Access and put SceneBuilderKit.jar into the right directory (hint there is a file indicating, which one it is).   Stay tuned for the next part of this series, showing how to get the navigation integrated.

Creating a JavaFX 2 (or should I say FX8) application can be a time consuming task. We all got used to powerful frameworks helping us creating such applications based on Swing (NetBeans Platform) or SWT (Eclipse). There even was an attempt at getting this standardized in Java for Swing - JAF anyone? So where is such an application framework based on JavaFX 2/8, which leverages all the nice things from Java7 and 8?

Stop looking - there is help out there. A small project called eFX(looking for a better name - suggestions anyone?) tries to put together at least some building blocks, to help create complex JavaFX applications, things like:

  • A simple docking framework (no drag and drop)
  • Tool bar and menu bar support
  • Simple statusline
  • ....

This post is the start of a series in which I will try to describe the major available features, how they work, what is missing. To whet your appetite this is how a menu bar registration works in eFX

 @HandlerID(category = "File", id = "org.efx.windows.about.AboutHandler") @HandlerRegistration(displayName = "#CTL_AboutHandler") @HandlerReference(path = "fx/Menu/Help", position = 10000) @Messages("CTL_OpenHandler=About") public class AboutHandler implements EventHandler { ... } 

Simple? Does that feel familiar? Guess how tool bar registration works! So stay tuned to get to know more about eFX... If you want to see a working prototype application based on eFX visit vmFX