JavaFX software design - java

In a JavaFX application, javafx.application.Application must be subclassed, and the inherited launch() method, although it's public, must be called from within this derived class, otherwise an exception is thrown. The launch() method then uses reflection to instantiate the derived class, making it difficult to set values for the class members without losing them when launching. All that appears totally unusual to me, and I was wondering why starting a JavaFX application is so complicated, if that kind of software design (design pattern?) has a name, or if it's just bad design?
EDIT:
To be more specific, I want to use the observer pattern, so my java application gets notified when a document was loaded, like this:
public class MyDocumentLoader extends Application
{
private ChangeListener<Worker.State> changeListener;
public void setChangeListener(ChangeListener<Worker.State> changeListener)
{
this.changeListener = changeListener;
}
...
public void loadDocument(String url)
{
webEngine.getLoadWorker().stateProperty().addListener(changeListener);
webEngine.load(url);
}
...
}
I need the callback member in several methods, and ideally I can have more than one instances of the class that loads documents, so I can set different ChangeListeners for different URLs.

My guess is that this design was motivated by the (vast) number of Swing applications that were incorrectly written, with the "primary" JFrames being instantiated and shown on the wrong thread (i.e. not on the AWT event dispatch thread). My guess is that so many Swing applications were incorrectly written that they had to defensively code the framework against the incorrect usage, and that they wanted to avoid this scenario with JavaFX.
Forcing (well, almost forcing, there are hack-arounds) an FX Application to start this way makes it much harder to write an application incorrectly in a similar way. The launch method (and the equivalent Oracle JVM startup process if you have an Application subclass without a main method and a call to launch) does quite a bit of boilerplate work: it starts the FX toolkit, instantiates the Application subclass and calls its init() method, then on the FX Application Thread it instantiates the primary Stage and passes it to the Application subclass's start(...) method. This then ensures everything is running on the correct thread.
You should basically consider the start(...) method in a JavaFX application as the replacement for the main(...) method in a "traditional" Java application, with the understanding it is invoked on the FX Application Thread.
My recommendation is that the Application subclass should be as minimal as possible; it should just delegate to something else to actually create the UI, and then should just place it in the primary stage and show it. Include a main method that does nothing other than call launch(...) as a fallback for non-JavaFX-aware JVMs. You should only have one instance of one Application subclass present in any JVM. This way your Application subclass has no class members to set, and so the issues you describe simply don't arise.
If you use FXML, this is actually fairly natural: the start(...) method essentially just delegates to the FXML-controller pair to do the real work. If you don't use FXML, create a separate class to do the actual layout, etc, and delegate to it. See this related question which gets at the same kind of idea.
Note also that your statement
the inherited launch() method, although it's public, must be called
from within this derived class
is not entirely accurate, as there is an overloaded form of the launch(...) method in which you can specify the application subclass. So, if you really need, you can just create a stub for starting the FX toolkit:
public class FXStarter extends Application {
#Override
public void start(Stage primaryStage) {
// no-op
}
}
Now you can do:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Note that launch does not return until the FX toolkit shuts down, so it is imperative to put this call in another thread. This potentially creates race conditions, where you may try to do something needing the FX toolkit before launch(...) has actually initialized it, so you should probably guard against that:
public class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void init() {
latch.countDown();
}
#Override
public void start(Stage primaryStage) {
// no-op
}
}
and then
public class MyRegularApplication {
public static void main(String[] args) throws InterruptedException {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
// other stuff here...
}
}
SSCCE (I just used inner classes for everything so this is convenient to run, but in real life these would be standalone classes):
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BackgroundProcessDrivenApp {
public static void main(String[] args) throws InterruptedException {
Platform.setImplicitExit(false);
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
new MockProcessor().doStuff() ;
}
public static class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
#Override
public void init() {
latch.countDown();
}
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void start(Stage primaryStage) { }
}
public static class MockProcessor {
private final int numEvents = 10 ;
public void doStuff() {
Random rng = new Random();
try {
for (int event = 1 ; event <= numEvents; event++) {
// just sleep to mimic waiting for background service...
Thread.sleep(rng.nextInt(5000) + 5000);
String message = "Event " + event + " occurred" ;
Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
}
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} finally {
Platform.setImplicitExit(true);
}
}
}
public static class Messager {
private final String message ;
public Messager(String message) {
this.message = message ;
}
public void showMessageInNewWindow() {
Stage stage = new Stage();
Label label = new Label(message);
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
VBox root = new VBox(10, label, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
stage.setScene(scene);
stage.setAlwaysOnTop(true);
stage.show();
}
}
}

