3

I have started using JavaFX to create a window for user interaction, to be used in another, non-JavaFX, program.

My main program is called the Abc class, having a main method. This is a non-JavaFX program, but plain vanilla Java. This program runs some activities, then asks the user to select a String from a list of possible Strings. This user interaction is done with a JavaFX program called MenuSelector. The user selects one String, which will then be returned to Abc for further processing.

MenuSelector is made with Netbeans and Scene Builder, which uses three files: FXMLDocument.fxml, FXMLDocumentController.java, and MenuSelector.java. Handling of the selection process is done in FXMLDocumentController.java. The MenuSelector.java class only defines the Stage and Scene.

What I can not find online is instructions for how Abc would start MenuSelector. And can not find instructions how the resulting String in FXMLDocumentController can be passed back to Abc. What steps should I take to get this up and running?

Edit:

I was asked about the reason for having things this way. It was suggested that I am having a design problem.

My current implementation of the MenuSelector is with using javax.swing. This is hand-coded in one java class called MenuSelector, having a top level method which can be called from other programs (e.g. Abc, Def, ..). This MenuSelector is a supporting piece of software which can be used by multiple programs. Each program can submit a list of Strings to the menu, out of which the user can select one String, which will then be returned to the program which called this MenuSelector.

The MenuSelector therefore does not have a main method, only a top-level method which can be called by others. MenuSelector is a supporting program, and is not supposed to be the highest level.

My attempt is to replace the hand-coded javax.swing version of MenuSelector by a JavaFX version. But I can only do this if I can input a list of Strings as input to the menu, and return a single String as result.

Edit, to explain the JavaFX structure as generated by Netbeans:

When generating a new Netbeans JavaFX project it comes with three files: FXMLDocument.fxml, FXMLDocumentController.java, and MenuSelectorFX.java. After building the GUI with Scene Builder and creating the control software the contents of these three are:

FXMLDocument.fxml (not manually modified by me):

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.171" fx:controller="menuselector.FXMLDocumentController">
   <children>
      <VBox alignment="CENTER" prefHeight="200.0" prefWidth="320.0" spacing="20.0">
         <children>
            <ChoiceBox fx:id="InstrumentChoiceBox" prefWidth="150.0" />
            <Button mnemonicParsing="false" onAction="#onChoiceMade" text="This One" />
            <Label fx:id="SelectionLabel" text="Label" />
         </children>
      </VBox>
   </children>
</AnchorPane>

FXMLDocumentController.java (code added by me):

package menuselector;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;

public class FXMLDocumentController implements Initializable {

    @FXML private ChoiceBox InstrumentChoiceBox;
    @FXML private Label SelectionLabel;
    private String SelectedInstrument;

    public String getSelectedInstrument(){
        return SelectedInstrument;
    }

    public void onChoiceMade(){
        SelectedInstrument = InstrumentChoiceBox.getSelectionModel().getSelectedItem().toString();
        SelectionLabel.setText("Instrument selected: "+SelectedInstrument);
    }

    private String[] determineCandidates(){
        //method to determine the list of candidates, abbreviated
        //Reads in a number of Strings from file and converts to String[]
        return Result;
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        String[] Candidates = determineCandidates();
        SelectedInstrument = "";
        for(int i = 0; i < Candidates.length;i++) InstrumentChoiceBox.getItems().add(Candidates[i]);
        InstrumentChoiceBox.setValue(Candidates[0]);
    }
}

and finally MenuSelectorFX (not manually modified by me):

package menuselector;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MenuSelectorFX extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);

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

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}

Until now have I been able to call the MenuSelector from my non-JavaFX class Abc (or Def) by using the lines:

        MenuSelectorFX msfx = new MenuSelectorFX();
        msfx.main(new String[0]);

This starts the GUI with the choice box, and when I select an option the chosen option is displayed in the GUI. What I have not yet been able to achieve is the return of parameter SelectedInstrument in FXMLDocumentController to Abc.

Joe
  • 115
  • 7
  • Please add your code to the question. I do not understand why you are (apparently) using two main-entries. But I suppose you could pass the selected String as an argument to the `Abc` main in `args`, if you really need that. I am guessing is a design problem. – rhenesys Dec 31 '19 at 09:35
  • Perhaps [this sample](https://stackoverflow.com/questions/24320014/how-to-call-launch-more-than-once-in-java) from a related question might help. – jewelsea Dec 31 '19 at 09:51
  • @rhenesys I have added some explanation to my question, addressing the issue why another, non-JavaFX, program is calling the JavaFX program and is expecting a response from the JavaFX program. This to clarify the design of my software. – Joe Jan 02 '20 at 03:35

2 Answers2

4

Usually you'd launch the Application, use it as entry point of the app and start any other logic from it's start/init methods or as reaction to an event.

In your case though starting with JavaFX 9 you can use Platform.startup to initialize the GUI. (Works only once though; Subsequent JavaFX logic would require you to use Platform.runLater instead).

public static void main(String[] args) {
    System.out.println("in main");
    CompletableFuture<String> result = new CompletableFuture<>();
    Platform.startup(() -> {
        System.out.println("creating gui");
        ListView<String> list = new ListView<>();
        for (int i = 0; i <= 100; i++) {
            list.getItems().add(Integer.toString(i));
        }
        Button submit = new Button("OK");
        submit.disableProperty().bind(list.getSelectionModel().selectedItemProperty().isNull());
        submit.setOnAction(evt -> {
            String selection = list.getSelectionModel().getSelectedItem();
            result.complete(selection);
        });
        Scene scene = new Scene(new VBox(list, submit));
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.setOnCloseRequest(evt -> {
            result.completeExceptionally(new IOException("User failed to select an element"));
        });
        stage.show();
    });
    System.out.println("start waiting for result");
    try {
        System.out.println("result: " + result.get());
    } catch (ExecutionException ex) {
        // handle user failing to select an element
        System.out.println(ex.getCause().getMessage());
    } catch (InterruptedException ex) {
        // TODO: make sure not to execute more logic except for shutting down after this
    }
    Platform.exit(); // shutdown javafx
}

For JavaFX < 9 you'd need to use Application.launch to get the application up and runing though and use a static CompletableFuture to get your hands on an object allowing communication with the GUI (or the result, if you only need the GUI once). The logic to use would be similar to the one in the above snippet.

fabian
  • 80,457
  • 12
  • 86
  • 114
  • This is brilliant. I'm using JavaFX 14.0.1 and while I didn't copy your answer exactly, I omitted Platform.exit() on the last line because it caused an error for me. Then it worked and displayed two JavaFX windows! I just ran the same program twice with different program arguments but did not use Platform.runLater the second time. Could this be some new feature of a later JavaFX or Java (15) release? – saner Oct 07 '20 at 00:53
  • How exactly your code looks and I haven't worked with JavaFX lately,but it used to be the case that you need to start up the platform exactly once somehow(Platform.runLater or normal Application startup).After that you can create as many windows as you like using Platform.runLater or running the logic on the JavaFX application thread by some other means.In the end you need to make sure the platform exits(Platform.runLater or in some cases closing the last window);after this trying to access javafx functionality may result in exceptions.2 seperate instances of a program shouldn't interfere btw – fabian Oct 08 '20 at 18:37
1

I made a small program to build what I understood you are trying to do. The Abc class starts and get some input from the user. With that input we start the JavaFX Window with some options in a combobox. The picked value from the combobox is returned to Abc and we can use it.

The Abc class:

package application;

import java.util.Scanner;

public class Abc {

    public static void main(String[] args) {
        System.out.println("Some activitives...");
        System.out.println("Press 1 to start JAVAFX");

        try(Scanner input = new Scanner(System.in)){
            int one = input.nextInt();
            if(one == 1) {
                String[] inputedValue = new String[] {String.valueOf(one)};
                MainJavaFX.main(inputedValue);
                System.out.println("The user picked " + FXMLDocumentController.selectedValue);
                System.out.println("Go on and use it...");
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}

The MainJavaFX application. Note I made a MyStrings class to populate the combobox:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;


public class MainJavaFX extends Application {
@Override
public void start(Stage stage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("Document.fxml"));
    AnchorPane root = loader.load();
    Scene scene = new Scene(root);

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

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}
}

The Controller:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;

public class FXMLDocumentController implements Initializable {

    @FXML private ChoiceBox InstrumentChoiceBox;
    @FXML private Label SelectionLabel;
    @FXML private Button selectionButton;
    static String SelectedInstrument;

    public String getSelectedInstrument(){
        return SelectedInstrument;
    }

    public void onChoiceMade(){
        SelectedInstrument = InstrumentChoiceBox.getSelectionModel().getSelectedItem().toString();
        SelectionLabel.setText("Instrument selected: "+SelectedInstrument);
        selectionButton.getScene().getWindow().hide();
    }

    private String[] determineCandidates(){
        //method to determine the list of candidates, abbreviated
        //Reads in a number of Strings from file and converts to String[]
        return new String[] {"a","b","c"};
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        String[] Candidates = determineCandidates();
        SelectedInstrument = "";
        for(int i = 0; i < Candidates.length;i++) InstrumentChoiceBox.getItems().add(Candidates[i]);
        InstrumentChoiceBox.setValue(Candidates[0]);
    }
}

The Document.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.171" fx:controller="application.FXMLDocumentController">
   <children>
      <VBox alignment="CENTER" prefHeight="200.0" prefWidth="320.0" spacing="20.0">
         <children>
            <ChoiceBox fx:id="InstrumentChoiceBox" prefWidth="150.0" />
            <Button fx:id="selectionButton" mnemonicParsing="false" onAction="#onChoiceMade" text="This One" />
            <Label fx:id="SelectionLabel" text="Label" />
         </children>
      </VBox>
   </children>
</AnchorPane>

And the output when running it:

Some activitives...
Press 1 to start JAVAFX
-- The JavaFX Window pops up --
The user picked b
Go on and use it...

EDIT: I added a controller class so you have the same structure. Note that the button has now an Id so I can use it to close the JavaFX after the user picked a value from the combobox. I also made the selected String value static so I can just call it from the Abcclass in my print.

rhenesys
  • 174
  • 1
  • 11
  • rhenesys Thank you for your sample code. I understand your code, and see how class `Abc` starts `MainJavaFX`. The remaining issue I am having is about how to process the selected value. But this is probably caused by the way Netbeans generates a JavaFX project/package. Netbeans generates three files: `FXMLDocument.fxml`, `FXMLDocumentController.java`, and `MainJavaFX.java`. In Netbeans is the selected value not available in `MainJavaFX`, but in `FXMLDocumentController`. I need to find a way to export from that `FXMLDocumentController` to `MainJavaFX`, before it is available to `Abc`. – Joe Jan 02 '20 at 14:12
  • 1
    If you post the code, it would be easier. The `FXMLDocument` is the same that mine `UnserInteractionView.fxml`. The `MainJavaFX` is mine `MainJavaFX` but my main is also the "controller". I saved the selected value in the static string and this is available in the Abc class for further work. – rhenesys Jan 02 '20 at 15:22
  • I have posted the code, with the exception of the actual implementation of method `determineCandidates()`. This to keep the code block shorter and easier to understand, without distractions. rhenesys, thank you for your help thus far. – Joe Jan 04 '20 at 13:21
  • 1
    Thank you, thank you! I appreciate the way you have guided me in answering my question, and made me learn more about both JavaFX and Java. – Joe Jan 06 '20 at 12:39
  • "Note that the button has now an Id so I can use it to close the JavaFX after the user picked a value from the combobox." The `Label` already had an id, so I used that one instead to close the window. I might be wrong but have the impression that the return String is only available after the window got closed. – Joe Jan 06 '20 at 12:43