2

I'm working on creating a Desktop UI using JavaFX. The app will open a window where the user will input some information before closing it. My initial idea for returning the data, was to have this opened window save the info to the class variables. And I would use getters to retrieve the data from the class that created the UI application. However, it seems that the variables are getting reset to their original values after the window is closed and I am unable to retrieve the data. Below is psuedoCode for what I am trying to achieve.

The class for the UI.

public class MyScreen extends Application {
    private String userText;

    public void openWindow() {
        launch();
        System.out.println("3. " + userText); // Outputs empty string.
    }

    @Override
    private void start(Stage stage) {
        // Create window controls
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(final ActionEvent event) {
                // Do stuff with user data
                System.out.println("1. " + userText); // Outputs empty string
                userText = textField.getText();
                System.out.println("2. " + userText); // Outputs user inputted text.
                stage.close();
            }
        })
    }

    public getUserText() {
        return userText;
    }
}

The calling class.

public class MyMainClass {
    public static void main(String args[]) {
        MyScreen screen = New MyScreen();
        screen.openWindow();
        System.out.println("4. " + screen.getUserText());
    }
}

Sample output from running my process:

> 1.
> 2. sample text
> 3.
> 4.

What is the proper methodology to go about returning data from a class that instantiates a JavaFX ui?

  • I'm actually quite surprised you're not getting an exception because in theory your instance of the `MySpace` class should already be deconstructed after you call `stage.close()`. You could try saving the value in your main class, e.g. create a private field and a setter to call from the button event handler just before you call `stage.close()` – Shovel_Knight Aug 24 '23 at 21:57
  • that, of course, would be a very bad design, but your classes are interdependent anyway, so you wouldn't lose anything – Shovel_Knight Aug 24 '23 at 22:15
  • 5
    JavaFX, as part of calling `launch`, instantiates an instance of the `Application` subclass (via reflection). So, there's two instances of `MyScreen` in your program. The one you created, and the one JavaFX creates. You only have access to the former, but it's the latter's `userText` field that's being set. – Slaw Aug 24 '23 at 22:15
  • 4
    Taking a step back, this whole design seems odd. If you're working on a GUI application, then I would expect the UI to be shown for the entire duration the application is in use. Right now, it seems like you have a CLI application and are using JavaFX to prompt the user for information. But if you have a CLI application then you should use the console to prompt the user. In other words, choose if your app is a CLI or a GUI app, but don't mix them. You should also note that, by default, JavaFX exits once the last window closes, and it cannot be launched again in the same JVM instance. – Slaw Aug 24 '23 at 22:23
  • In the interim, don't overlook the convenience of the clipboard. – trashgod Aug 24 '23 at 22:24
  • @Slaw The original design was going to follow the same general logic the current Graphical UI follows that was written in Powershell. I'm changing the design from Powershell that uses Windows Forms to Java to be more responsive since there's a lot of overhead with PS. This PS code would launch authentication, then launch the UI. So that's where this comes from. I will have to re-evaluate my design implementation for this. Thank you for your feedback. – Levithan6785 Aug 25 '23 at 14:43

2 Answers2

4

I'll make an Answer of the two Comments by Slaw, here & here.

JavaFX “magic”

JavaFX is a sophisticated framework that does quite a bit of “magic” beyond what you see in your own source code.

One bit of that magic is that when your JavaFX app launches, your subclass of Application is automatically instantiated via reflection.

So your line of code, MyScreen screen = New MyScreen(); is superfluous, and should be deleted. The JavaFX framework is already making an instance of MyScreen as your subclass of Application. So your app has two instances of MyScreen going, the one implicitly created by the JavaFX framework, and another one created by you in that line of code.

This explains why you fail in getting the value of userText field. Your code is making a call to your instance of MyScreen, not the JavaFX framework’s instance of MyScreen. The userText field was indeed set, but was set in the latter, not the former.

this hack

If you really want to track state within your app in your given approach, I have seen a hack where people make a Singleton reference to the implicitly instantiated object of their Application subclass. They add a static var on their subclass of Application, a var of the type of the subclass. Then in their Application#start override implementation, they assign this to that var.

public class MyScreen extends Application {
    public static MyScreen myInstanceOfMyScreen;
    private String userText;

    @Override
    private void start(Stage stage) {
        …
        this.myInstanceOfMyScreen = this ;  // A hack, not necessarily recommended. 
    }