JavaFX supports a great number of deployment and packaging strategies, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, and having a standardized lifecycle entry- and exit-point simplifies supporting all these strategies.
If you are struggling to initialize your main application class, due to it being instanciated by the JavaFX launcher, your best option is to use the Application.init() and Application.stop() methods, as James_D points out.

Related

ScalaFX (or JavaFX) - Calling GUI from another main class multiple times

I am following on from a previous question: link
I am writing a standalone visualization package for a simulation package
The simulation package is written by one of our team in Scala
What I want to do, is create a matplotlib like package for this simulation package. My envisioned end use would look something like matplotlib:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
plt.plot([10, 20, 3000, 4121212])
plt.ylabel('some numbers')
plt.show()
For my package, I would do something analogous. Assume here that myFXPackage is a chart package written in ScalaFX. In my DRIVER class:
Import myFXPackage
// run some simulation code here...
myFXPackage.plot(results)
// run some MORE simulation code here...
myFXPackage.plot(results)
Now it seems that for ScalaFX, there can only be one entry point for the whole app; this is the JFXApp class. However, I want to import the package and simply run this code multiple times in my DRIVER class as shown above. So somehow how DRIVER class would call ScalaFX and run the plot, close it, then run another plot.
Is this feasible? If so how would I go about doing this?
Most JavaFX example code conflates the main method with the Application subclass, and in many cases even does the UI layout, etc., in the same class. There's not necessarily a reason to do this, and in the latter case it's not a particularly good design.
Assuming you separate concerns appropriately in your UI code, you might have a class like
public class MainUI {
private BorderPane root ;
public MainUI() {
root = new BorderPane();
// do layout, register event handlers, etc etc
}
public Pane getView() {
return root ;
}
}
Then if you want a standalone JavaFX application, you would do
public class JavaFxApp extends Application {
#Override
public void start(Stage stage) {
MainUI ui = new MainUI();
Scene scene = new Scene(ui.getView());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
But this isn't the only place you can use the UI class. In JavaFX (as in most UI toolkits), you must create a new stage, and perform other UI tasks, on the UI thread (i.e. the FX Application Thread).
Additionally, in JavaFX you must explicitly start up the FX toolkit. In the code above, this is done by the Application.launch() method.
JavaFX 9 introduced a Platform.startup(Runnable), which starts the FX toolkit and executes the supplied Runnable on the FX Application Thread.
So, using JavaFX 9 and later, you can have code like
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
// Since we don't want it to exit when we close a window, set
// implicit exit to false:
Platform.startup(() -> Platform.setImplicitExit(false));
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}
Prior to JavaFX 9, you can still do this, but you need to launch a "blank" application:
public class FXStartup extends Application {
#Override
public void start(Stage ignored) {
// probably need this:
Platform.setImplicitExit(false);
}
}
Note that the launch() method blocks until the toolkit exits, so your application needs to start it in another thread:
public class SomeApp {
public static void main(String[] args) {
// start FX toolkit
new Thread(() -> Application.launch(FXStartup.class, args)).start();
// do some other stuff...
// whenever you need to:
Platform.runLater(SomeApp::showStage);
// do other stuff...
}
private static void showStage() {
Scene scene = new Scene(new MainUI().getView());
Stage stage = new Stage();
stage.show();
}
}

How to handle an event from another class in JavaFX?

I'm trying to have a button in the Launcher class and make a handling function in another class as below. However, the handling function doesn't seem to work. Nothing printed.
I think the function button.setOnAction(anotherclass) is the cause. On some tutorials, they sai the parameter for setOnAction() is where I put the handling function at. So I put anotherclass there.
I know that I can just make a handling function in the same class or just use lambda. However, I'm trying to see if this way works.
public class Launcher extends Application{
public static Button button;
AnotherClass anotherclass;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage myStage) throws Exception {
button = new Button("Click me");
button.setOnAction(anotherclass);
StackPane layout = new StackPane();
layout.getChildren().add(button);
Scene scene = new Scene(layout, 300, 250);
myStage.setScene(scene);
myStage.show();
}
}
public class AnotherClass implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
if(event.getSource()== Launcher.button) {
System.out.println("print");
}
}
}
Can anyone help me make a handling function in a different class as the button's?
This would be a very strange design to have an entirely different class created for the sole purpose of handling one event, but it can be done. Just not the way you're trying to do it.
If you are set on using this separate AnotherClass to handle it, you just need to pass the class with the new keyword to actually instantiate the class:
button.setOnAction(new AnotherClass());
However, this would be poor design for a couple of reasons:
You are exposing your Button through the public modifier. This is a bad idea and violates the principles of encapsulation.
You would be separating the logic of your application from the UI in a fairly unintuitive manner, making it more challenging to maintain in the future.
My suggestion would be to use an anonymous class and a lambda expression within the setOnAction() method:
button.setOnAction(event -> {
System.out.println("print");
}
This would have the same result as your current implementation of AnotherClass, but is easier to read, easier to maintain, keeps the logic for your buttons right in the same code, and does not require you to expose your Button publicly.

Javafx - Exception in thread "main" java.lang.ExceptionInInitializerError [duplicate]

My application is Swing-based. I would like to introduce JavaFX and configure it to render a Scene on a secondary display.
I could use a JFrame to hold a JFXPanel which could hold a JFXPanel but I would like to achieve this with JavaFX API.
Subclassing com.sun.glass.ui.Application and using Application.launch(this) is not an option because the invoking thread would be blocked.
When instantiating a Stage from Swing EDT, the error I get is:
java.lang.IllegalStateException: Toolkit not initialized
Any pointers?
EDIT: Conclusions
Problem: Non-trivial Swing GUI application needs to run JavaFX components. Application's startup process initializes the GUI after starting up a dependent service layer.
Solutions
Subclass JavaFX Application class and run it in a separate thread e.g.:
public class JavaFXInitializer extends Application {
#Override
public void start(Stage stage) throws Exception {
// JavaFX should be initialized
someGlobalVar.setInitialized(true);
}
}
Sidenote: Because Application.launch() method takes a Class<? extends Application> as an argument, one has to use a global variable to signal JavaFX environment has been initialized.
Alternative approach: instantiate JFXPanel in Swing Event Dispatcher Thread:
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
}
});
latch.await();
By using this approach the calling thread will wait until JavaFX environment is set up.
Pick any solution you see fit. I went with the second one because it doesn't need a global variable to signal the initialization of JavaFX environment and also doesn't waste a thread.
Found a solution. If I just create a JFXPanel from Swing EDT before invoking JavaFX Platform.runLater it works.
I don't know how reliable this solution is, I might choose JFXPanel and JFrame if turns out to be unstable.
public class BootJavaFX {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new JFXPanel(); // this will prepare JavaFX toolkit and environment
Platform.runLater(new Runnable() {
#Override
public void run() {
StageBuilder.create()
.scene(SceneBuilder.create()
.width(320)
.height(240)
.root(LabelBuilder.create()
.font(Font.font("Arial", 54))
.text("JavaFX")
.build())
.build())
.onCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent windowEvent) {
System.exit(0);
}
})
.build()
.show();
}
});
}
});
}
}
Since JavaFX 9, you can run JavaFX application without extending Application class, by calling Platform.startup():
Platform.startup(() ->
{
// This block will be executed on JavaFX Thread
});
This method starts the JavaFX runtime.
The only way to work with JavaFX is to subclass Application or use JFXPanel, exactly because they prepare env and toolkit.
Blocking thread can be solved by using new Thread(...).
Although I suggest to use JFXPanel if you are using JavaFX in the same VM as Swing/AWT, you can find more details here: Is it OK to use AWT with JavaFx?
I checked the source code and this is to initialize it
com.sun.javafx.application.PlatformImpl.startup(()->{});
and to exit it
com.sun.javafx.application.PlatformImpl.exit();
I used following when creating unittests for testing javaFX tableview updates
public class testingTableView {
#BeforeClass
public static void initToolkit() throws InterruptedException
{
final CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
new JFXPanel(); // initializes JavaFX environment
latch.countDown();
});
if (!latch.await(5L, TimeUnit.SECONDS))
throw new ExceptionInInitializerError();
}
#Test
public void updateTableView() throws Exception {
TableView<yourclassDefiningEntries> yourTable = new TableView<>();
.... do your testing stuff
}
}
even though this post is not test related, then it helped me to get my unittest to work
without the BeforeClass initToolkit, then the instantiation of TableView in the unittest would yield a message of missing toolkit
There's also way to initialize toolkit explicitly, by calling:
com.sun.javafx.application.PlatformImpl#startup(Runnable)
Little bit hacky, due to using *Impl, but is useful, if you don't want to use Application or JXFPanel for some reason.
re-posting myself from this post
private static Thread thread;
public static void main(String[] args) {
Main main = new Main();
startup(main);
thread = new Thread(main);
thread.start();
}
public static void startup(Runnable r) {
com.sun.javafx.application.PlatformImpl.startup(r);
}
#Override
public void run() {
SoundPlayer.play("BelievexBelieve.mp3");
}
This is my solution. The class is named Main and implements Runnable. Method startup(Runnable r) is the key.
Using Jack Lin’s answer, I found that it fired off the run() twice. With a few modifications that also made the answer more concise, I offer the following;
import com.sun.javafx.application.PlatformImpl;
public class MyFxTest implements Runnable {
public static void main(String[] args) {
MyFxTest main = new MyFxTest();
PlatformImpl.startup((Runnable) main);
}
#Override
public void run() {
// do your testing;
System.out.println("Here 'tis");
System.exit(0); // Optional
}
}

