The Swing-based window system in the NetBeans Platform has the concept of a window that is loaded at runtime. These windows derive from the class TopComponent and are registered using annotations, and are created using a handy wizard in the NetBeans IDE. Today I am going to show you how to do something similar with JavaFX components, using the default lookup of the NetBeans platform. My aim is to load an FXML file and display it on a tabbed pane.
I am using the source code from my previous blog entry as a starting point because I already had it handy. That main fxml file already had a tabPane, which is what I require in the GUI.
I created a package called za.co.pellissier.javafxwindowsystem.api where the public API of my JavaFXWindowSystem will be located. To be able to add a component to the tabbed pane, the text to be displayed on the tab as well as a javafx.scene.Node object is needed. So I created an interface that windows will need to implement:
[java]package za.co.pellissier.javafxwindowsystem.api;
import javafx.scene.Node;
public interface IJavaFXWindow {
public Node load();
String getWindowName();
}[/java]
There is a class called FXMLLoader that is able to load an fxml file and return a Node object representing its content. To save future implementers of this API some effort, here is an abstract class that handles the loading:
[java]package za.co.pellissier.javafxwindowsystem.api;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
public abstract class AJavaFXWindow implements IJavaFXWindow {
private final String fxmlFileName;
public AJavaFXWindow(String fxmlName) {
fxmlFileName = fxmlName;
}
@Override
public Node load() {
try {
URL resource = getClass().getResource(fxmlFileName);
return (Node) FXMLLoader.load(resource);
} catch (IOException ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}[/java]
That is all that there is to the API, for now. 🙂
Note: Remember to export the package (set it to public) if you want it to be visible to other modules!
So lets look at how it is used by the window system. In my MainWindowController class (the JavaFX controller for the main fxml file), I added this method:
[java] private void loadWindows() {
Collection extends IJavaFXWindow> windows =
Lookup.getDefault().lookupAll(IJavaFXWindow.class);
for (IJavaFXWindow window : windows) {
Node win = window.load();
String name = window.getWindowName();
tabPane.getTabs().add(new Tab(name, win));
}
}[/java]
I called this method in the initialize() method after the menu items are loaded.
All that remains is to see how a new window can be added to the system. First, lets create a new module called JavaFXTestWindow to contain the new window. It needs dependencies on Lookup API and JavaFXWindowSystem (the module where the API is defined).
In the JavaFXTestWindow module, I created a new fxml file called testwindow.fxml using SceneBuilder, and generated a controller class using the NetBeans IDE. Now for the most important step… implementing that brand new API!
[java]package za.co.pellissier.testwindow;
import org.openide.util.lookup.ServiceProvider;
import za.co.pellissier.javafxwindowsystem.api.AJavaFXWindow;
import za.co.pellissier.javafxwindowsystem.api.IJavaFXWindow;
@ServiceProvider(service = IJavaFXWindow.class)
public class MyWindow extends AJavaFXWindow implements IJavaFXWindow {
public MyWindow() {
super(“testwindow.fxml”);
}
@Override
public String getWindowName() {
return “Test Me!”;
}
}[/java]
The most important line is the @ServiceProvider annotation – if you leave that out, the default lookup will not be able to find the window!
After cleaning and building, here is the result!
There are certainly still improvements to be made to this implementation. It only emulates a single mode in the NetBeans Platform Window System. And it requires the user to manually create an additional class. But it is a good start! 🙂