    public getUserText() {
        return userText;
    }
        

Then other classes can access that static singleton.

// Some code in some other class
String userText = MyScreen.myInstanceOfMyScreen.getUserText() ;

But this approach likely indicates a poor design within your app.

JavaFX lifecycle

To learn more about the lifecycle of a JavaFX app, see my lengthy Answer on another Question.

Caveat: I am not an expert on JavaFX. For more expertise, look for posts by jewelsea, by Slaw, by James_D, and others. And read the documentation.

GUI app versus console app

Your approach in designing your app seems peculiar, running the JavaFX-based GUI app as a subroutine of a larger "outer" app.

A GUI app is standalone. The lifecycle of the GUI should be the lifecycle of the app. If all the elements of the GUI have been closed, with no more windows and no more menu bar, then the app should end.

The fact that you named your Application subclass MyScreen shows how you are thinking of JavaFX as being only a part of your app. Instead you should be thinking of JavaFX as your entire app.

If your goal is build a console app that gathers input from a user, then use the console-related classes of Java such as Scanner to interact with the user on the console. Trying to mix a GUI app with a console app makes no sense, not from the architectural design of modern operating systems such as macOS, Windows, Linux, etc.

The one exception would be console-oriented environment that wants to run a GUI-style within that terminal screen. Such apps are known as text-based user-interface, made famous by Microsoft with their COW (Character-Oriented Windows) apps such as Microsoft Works for MS-DOS. But such apps are meant to run within a terminal screen/window. In contrast, JavaFX apps are meant to run within a graphical environment such as macOS/Windows/Linux/etc. FYI, the Java ecosystem does offers some frameworks for build text-based UI apps, such as Lanterna.

Communicating data

If you need to share the user’s input beyond the limits of the GUI, use an external system. For example, post the data to a message queue, send an email, insert into a database, write a file to storage.

Example app

Here is an example JavaFX app that:

  1. Gathers input from the user.
  2. Reports that data to a message queue, sends an email, and writes to a database.
  3. Quits/exits the app.

screenshot of example app soliciting the user for their favorite color, along with a button to submit that data and then exit the app

Notice that the GUI remains visible while reporting to that queue, email, and database. If anything goes wrong, you can use that GUI to notify the user of a problem.

package work.basil.example.exfxgatherinput;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;

public class GatherInputApp extends Application
{
    @Override
    public void start ( Stage stage ) throws IOException
    {
        // Scene
        Scene scene = this.makeScene ( );

        // Stage
        stage.setTitle ( "Give me your input" );
        stage.setHeight ( 300 );
        stage.setWidth ( 500 );

        stage.setScene ( scene );
        stage.show ( );
    }

    private Scene makeScene ( )
    {
        // Data input
        Label inputLabel = new Label ( "Favorite color:" );
        TextField inputTextField = new TextField ( );
        HBox inputBox = new HBox ( );
        inputBox.setAlignment ( Pos.CENTER );
        inputBox.getChildren ( ).addAll ( inputLabel , inputTextField );
        inputBox.setSpacing ( 10 );

        // Submit data
        Button button = new Button ( );
        button.setText ( "Submit input, quit app" );
        button.setOnAction ( ( event ) ->
        {
            String favoriteColor = inputTextField.getText ( );
            System.out.println ( "Reporting input to a message queue… " + favoriteColor );
            System.out.println ( "Sending input in an email… " + favoriteColor );
            System.out.println ( "Writing input to a database… " + favoriteColor );
            Platform.exit ( );  // Ends the JavaFX app, after firing hooks for app-shutdown. 
        } );

        VBox vbox = new VBox ( inputBox , button );
        vbox.setPadding ( new Insets ( 20 , 20 , 20 , 20 ) );
        vbox.setSpacing ( 10 );
        vbox.setAlignment ( Pos.CENTER );

        return new Scene ( vbox );
    }

    public static void main ( String[] args ) { launch ( ); }
}

When run:

Reporting input to a message queue… purple
Sending input in an email… purple
Writing input to a database… purple

Process finished with exit code 0

By the way, exiting a GUI app programmatically is considered rude. In a graphical environment, the user is in charge. Generally, the app should remain open and running until the user decides to quit/exit.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Accepting your answer as it provided most of the information I required to fixing my design flaw and providing correct solutions for application <-> application communication. The ultimate fix for what I'm trying to do is posted in another comment.. Thank you – Levithan6785 Aug 25 '23 at 16:03
0

After reviewing the comments and answers, I realized the original design I had was inherently flawed.

I attempted to return and get data from an independent Application to be used in another Application. The correct approach was to have one Main Application with different classes to display sub windows (stages). And store that info in their respective class.

Below is the corrected approach for what I'm trying to do.

public class MyScreen {
    private String userText;

    private void displayUserInputPrompt() {
        Stage stage = new Stage();
        // Create window controls
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(final ActionEvent event) {
                // Do stuff with user data
                userText = textField.getText();
                stage.close();
            }
        })

       stage.initModality(Modality.APPLICATION_MODAL)
       stage.showAndWait();
    }

    public getUserText() {
        return userText;

Main Class

public class MyMainClass extends Application {
    public static void main(String args[]) {
        launch();
    }

    @Override
    public void start(final Stage primaryStage) {
        // Setup Control elements
        MyScreen newScreen = MyScreen();
        newScreen.displayUserInputPrompt();
        String text = newScreen.getUserText();
        // Do stuff
    }
}

The reason my original approach was flawed was because:

  1. launch() creates a reflection of the current class. So my new instance of MyScreen is not the same object that is created when calling launch();
  2. Only one Application per JVM instance is allowed.
  3. The MyMainClass should be the application object, and call classes that create different controls or stages. NOT another application.
  4. Sharing data between two applications should be done using:

message queue, send an email, insert into a database, write a file to storage.