Javafx Software Design

JavaFX software design

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.

Most appropriate UI design pattern and JavaFX questions?

The MVP pattern in JavaFX is implemented as an FXML View connected to a controller class as the Presenter that launches separate Model threads as needed for various computations/tasks that feed into the View controls. In JavaFX you create the FXML view with SceneBuilder. SceneBuilder creates the visual part of the GUI in a FXML file that is a passive file containing all of the visual attributes. Then you attach a controller class that handles all of the events from the GUI controls. This implements the Presenter aspect of MVP. Then the Model is a separate thread running various computations in the background and interacting with the JavaFX thread as needed. If the computations are not too intensive, you can just run them in the JavaFX thread itself. If they take a lot of CPU cycles, you must run then in a separate thread so you don't lock up the GUI.

MVC Design Pattern and Controllers in JavaFX

It's not possible to do what you are saying, you can't create something from some other thing that's not even existing. If you don't instantiate the PersonOverview in Main, you can't make it do anything.

Also, notice in this case that the view of PersonOverview is attached to the RootLayout which is created in Main. So you can consider these as one Main View where each controller is managing one part of the view.

In the case of PersonEditDialog, You are starting a stage through the main view to edit some information. That's why it's created in Main. The stage is attached to the MainStage.

And if you have several stages to create, You don't have to do that in Main. It depends on your needs. You can basically run a stage which uses some controller from another controller by clicking on a button for example. So it depends on : after what event you want to see that stage.

Example : You can add a button in PersonEditDialog controller like More... and you define its setOnAction event to open a new view (stage) where you show the picture, the Twitter link...

How to achieve JavaFX and non-JavaFX interaction?

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.

Leveraging the observer pattern in JavaFX GUI design

As noted here, the JavaFX architecture tends to favor binding GUI elements via classes that implement the Observable interface. Toward this end, Irina Fedortsova has adapted the original Converter to JavaFX in Chapter 5 of JavaFX for Swing Developers: Implementing a Swing Application in JavaFX.

image

I've recapitulated the program below, updating to Java 8 and removing the dependency on the now deprecated builder API. In the variation below,

  • A DoubleProperty named meters functions as the application's Observable model.

  • Instances of Control, such as TextField, ComboBox and Slider, each function as a view of the model, as well as providing a way for the user to control the interaction.

  • Within a ConversionPanel, an InvalidationListener added to the ComboBox updates the TextField view of the model as needed to reflect the currently selected Unit; a similar listener added to the TextField updates the model itself as the user types.

  • The same model is shared between instances of ConversionPanel by the Slider, linking the sliders and any controls listening to the model.

      slider.valueProperty().bindBidirectional(meters);
  • Each ComboBox also has a model, ObservableList, from which the user can select among instances of Unit.

Code: converter/Unit.java

/*
* Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package converter;

/**
* @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
*/
public class Unit {

String description;
double multiplier;

Unit(String description, double multiplier) {
super();
this.description = description;
this.multiplier = multiplier;
}

@Override
public String toString() {
String s = "Meters/" + description + " = " + multiplier;
return s;
}
}

Code: converter/ConversionPanel.java

/*
* Copyright (c) 2012, 2013 Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
package converter;

/**
* @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
*/
import java.text.NumberFormat;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;

public class ConversionPanel extends TitledPane {

private static final int MAX = 10000;
private static final int DIGITS = 3;

private final TextField textField = new TextField();
private final Slider slider = new Slider(0, MAX, 0);
private final ComboBox<Unit> comboBox;
private NumberFormat numberFormat = NumberFormat.getNumberInstance();
private DoubleProperty meters;

{
numberFormat.setMaximumFractionDigits(DIGITS);
}

private InvalidationListener fromMeters = (Observable o) -> {
if (!textField.isFocused()) {
textField.setText(numberFormat.format(meters.get() / getMultiplier()));
}
};

private InvalidationListener toMeters = (Observable o) -> {
if (textField.isFocused()) {
try {
Number n = numberFormat.parse(textField.getText());
meters.set(n.doubleValue() * getMultiplier());
} catch (Exception ignored) {
}
}
};

public ConversionPanel(String title, ObservableList<Unit> units, DoubleProperty meters) {
setText(title);
setCollapsible(false);
comboBox = new ComboBox<>(units);
comboBox.getSelectionModel().select(0);
comboBox.setConverter(new StringConverter<Unit>() {

@Override
public String toString(Unit t) {
return t.description;
}

@Override
public Unit fromString(String string) {
throw new UnsupportedOperationException("Not supported yet.");
}
});
setContent(new HBox(new VBox(textField, slider), comboBox));

this.meters = meters;
meters.addListener(fromMeters);
comboBox.valueProperty().addListener(fromMeters);
textField.textProperty().addListener(toMeters);
slider.valueProperty().bindBidirectional(meters);
fromMeters.invalidated(null);
}

/**
* Returns the multiplier for the currently selected unit of measurement.
*/
public double getMultiplier() {
return comboBox.getValue().multiplier;
}
}

Code: converter/Converter.java

/*
* Copyright (c) 2012, 2013 Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
package converter;

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
* @see https://stackoverflow.com/a/31909942/230513
* @see http://docs.oracle.com/javafx/2/swing/port-to-javafx.htm
*/
public class Converter extends Application {

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

private final ObservableList<Unit> metricDistances;
private final ObservableList<Unit> usaDistances;
private final DoubleProperty meters = new SimpleDoubleProperty(1);

public Converter() {
//Create Unit objects for metric distances, and then
//instantiate a ConversionPanel with these Units.
metricDistances = FXCollections.observableArrayList(
new Unit("Centimeters", 0.01),
new Unit("Meters", 1.0),
new Unit("Kilometers", 1000.0));

//Create Unit objects for U.S. distances, and then
//instantiate a ConversionPanel with these Units.
usaDistances = FXCollections.observableArrayList(
new Unit("Inches", 0.0254),
new Unit("Feet", 0.3048),
new Unit("Yards", 0.9144),
new Unit("Miles", 1609.34));
}

@Override
public void start(Stage stage) {
stage.setScene(new Scene(new VBox(
new ConversionPanel("Metric System", metricDistances, meters),
new ConversionPanel("U.S. System", usaDistances, meters))));
stage.show();
}
}


Related Topics



Leave a reply



Submit