JavaFX - Why does JavaFx create a primary stage instead of allowing me to do it?

I would like to create a class called EnhancedStage which adds functionality to Stage.
The problem is, javaFx creates the primary stage and passes it into the application, instead
of allowing me to create it. This way I'm not able to use my own class for the primary stage.
I don't understand why the system creates the Stage, and would I be losing anything by not
using that stage and instead constructing another one ?
Thanks
If you don't want to use the stage which is a parameter of the start() method, then don't call show() on that stage, just create your own stage and only call show() on your custom stage. I suggest doing this inside the start method rather than via a runLater call from init. I don't see any significant drawback in such an approach.
As to why a stage is passed in start, MadProgrammer's guess is as good as any: Simplifies life for programmers, removes some boilerplate code from basic apps and limits app startup coding errors. For all those reasons, for most people, I'd generally just advise using the provided stage rather than creating your own.
Suggested approach if you need or desire to use a stage subclass for your primary stage:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class StageTester extends Application {
#Override public void start(final Stage systemStage) throws Exception {
UserStage userStage = new UserStage();
userStage.show();
}
private class UserStage extends Stage {
FlowPane layout = new FlowPane();
UserStage() {
super();
foo();
setScene(new Scene(layout));
}
public void foo() {
layout.getChildren().add(new Label("foo"));
}
}
public static void main(String[] args) { launch(args); }
}
There is nothing stopping you from creating another stage and ignoring the one you get from the start method. There are number of ways to do this:
Extend Stage and add your additional functionality
public class EStage extends javafx.stage.Stage {
... // Do your magic here
}
or Extend Window
public class EStage extends javafx.stage.Window {
}
In either case, to use your new stage, you can just create it in the init method and use it in the start method, ignoring the stage supplied to you
public class ShowCase extends Application {
private Stage mStage;
#Override
public void init() throws Exception {
Platform.runLater(() -> {
mStage = new EStage();
//mStage.impl_setPrimary(true);
}
}
#Override
public void start(Stage primaryStage) {
mStage.setScene(...)
}
}
The JavaFX architecture is based upon a container which manages the JavaFX Event Loop (on the JavaFX application thread) for your application (which is why the entry class for your application must inherit from javafx.application.Application).
Because JavaFX applications are container managed, you gain a few benefits:
the container can inject JavaFX control references into your controller class instances via the FXML loader
the container manages event propagation from the event loop all the way down to the target control and back up to the event loop again without complex event handling on your part
Of course, the event loop requires a top-level object for event propagation and this top-level object is the Stage object, which gets passed to the start() method of your Application object.

NullPointerException when using TextArea.append()

I realize there is an nearly identical question in title. The question does not seem to be relevant to my particular issues.
I'm using the JavaFX scene builder to create my UI, (which includes the TextArea in question). All I want to do is take the message I get from the server and post it into the text area. Through various println statements I have narrowed the problem to this. I have tried various solutions (for hours); coming here was a last resort.
The only other possible cause I can think of would be something going wrong with the multithreading, but can even begin to think of what.
public class IncomingReader implements Runnable
{
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
FXMLDocumentController.controller.chatBox
.appendText(message + "\n"); /////////PROBLEM HERE//////
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
FXML controller class (relevant line only):
#FXML protected TextArea chatBox;
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader());
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The line of FXML that defines the chatBox:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
The resulting exception:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
Note: this is really covered fully in Passing Parameters JavaFX FXML. However, the code you posted is so far from being structured properly to use the approaches there, that you probably need to see it specifically for your example. I would strongly recommend you read through that post and understand it, though.
Why you are seeing a NullPointerException:
FXMLDocumentController.controller refers to an instance of FXMLDocumentController which is not the same instance that was created for you by the FXMLLoader. Consequently, the chatBox field in FXMLDocumentController.controller was not initialized by the FXMLLoader.
What FXMLLoader does:
When you call one of the FXMLLoader.load(...) methods, it basically does the following:
Parses the FXML file (or stream)
If the root element of the FXML contains an fx:controller attribute, and no controller has been set by other means, it creates an instance of the class specified in that attribute
Creates the object hierarchy described by the FXML. If any of the objects defined in FXML have fx:id attributes, and a controller is associated with the FXMLLoader, it initializes #FXML annotated fields of the controller with the corresponding objects
Associates event handlers with the nodes, where defined
Returns the root object of the FXML object hierarchy
How to access the controller after loading the FXML
To access the controller, you must create an FXMLLoader instance instead of relying on the (evil) static FXMLLoader.load(URL) method. You can either pass the URL of the FXML resource into the FXMLLoader constructor, or call setLocation(...) on the FXMLLoader. Then, to load the FXML file, just call the no-argument load() method on the FXMLLoader. Once that is complete, you can access the controller by calling getController() on the FXMLLoader instance.
Other issues in your code
You cannot update the UI from a background thread: you must update it from the JavaFX Application Thread. As it stands (if you fix your NullPointerException issue), your code would throw an IllegalArgumentException in Java 8. In JavaFX 2.2 and earlier you would have to live with the possibility of bugs showing up at arbitrary times in the future. You can schedule code to be executed on the FX Application Thread by wrapping that code in a call to Platform.runLater().
Not quite so bad, but imo a bad design, is that you are exposing UI elements (the TextArea) outside of your FXML-controller pair. This becomes a real issue when your boss comes into your office and tells you he doesn't want the messages displayed in a TextArea any more, he wants them in a ListView. Since the TextArea is exposed, you have to trawl through all your code looking for any references to it. A better approach is to define a appendMessage(String) method in your controller, and access it. You might even want to factor all the data out into a separate model class, and pass references an instance of that class to both the controller and the reader class, but that is beyond the scope of this question.
I will refrain from complaining about your overuse of static stuff.. ;).
Here's the skeleton of one way to fix this code:
public class IncomingReader implements Runnable
{
private final FXMLDocumentController controller ;
public IncomingReader(FXMLDocumentController controller) {
this.controller = controller ;
}
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
final String msg = message ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.appendMessage(msg + "\n");
}
});
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
Controller:
public class FXMLDocumentController {
#FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
Application:
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
FMXLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader(controller));
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories