JavaFX with multiple FXML models

Joe

Thành viên VIP
21/1/13
2,958
1,323
113
Hi

Today I show you how to develop a JFX application with different, independent FXML models: a so-called MMVC (Multiple Model View Controller). Normally JFX application is an extension of javafx.application.Application -similar to SWING app as an extension of javax.swing.JFrame. Meaning: a JFX application has ONE model, ONE view and ONE controller. If a JFX app has to work with different models unpredictable problems could arise. For example, what model should be firstly presented and what model should be the next. Of course, there are several ways to achieve the goal. But the logical flow is always the same: the main view (of the main model) installs a connection to the next view (of the next model) and the next view repeats the flow to the next-next and so on and so forth.

Further, I show you how to FXML with your own customized JFX objects (e.g. Button, Label, etc.) In this tutorial I demonstrate how a customized "JoeButton" is used in a FXML model.

First of all: the customized JoeButton.java
PHP:
package joe;
import javafx.scene.control.Button;
// Joe Nartca (C)
public class JoeButton extends Button {
  public JoeButton() {
    super();
    setStyle("-fx-background-color: #ecebe9,"+
                   "rgba(0,0,0,0.05),"+
                   "linear-gradient(#dcca8a, #c7a740),"+
                   "linear-gradient(#f9f2d6 0%, #f4e5bc 20%, #e6c75d 80%, #e2c045 100%),"+
                   "linear-gradient(#f6ebbe, #e6c34d);"+
             "-fx-background-insets: 0,9 9 8 9,9,10,11;"+
             "-fx-background-radius: 50;"+
             "-fx-padding: 15 30 15 30;"+
             "-fx-font-family: \"Helvetica\";"+
             "-fx-font-size: 18px;"+
             "-fx-text-fill: #311c09;"+
             "-fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.1) , 2, 0.0, 0, 1);");
  }
}
Explanation: JoeButton is an extension of javafx.scene.control.Button which is internally customized by CSS codes (see: HERE for more other "models".)

I show you hereunder a typical JFX application with a Login Entry.

Welcome.jpg

The model: Login.fxml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.text.Font?>
<!-- this is Joe's JoeButton -->
<?import joe.JoeButton?>

<AnchorPane fx:id="aPane" prefHeight="408.0" prefWidth="520.0"
            xmlns="http://javafx.com/javafx/8"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="LoginController">
    <style>
       -fx-background-image: url(./RedRiverDelta.jpg)
    </style>
    <children>
       <TextField fx:id="Username" layoutX="140.0" layoutY="90.0" maxWidth="309.0" minWidth="309.0" prefHeight="31.0" prefWidth="309.0" promptText="Username/email" />
       <PasswordField fx:id="Password" layoutX="140.0" layoutY="135.0" maxWidth="309.0" minWidth="309.0" prefHeight="31.0" prefWidth="309.0" promptText="Password"  />
       <Label layoutX="255.0" layoutY="25.0" text="Log In">
          <font>
            <Font size="26.0" />
          </font>
      </Label>
      <Hyperlink fx:id="CongdongJava" onAction="#goCDJ"  text="CongdongJava.com" />
      <Label layoutX="205.0" layoutY="60.0" prefHeight="31.0" prefWidth="272.0"  text="Welcome to CDJ">
         <font>
            <Font size="25.0" />
         </font>
      </Label>
      <Label layoutX="55.0" layoutY="290.0" prefHeight="17.0" prefWidth="550.0" text="What you need is to join CongdongJava" >
         <!-- embedded CSS -->
         <style>
            -fx-text-fill: red;
            -fx-font-size: 25.0;
            -fx-font-weight: bold;
         </style>
      </Label>
      <JoeButton fx:id="butLogin" layoutX="180.0" layoutY="330.0" onAction="#login"
                 prefHeight="25.0" prefWidth="220.0" text="LogIn">
      </JoeButton>
   </children>
</AnchorPane>
Explanation:
  • line 1-8: declaration of JFX components
  • line 10: the above-mentioned Customized JoeButton
  • line 12-15: the bearing pane (here AnchorPane) with the neccessary attributes (fx-ID, Controller, height, width, etc.)
  • line 16-18: inline CSS for the AnchorPane Background Image (RedRiverDelta of Vietnam)
  • line 20: shows you how to embed a Web link to CongdongJava and what-to-do method goCDJ in case of onAction
  • line 21-22: the usual LOGIN schemata (TextField, PasswordField) and attributes
  • line 23-32: Label with Font embedding
  • line 33-40: Label with inline CSS coding
  • line 41-43: JoeButton declaration with the text "LogIn"
To the model Login.fxml is the Controller LoginController.java
PHP:
import javafx.fxml.*;
import javafx.event.*;
import javafx.stage.Stage;
import javafx.scene.control.*;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.*;
// Joe Nartca
public class LoginController {
  @FXML private AnchorPane aPane;
  @FXML private joe.JoeButton butLogin;
  @FXML private Hyperlink siteCDJ;
  @FXML private TextField Username;
  @FXML private PasswordField Password;
  // method
  @FXML private void goCDJ() {
    try {
      java.awt.Desktop.getDesktop().
          browse(new java.net.URI("https://congdongjava.com/forum/"));
    } catch (Exception ex) {
      error("Cannot access CongdongJava.com");
    }
  }
  @FXML private void login(ActionEvent e) {
    if(Username.getText().isEmpty()) {
      error("Please enter your name!");
      return;
    } else if(Password.getText().isEmpty()) {
      error("Please enter your password!");
      return;
    }
    try {
      FXMLLoader fxml = new FXMLLoader(getClass().getResource("svg.fxml"));
      Pane pane = fxml.load();       // fetch the new model svg.fxml
      aPane.getChildren().clear();   // remove the old model
      aPane.getChildren().add(pane); // insert the new model
    } catch (Exception ex) {
      ex.printStackTrace();
      error("Cannot load svg.fxml");
    }
  }
  private void error(String err) {
    alert = new Alert(Alert.AlertType.ERROR);
    alert.setTitle("Error!");
    alert.setHeaderText("Invalid Input");
    alert.setContentText(err);
    alert.showAndWait();
  }
  private Alert alert;
}
Explanation:
  • line 9-13: the references to the in Login.fxml declared JFX and JFX-Customized Components (see fx:id=....")
  • line 15-22: the method goCDJ declared in Login.fxml for the Hyperlink in case of onAction
  • line 23-40: the method login declared in Login.fxml for JoeButton in case of onAction. In this method the next FXML model (here: svg.fxml) is loaded and replaced the old model contained in AnchorPane (pass to the next model)
  • the rest is the internal processing method
The view MultipleFXML.java
PHP:
import javafx.application.*;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.layout.AnchorPane;

public class MultipleFXML extends Application {
  public void start(Stage stage) throws Exception {
    FXMLLoader fxml = new FXMLLoader(getClass().getResource("Login.fxml"));
    stage.setScene(new Scene(fxml.load()));
    stage.show();
  }
}
Explanation:
  • line 8: load the login.fxml model
  • line 9, 10: load the Scene with AnchorPane and start the show().
The "next" FXML model is the svg.fxml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Pane?>
<!-- this is Joe's JoeButton -->
<?import joe.JoeButton?>

<Pane fx:id="pane" prefHeight="408.0" prefWidth="520.0"
            xmlns="http://javafx.com/javafx/8"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="SVGController">
  <children>
    <Label layoutX="190.0" layoutY="200.0" prefHeight="17.0"
           prefWidth="550.0" text="Click START to see" >
      <!-- embedded CSS -->
      <style>
         -fx-text-fill: red;
         -fx-font-size: 25.0;
         -fx-font-weight: bold;
      </style>
    </Label>
    <JoeButton fx:id="Play" layoutX="190.0" layoutY="340.0" onAction="#play" text="START">
    </JoeButton>
    <JoeButton fx:id="Back" layoutX="310.0" layoutY="340.0" onAction="#back" text="EXIT">
    </JoeButton>
  </children>
</Pane>
Explanation:
  • line 12-20: Label with inline CSS coding
  • line 21-24: two JFX customized JoeButtons
and the Controller SVGController.java
PHP:
import javafx.fxml.*;
import javafx.scene.shape.*;
import javafx.scene.layout.*;
import javafx.animation.PathTransition;
import javafx.animation.*;
import javafx.util.*;
import javafx.scene.paint.Color;

// Joe Nartca
public class SVGController {
  @FXML private Pane pane;
  @FXML private void play() {
    String svg = "M 100 100 L 300 100 L 200 300 z";
    SVGPath path = new SVGPath();
    path.setContent(svg);
    path.setStrokeWidth(2);
    path.setStroke(Color.RED);
    path.setFill(Color.YELLOW);

    // JFX Graphic CIRCLE that can be used as SVG Circle as well
    Circle circle = new Circle(10, Color.BLUE);
    circle.setLayoutX(170);
    circle.setLayoutY(150);
          
    pane.getChildren().addAll(path, circle);

    PathTransition pt = new PathTransition();
    pt.setDuration(Duration.millis(10000));
    pt.setNode(path);
    pt.setPath(circle);

    pt.setOrientation(javafx.animation.PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
    pt.setCycleCount(javafx.animation.Animation.INDEFINITE);
    pt.setInterpolator(Interpolator.LINEAR);
    pt.play();
  }
  @FXML private void back() {
    javafx.application.Platform.exit();
  }
}
Explanation:
  • line 11: the Model bearing Pane
  • line 12-39: two methods (#play and #back) declared in the svg.fxml model
And if you have successfully compiled the sources you will get this second view (after filling the Username and Password with any string):

next.jpg

and after 2x clicking starts:

svg.jpg

Enjoy
Joe

PS. to run this app with the background image you have to have another image or download this "RedRiverDelta.jpg"
 

Attachments

Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,958
1,323
113
(cont.)

The first option shows you how to FXMLLoad a model (svg.fxml) from a Controller (LoginController). Today I show you how to work with 3 FXML models using a TabPane to view 2 tabbed models. First of all we have to modify the main app MultipleFXML to Multiple_FXML
PHP:
public class Multiple_FXML extends Application {
  public void start(Stage stage) throws Exception {
    FXMLLoader fxml = new FXMLLoader(getClass().getResource("multipleFXML.fxml")); // << the change
    stage.setScene(new Scene(fxml.load()));
    stage.show();
  }
}
Instead of "login.fxml" we create a TabModel multipleFXML.fxml to present 2 Tabs: Tab_1 and Tab_2.
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<TabPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
         minWidth="-Infinity" prefHeight="438.0" prefWidth="600.0"
         xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1"
         tabClosingPolicy="UNAVAILABLE">
  <tabs>
    <Tab text="Tab_1">
        <content>
          <fx:include fx:id="Tab_1" source="tab1.fxml" />
        </content>
    </Tab>
    <Tab text="Tab_2">
        <content>
          <fx:include fx:id="Tab_2" source="svg.fxml" />
        </content>
    </Tab>
  </tabs>
</TabPane>
Explanation: line 10-19: Tab specification. The term "fx:include" indicates the ID of the source (fx:id) and the source itself: source="..."

Note: The model with the bearing TabPane won't have a Controller. Why? Because the TabPane contains 2 models which include their own Controller and therefore there's no need to create an empty Controller for the main model. To "minimize" my work I reuse the model Login.fxml and modify it to tab1.fxml which just displays an information after a successful "login" (instead of loading the svg.fxml)
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.text.Font?>
<!-- this is Joe's JoeButton -->
<?import joe.JoeButton?>

<AnchorPane fx:id="aPane" prefHeight="408.0" prefWidth="520.0"
            xmlns="http://javafx.com/javafx/8"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="Tab1Controller">
    <style>
       -fx-background-image: url(./RedRiverDelta.jpg)
    </style>
    <children>
       <Hyperlink fx:id="CongdongJava" onAction="#goCDJ"  text="CongdongJava.com" />
       <TextField fx:id="Username" layoutX="140.0" layoutY="90.0" maxWidth="309.0"
                  minWidth="309.0" prefHeight="31.0" prefWidth="309.0"
                  promptText="Username/email" />
       <PasswordField fx:id="Password" layoutX="140.0" layoutY="135.0" maxWidth="309.0"
                  minWidth="309.0" prefHeight="31.0" prefWidth="309.0"
                  promptText="Password"  />
       <Label layoutX="255.0" layoutY="25.0" text="Log In">
          <font>
            <Font size="26.0" />
          </font>
      </Label>
      <Label layoutX="205.0" layoutY="60.0" prefHeight="31.0" prefWidth="272.0"
             text="Welcome to CDJ">
         <font>
            <Font size="25.0" />
         </font>
      </Label>
      <Label layoutX="55.0" layoutY="290.0" prefHeight="17.0" prefWidth="550.0"
             text="What you need is to join CongdongJava" >
         <!-- embedded CSS -->
         <style>
            -fx-text-fill: red;
            -fx-font-size: 25.0;
            -fx-font-weight: bold;
         </style>
      </Label>
      <JoeButton fx:id="butLogin" layoutX="180.0" layoutY="330.0" onAction="#login"
                 prefHeight="25.0" prefWidth="220.0" text="LogIn">
      </JoeButton>
   </children>
</AnchorPane>
Explanation: line 9: the modification: Tab1Controller instead of LoginController. The rest is the same.

The modified LoginController is: Tab1Controller
PHP:
import javafx.fxml.*;
import javafx.event.*;
import javafx.stage.Stage;
import javafx.scene.control.*;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.*;
// Joe Nartca
public class Tab1Controller {
  @FXML private joe.JoeButton butLogin;
  @FXML private Hyperlink siteCDJ;
  @FXML private TextField Username;
  @FXML private PasswordField Password;
  // method
  @FXML private void goCDJ() {
    try {
      java.awt.Desktop.getDesktop().
          browse(new java.net.URI("https://congdongjava.com/forum/"));
    } catch (Exception ex) {
      error("Cannot access CongdongJava.com");
    }
  }
  @FXML private void login(ActionEvent e) {
    if(Username.getText().isEmpty()) {
      error("Please enter your name!");
      return;
    } else if(Password.getText().isEmpty()) {
      error("Please enter your password!");
      return;
    }
   // here is the modification
    alert = new Alert(Alert.AlertType.INFORMATION);
    alert.setTitle("INFORMATION");
    alert.setHeaderText("Welcome to JavaFX");
    alert.setContentText("You've successfully logged in.");
    alert.showAndWait();
  }
  private void error(String err) {
    alert = new Alert(Alert.AlertType.ERROR);
    alert.setTitle("Error!");
    alert.setHeaderText("Invalid Input");
    alert.setContentText(err);
    alert.showAndWait();
  }
  private Alert alert;
}
The Tab_2 with the model svg.fxml and SVGController stay unchanged.

If you have successfully compiled the sources you would get this picture:

multiple.png

svg.png
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,958
1,323
113
(cont.)

Dynamic Expansion of FXML models

Today I show you how to create a JavaFX application with a dynamic expansion of FXML models. In theory it's unlimited because it depends fully on your effort =)) and on the limitation of a TabPane :D Let's reuse the last MMVC with TabPane example and modify the sources according to our need. First: the Multiple_FXML.java
PHP:
// Joe Nartca (C)
public class Multiple_FXML extends Application {
  public void start(Stage stage) throws Exception {
    FXMLLoader fxml = new FXMLLoader(getClass().getResource("multiple_FXML.fxml"));
    AnchorPane ap = fxml.load();
    stage.setScene(new Scene(ap));
    stage.show();
    // dynamic FXML invocation
    ((RootController)fxml.getController()).init(); // << inject the "dynamicism"
  }
}
Explanation: line 9: invoke the RootController and execute the init() method.

Then we slightly modify the model multiple_FXML.fxml so that we could dynamiccally run the TabPane without having to refresh it when Tab is added or removed.
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity"
   minHeight="-Infinity" minWidth="-Infinity" prefHeight="438.0" prefWidth="600.0"
   xmlns="http://javafx.com/javafx/8"
   xmlns:fx="http://javafx.com/fxml/1"
  fx:controller="RootController">
  <children>
    <TabPane fx:id="tabPane"  prefHeight="438.0" prefWidth="600.0"
      <!-- tabClosingPolicy="UNAVAILABLE" --> >
      <tabs>
        <Tab fx:id="tab_1" text="Tab_1">
            <content>
              <fx:include fx:id="tTab1" source="tab1.fxml" />
            </content>
        </Tab>
        <Tab fx:id="tab_2" text="Tab_2">
            <content>
              <fx:include fx:id="tSVG" source="svg.fxml"/>
            </content>
        </Tab>
      </tabs>
    </TabPane>
  </children>
</AnchorPane>
Explanation: line 7-11: AnchorPane is now the root that bears the TabPane. The Controller is RootController.java
PHP:
public class RootController {
  @FXML private AnchorPane root;
  @FXML private TabPane tabPane;
  // Rule: fx:id="tSVG" + "Controller" = tSVGController
  @FXML private SVGController tSVGController;
  public void init( ) {
    tSVGController.setRoot(root, tabPane); // << inject the Root and TabPane into SVG model
  }
}
Explanation:
  • line 2-3: the declared FXML JavaFX objects (AnchorPane and TabPane)
  • line 4: the Rule for an injection into a Tab of TabPane is Tab-Content ID (fx:include fx:id="tSVG") concatenated with the reserved word "Controller". Together: tSVGController is the declared FXML controller for the included model (here: svg.fxml)
  • line 5: is the declared FXML controller SVGController for the model multiple_FXML.fxml
Now we have the main app. The next is to modify the SVG model so that we can dynamically expand the TabPane as many as we need. First we change the ID and the name of EXIT JoeButton to GAME and its corresponding method (#back to #game). The rest stays unchanged.
Code:
    <JoeButton fx:id="Game" layoutX="310.0" layoutY="340.0" onAction="#game" text="GAME">
    </JoeButton>
PHP:
public class SVGController {
  @FXML private Pane pane;
  private TabPane tp;
  private AnchorPane root;
  public void setRoot(AnchorPane root, TabPane tp) {
    this.root = root;
    this.tp = tp;
  }
  @FXML private void game() { // new method
    // dynamically create a new Tab
    try {
      FXMLLoader fxml = new FXMLLoader(getClass().getResource("tictactoe.fxml"));
      Tab tab = new Tab("Game", fxml.load());  // get & load the model tictactoe
      tp.getTabs().add(tab);                         // add to TabPane
      tp.getSelectionModel().select(tab);            // set to this tab
    } catch (Exception ex) {
      Alert alert = new Alert(Alert.AlertType.ERROR);
      alert.setTitle("Error!");
      alert.setHeaderText("Invalid Input");
      alert.setContentText("Cannot load tictactoe.fxml");
      alert.showAndWait();
    }
  }
  @FXML private void play() {
    ....
  }
}
Finally we create a new model for the game, says TicTacToe (see this Section JavaFX with FXML for the complete source codes): the tictactoe.fxml
Code:
<VBox fx:id="vbox" alignment="center" spacing="5"
    maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
    prefHeight="438.0" prefWidth="325.0" xmlns:fx="http://joenartca.com/fxml"
    fx:controller="TTTController">
        .....
   <stylesheets>
       <URL value="@joe.css" />
   </stylesheets>
</VBox>
PHP:
public class TTTController {
    @FXML HBox bBox;
    @FXML Label aLabel;
    @FXML TextArea report;
    @FXML Button newGame, b00, b01, b02, b10, b11, b12, b20, b21, b22; 
    //
    public void play( ) {.... }
    //
    @FXML public void actionMove(ActionEvent ev) { .... }
    //
    @FXML public void actionNew( ) { .... }
    //
    private int getRow(Button b) { .... }
    //
    private int getCol(Button b) { ... }
}
And here is the result:

dynmaic1.png

Select: Tab_2
dynmaic2.png

Click GAME
dynmaic3.png

Joe
 
Sửa lần cuối: