Passing reference to javafx.application.Application - java

Consider a non-fx existing application, let's call it Business.
Business exposes a Model object, which in turn exposes some properties. Model also accepts listeners to those properties.
My question is about adding JavaFx gui to such application. The GuiApp obviously extends javafx.application.Application and will need a reference to a Model object.
Searching for a solution for passing a non-String parameter to GuiApp I found several different approaches:
Static approach : for example have Business initialize a static reference to Model in GuiApp. One example of the use of statics can be seen here .
JavaFx 9 approach: as demonstrated here you can launch JavaFx application without extending Application.
Change workflow approach: change the existing workflow to have GuiApp initialize BusinessApp . One example of such workflow can be seen here.
Are there another viable approaches ? Best practice ?

I'll try to demonstrate some different approaches for passing a reference between a java program, and a java-fx program.
I post it in hope it will help some future readers having similar need. I also hope it may encourage other answers with additional solutions.
The posted code should not be considered proper implementation, but rather a short code aiming to clarify the different approaches. For this purpose I'll introduce a simple listening interface :
interface Observe{ void update(int i); }
A java class, that represents an exiting business application :
public class JavaApp {
private Observe observer; private int counter = 0;
JavaApp(Observe observer){ //not null safe
this.observer = observer;
}
void process() {
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
observer.update(counter >=100 ? 0 : ++counter);
}
}, 1000,1000);
}
}
A java-fx application that should be added to the existing business application, listen to it and serve as view:
public class JavaFxApp extends Application implements Observe{
private Label label;
#Override public void start(Stage stage) {
label = new Label("waiting");
BorderPane pane = new BorderPane(label);
Scene scene = new Scene(pane, 100, 100);
stage.setScene(scene);
stage.show();
}
#Override public void update(int i) {
Platform.runLater(()-> label.setText(String.valueOf(i)));
}
}
How do we share a reference, in this case a reference to Observe instance, between the two applications ?
Approach 1: Consider the start() method as the entry point to the application (see James_D answer)
This is simple and straight forward if you want to tie the existing java application with java-fx and use java-fx Application as the entry point:
public class JavaFxApp extends Application implements Observe{
private Label label;
#Override public void start(Stage stage) {
JavaApp main = new JavaApp(this);
label = new Label("waiting");
BorderPane pane = new BorderPane(label);
Scene scene = new Scene(pane, 100, 100);
stage.setScene(scene);
stage.show();
new Thread(()-> { main.process();}).start(); //launch the business process
}
#Override public void update(int i) {
Platform.runLater(()-> label.setText(String.valueOf(i)));
}
public static void main(String[] args) { launch(); }
}
Approach 2: Use JavaFX 9 Platform#startup
This is the best solution I found, when you can not use the Application#start method as the entry point to the application.
As demonstrated in fabians answer, as off java-fx 9 you can launch without extending Application. All you have to do is modify the main of the java application:
public class JavaApp {
private Observe observer; private int counter = 0;
JavaApp(Observe observer){//not null safe
this.observer = observer;
}
void process() {
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override public void run() {
observer.update(counter >=100 ? 0 : ++counter);
}
}, 1000,1000);
}
public static void main(String[] args) {
JavaFxApp view = new JavaFxApp(); //initialize JavaFx application
JavaApp main = new JavaApp(view);
Platform.startup(() -> {//launch JavaFx application
Stage stage = new Stage();
try {
view.start(stage);
} catch (Exception ex) {ex.printStackTrace();}
});
main.process(); //run business process
}
}
Approach 3: Use Static members
For example introduce a static getter in the java-fx application :
public class JavaFxApp extends Application {
private static Label label = new Label("waiting");
#Override public void start(Stage stage) {
BorderPane pane = new BorderPane(label);
Scene scene = new Scene(pane, 100, 100);
stage.setScene(scene);
stage.show();
}
static Observe getObserver() {
return JavaFxApp::update;
}
private static void update(int i) {
Platform.runLater(()-> label.setText(String.valueOf(i)));
}
}
and use it in the java application:
public class JavaApp {
private Observe observer; private int counter = 0;
JavaApp(Observe observer){//not null safe
this.observer = observer;
}
void process() {
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
observer.update(counter >=100 ? 0 : ++counter);
}
}, 1000,1000);
}
public static void main(String[] args){
new Thread(()-> Application.launch(JavaFxApp.class)).start();
Observe observer = JavaFxApp.getObserver(); //get static observer reference
JavaApp main = new JavaApp(observer);
main.process();
}
}
A better approach to get a static reference might be (based on this answer) :
public class JavaFxApp extends Application implements Observe{
private static final CountDownLatch latch = new CountDownLatch(1);
private static Observe observer = null;
private Label label;
#Override public void init() {
observer = this;
latch.countDown();
}
#Override public void start(Stage stage){
label = new Label("waiting");
BorderPane pane = new BorderPane(label);
Scene scene = new Scene(pane, 100, 100);
stage.setScene(scene);
stage.show();
}
#Override public void update(int i) {
Platform.runLater(()-> label.setText(String.valueOf(i)));
}
static Observe getObserver() {
try {
latch.await();
} catch (InterruptedException e) { e.printStackTrace(); }
return observer;
}
}

Related

Change a binded string property throught non java application thread

Im trying to bind a label to some property that is modified outside the java application Thread and it throws not an fx application thread. I read the javafx concurrency documentation but Im honestly having a hard time of understanding it or how to implement it in my situation.
public class testApplication extends Application {
private final StringProperty someString = new SimpleStringProperty("inicial value");
#Override
public void start(Stage stage) throws IOException {
Label testLabel = new Label("");
VBox testBox = new VBox(testLabel);
Scene scene = new Scene(testBox);
testLabel.textProperty().bind(someStringProperty());
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
Executors.newSingleThreadExecutor().submit(new Runnable() {
#Override
public void run() {
setSomeString("new value");
}
});
}
// getters and setters
}

Refactor JFoenix JFXDialogLayout alert notification code to a simpler form so that it can be reused for other classes

how can I refactor the following code so that only the code in deleteButton.setOnAction(deleteEvent -> {//only this code varies} changes. Everything else will stay the same but the block of code in the lambda expression varies from time to time when I call the class from another class. The block of code that goes through the lambda expression is supposed to be a void method.
public class A {
public void test() {
// ensure that user can't close the alert
Stage primaryStage = (Stage) RootLayoutController.getRootLayout().getScene().getWindow();
JFXAlert<javafx.scene.control.ButtonType> alert = new JFXAlert<>(primaryStage);
alert.initModality(Modality.APPLICATION_MODAL);
alert.setOverlayClose(false);
//create font awesome icon
String ICON = "\uf071";
Label labelIcon = new Label(ICON);
labelIcon.setStyle("-fx-font-family: 'FontAwesome'; -fx-font-size: 60px; -fx-text-fill: #D34336;");
labelIcon.setPadding(new Insets(0,5,0,0));
// Create the content of the JFXAlert with JFXDialogLayout
JFXDialogLayout layout = new JFXDialogLayout();
Label labelHeading = new Label("Alert Notification");
Label labelBody = new Label("Are you sure you want to delete this?");
layout.setHeading(labelHeading);
layout.setBody(new VBox(new HBox(labelIcon, labelBody)));
// Buttons get added into the actions section of the layout.
JFXButton deleteButton = new JFXButton("Delete");
deleteButton.setDefaultButton(true);
deleteButton.setOnAction(deleteEvent -> {
//only this block of code changes
alert.hideWithAnimation();
});
JFXButton cancelButton = new JFXButton("Cancel");
cancelButton.setCancelButton(true);
cancelButton.setOnAction(closeEvent -> alert.hideWithAnimation());
layout.setActions(deleteButton, cancelButton);
alert.setContent(layout);
alert.showAndWait();
}
}
It is not entirely clear from your question what you are trying to accomplish, but I will take a wild stab at it.
If you are looking to be able to pass a code block to the deleteButton.setOnAction() method, you could use an Interface and pass implementations of that interface to the A class. Then just pass that reference to an internal method for the onAction lambda.
Here is a very quick example of how you could do something like this:
Main.java:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Action button
Button btnDoSomething = new Button("Do something...");
btnDoSomething.setOnAction(e -> doTheThings(new ImplDoSomething()));
Button btnDoSomethingElse = new Button("Do something else...");
btnDoSomethingElse.setOnAction(e -> doTheThings(new ImplDoSomethingElse()));
VBox mainPane = new VBox(5);
mainPane.setAlignment(Pos.CENTER);
mainPane.setPadding(new Insets(10));
mainPane.getChildren().addAll(btnDoSomething, btnDoSomethingElse);
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
private void doTheThings(IParameterMethod parameterMethod) {
parameterMethod.call();
}
}
The IParameterMethod.java Interface:
public interface IParameterMethod {
void call();
}
Then you can create as many classes as you like that implement that interface, each with their own call() method, allowing you to execute different code.
ImplDoSomething.java
public class ImplDoSomething implements IParameterMethod {
#Override
public void call() {
System.out.println("Doing something!");
}
}
ImplDoSomethingElse.java:
public class ImplDoSomethingElse implements IParameterMethod {
#Override
public void call() {
System.out.println("Doing something else!");
}
}
This should be easily adapted for your project.

How to run one of multiple distinct stages at application startup with javafx?

I'm starting with javafx and I'm having some trouble understanding how to correctly model the following situation:
Ideally I'd like to have a main() method that would somehow allow me to either open a LoginDialog or if there's already a user/password combination available on disk, to bypass login and directly show the MainDialog to the user.
My main issue is that when I run Application.launch() I'm expected to submit an Application instance, and when implementing one, I don't have any control over its Stage object creation, which creates a catch-22 for me here.
I could create a LoginScene and MainScene but then I'd have no control for things like the Stage's title, for instance.
What's the usual route to solve this kind of issues with javafx?
Thanks
Define a single Application subclass and put the logic to decide whether you need to show the login screen in the start() method (the proper place for startup logic is the aptly-named start() method, not the main method):
public class MyApplication extends Application {
private boolean loggedIn ;
#Override
public void start(Stage primaryStage) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
FXMLLoader loginLoader = new FXMLLoader(getClass().getResource("path/to/login.fxml"));
Parent loginRoot = loginLoader.load();
LoginController loginController = loginLoader.getController();
Scene loginScene = new Scene(loginRoot);
primaryStage.setScene(loginScene);
primaryStage.setTitle("Login");
primaryStage.showAndWait();
// check login from controller and update loggedIn...
}
FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("path/to/main.fxml"));
Parent mainRoot = mainLoader.load();
Scene mainScene = new Scene(mainRoot);
primaryStage.setScene(mainScene);
primaryStage.setTitle("My Application");
primaryStage.sizeToScene();
primaryStage.show();
}
private boolean checkLoginFromDisk() {
// ... etc
}
// for environments not supporting direct launch of JavaFX:
public static void main(String[] args) {
launch(args);
}
}
If you're not using FXML, you just define classes instead of FXML files + controllers for "login" and "main", but the structure stays the same:
public class LoginView {
private final GridPane /* for example */ view ;
public LoginView() {
// setup UI, etc...
}
public Pane getView() {
return view ;
}
public boolean checkLogin() {
// etc...
}
}
and
public class MainView {
private BorderPane /* for example */ view ;
public MainView() {
// set up UI etc...
}
public Pane getView() {
return view ;
}
}
and your start method then looks like
#Override
public void start(Stage primaryStage) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
LoginView loginView = new LoginView();
Scene loginScene = new Scene(loginView.getView());
primaryStage.setScene(loginScene);
primaryStage.setTitle("Login");
primaryStage.showAndWait();
loggedIn = loginView.checkLogin();
}
MainView mainView = new MainView();
Scene mainScene = new Scene(mainView.getView());
primaryStage.setScene(mainScene);
primaryStage.setTitle("My Application");
primaryStage.sizeToScene();
primaryStage.show();
}
Obviously you can refactor this in many different ways (reuse the same login class or fxml instance, use a different stage for the main view, etc etc etc) as you need.
Note that there is no requirement to use the stage passed to the start() method. So if you wanted standalone classes to encapsulate the stage containing a login scene and a main scene, you could add the following classes:
public class LoginStage extends Stage {
private final LoginView loginView ;
public LoginStage() {
loginView = new LoginView();
setScene(new Scene(loginView.getView());
setTitle("Login");
}
public boolean checkLogin() {
return loginView.checkLogin();
}
}
and similarly make a MainStage class. (In the FXML-based version, the LoginStage holds a reference to the LoginController and just loads the FXML in the constructor instead of instantiating the LoginView class.) Then
public class MyApplication extends Application {
private boolean loggedIn ;
#Override
public void start(Stage ignored) {
loggedIn = checkLoginFromDisk();
while (! loggedIn) {
LoginStage login = new LoginStage();
loginStage.showAndWait();
loggedIn = loginStage.checkLogin();
}
new MainStage().show();
}
// ...
}
This seems to be remarkably similar to what I was looking for. It follows jns suggestion.
Not ideal but not terrible:
class LoginScene(stage: Stage) extends Scene(new VBox()) {
val vbox = this.getRoot.asInstanceOf[VBox]
...
}
class MainScene(stage: Stage) extends Scene(new VBox()) {
val vbox = this.getRoot.asInstanceOf[VBox]
...
}
class ApplicationStartup extends Application {
override def start(primaryStage: Stage): Unit = {
val scene = if (...) new LoginScene(primaryStage) else new MainScene(primaryStage)
primaryStage.setScene(scene)
primaryStage.show()
}
}
(code is presented in Scala)
Alternatively, as can be seen from the question's comments, one can just ignore the primaryStage and create our own ones at will, which it's just what I wanted from the outset:
class MainDialog extends Application {
override def start(primaryStage: Stage): Unit = {
val newStage = new Stage {
setTitle("abcdef")
setScene(new Scene(new Button("Hello World")))
}
newStage.show()
}
}

Javafx Toolkit initialisation on Font loading [duplicate]

I want to create basic JUnit test for JavaFX 8 application. I have this simple code sample:
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();
for (int i = 0; i < 5; i++) {
Tab tab = new Tab();
tab.setText("Tab" + i);
HBox hbox = new HBox();
hbox.getChildren().add(new Label("Tab" + i));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
}
// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I only have this code so far:
import javafx.application.Application;
import javafx.stage.Stage;
import org.junit.BeforeClass;
public class BasicStart extends Application {
#BeforeClass
public static void initJFX() {
Thread t = new Thread("JavaFX Init Thread") {
#Override
public void run() {
Application.launch(BasicStart.class, new String[0]);
}
};
t.setDaemon(true);
t.start();
}
#Override
public void start(Stage primaryStage) throws Exception {
// noop
}
}
Can you tell me how I can create JUnit test for the above code?
I use a Junit Rule to run unit tests on the JavaFX thread. The details are in this post. Just copy the class from that post and then add this field to your unit tests.
#Rule public JavaFXThreadingRule javafxRule = new JavaFXThreadingRule();
This code works for both JavaFX 2 and JavaFX 8.
The easiest aproach is the following:
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.stage.Stage;
import org.junit.Test;
public class BasicStart {
#Test
public void testA() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
new JFXPanel(); // Initializes the JavaFx Platform
Platform.runLater(new Runnable() {
#Override
public void run() {
new Main().start(new Stage()); // Create and
// initialize
// your app.
}
});
}
});
thread.start();// Initialize the thread
Thread.sleep(10000); // Time to use the app, with out this, the thread
// will be killed before you can tell.
}
}
Hope it helps!
Based on Brian Blonski 's answer I created a JUnit-Testrunner, that does essentially the same thing, but is a bit simpler to use in my opinion.
Using it, your test would look like this:
#RunWith( JfxTestRunner.class )
public class MyUnitTest
{
#Test
public void testMyMethod()
{
//...
}
}

JavaFX launch Application standalone OR from another application

following Scenario:
JavaFxMainApp
JavaFXUpdaterApp
Both are JavaFX applications with a GUI and a void main() method.
The Updater has to be able to start the JavaFXMainApp by accessing
the JavaFxMainApp.jar ONLY knowing about the main class -> it can only! call
main().
The JavaFxMainApp has to also be able to run on its own, by starting
main().
I cannot start multiple VMS and the two apps have no means of communication.
Problem with this is:
Application.launch() can only be executed once per JVM.
The standard javaFx way to start the app:
Updater + MainApp
public static void main(String[] args) {
launch(args);
}
Here it is impossible to fullwill Requirement 1). As both main() methods call launch().
A second approach i found is:
Updater + MainApp
public static void main(String[] args) {
Application app2 = JavaFxMainApp.class.newInstance();
Stage anotherStage = new Stage();
app2.start(anotherStage);
}
First off, i lose the possibility to pass args, but i can live with that as they would not be used anyways.
Major Problem here is, that this code ONLY works, if the JVM already has a JavaFx Thread running, hence it requires that launch() has been called in the JVM at some point before. This is not the case as none of the two calls launch() anymore.
Hybrid approach:
Updater calls launch(), MainApp takes the second approach
Requirement 2) cannot be fulfilled, als starting MainApp without launcher Updater is impossible now.
Another idea i had where dirty "try launch() catch()-> try second approach() in both apps, but that couples both apps and make the setup less flexible.
Is there a way to accomplish this without having to override JavaFxs' LauncherImpl or Application classes to fit these needs?
Can you do something like this:
public class MainApp extends Application {
private Parent uiContent ;
public static final double DEFAULT_WIDTH = 800 ;
public static final double DEFAULT_HEIGHT = 600 ;
public Parent getContent() {
if (uiContent == null) {
uiContent = initializeUI();
}
return uiContent ;
}
public Scene createScene() {
return new Scene(getContent(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public void initializeAndShowStage(Stage stage) {
stage.setScene(createScene());
stage.show();
}
private Parent initializeUI() {
// probably wise to check we are on the FX Application thread here...
Pane root = ... ;
// build ui....
return root ;
}
#Override
public void start(Stage primaryStage) throws Exception {
initializeAndShowStage(primaryStage);
}
public static void main(String[] args) {
launch(args);
}
}
And
public class UpdaterApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
// whatever you need to launch the updater app here...
}
// invoke from the FX Application Thread to "start" main app:
private void showMainApp(Stage stage) {
MainApp app = new MainApp();
app.initializeAndShowStage(stage);
}
private void showMainApp() {
showMainApp(new Stage());
}
public static void main(String[] args) {
launch(args);
}
}
This would be the far preferred approach. If you have requirements that force you to call main, then you could try something like this:
public class MainApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
// .... build UI etc
}
public static void main(String[] args) throws Exception {
if (Platform.isFXApplicationThread()) {
Stage someStage = new Stage();
MainApp app = new MainApp();
app.start(stage);
} else {
launch(args);
}
}
}
Then your updater app can just call MainApp().main(new String[0])); from the FX Application Thread.
This feels like a bit of a hack though.
You want your MainApp to start of its own and you also want an UpdateApp to start the MainApp when required then you can follow my step. I have tried and this model is working.
This is the starting point. You need to call this Class to begin your application
public class StartAnApp {
public static void main(String[] args){
new Thread(new MainApp()).start(); // this will call your MainApp
}
}
This is the First Application which will start. As to start a JavaFX Application you need to have a main() methods. So ensure to provide a main method in this class.
public class MainApp extends Application implements Runnable{
public MainApp(){} // constructor
#Override
public void start(Stage stage){
Text text = new Text("MainApp");
Button startUpdate = new Button("Start Update");
// When this button is pressed. It will launch UpdateApp Application
startUpdate.setOnAction( e -> {
Platform.runLater(new Runnable(){
#Override
public void run(){
new UpdateApp().start(new Stage());
}
});
});
Group root = new Group(text);
Scene scene = new Scene(root,300,400);
stage.setScene(scene);
stage.setX(0);
stage.setY(0);
stage.show();
}
// This method will be used when you first start an Application for
// which main method is required
public static void main(String[] args){
launch(args);
}
// This will be used when you call this Application from another JavaFX application
// if you have closed this application before
#Override
public void run(){
launch();
}
}
This is your UpdateApp. This method does not have main() method.
public class UpdateApp extends Application implements Runnable{
public UpdateApp(){} // constructor
#Override
public void start(Stage stage){
Text text = new Text("UpdateApp");
Button startAnother = new Button("Start Another");
// When this button is pressed. It will launch MainApp Application or you can add any other JavaApplication
startAnother.setOnAction( e -> {
Platform.runLater(new Runnable(){
#Override
public void run(){
new MainApp().start(new Stage());
}
});
});
Group root = new Group(text);
Scene scene = new Scene(root,300,400);
stage.setScene(scene);
stage.setX(350);
stage.setY(0);
stage.show();
}
// This will be used when you call this Application from another JavaFX application
#Override
public void run(){
launch();
}
}

Categories

Resources