Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

JavaFX Seed (MVC with FXML + Worker/Tasks + PropertyBinding)

To start off with a client app for building anything serious with JavaFX, I had to go through a bunch of scattered tutorials and sometimes the aim of the tutorials were too microscopic in nature when you are looking for a seed project which exhibits most of the common functionality to get started off with. Some of the JavaFX samples partially covers what I am going to show in this tutorial. FXML views and controllers with scene switching is covered in FXML LoginDemo, Worker/Tasks with Property Binding is covered in the Henley Car Sales sample, Model-View separation is covered in the resources for the book Pro JavaFX 2. I would also like to point out the documentation for JavaFX Architecture and Best Practices for JavaFX 2.

Main

import java.io.InputStream;  
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class BotApp extends Application {

    private Stage stage;

    public static void main(String[] args) {
         Application.launch(BotMain.class, (java.lang.String[])null);
    }

    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setTitle("Sample App");
        gotoLogin();
        stage.show();
    }

    private void gotoLogin() {
        try {
            LoginController login = (LoginController) replaceSceneContent("Login.fxml");
            login.setApp(this);
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    public void gotoSecond() {
        try {
            //SecondController second = (SecondController) replaceSceneContent("Second.fxml");
            //second.setApp(this);
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    private Initializable replaceSceneContent(String fxml) throws Exception {
        FXMLLoader loader = new FXMLLoader();
        InputStream in = BotMain.class.getResourceAsStream(fxml);
        loader.setBuilderFactory(new JavaFXBuilderFactory());
        loader.setLocation(BotMain.class.getResource(fxml));
        AnchorPane page;
        try {
            page = (AnchorPane) loader.load(in);
        } finally {
            in.close();
        }
        Scene scene = new Scene(page, 600, 350);
        stage.setScene(scene);
        stage.sizeToScene();
        return (Initializable) loader.getController();
    }

   public void setClient(Client client) {
        this.client = client;
    }
   
}
 
See the replaceSceneContent function to find out how FXML views are dynamically loaded and the controller attached to the view is returned.

View
<?xml version="1.0" encoding="UTF-8"?>  
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="500.0" styleClass="background" xmlns:fx="http://javafx.com/fxml" fx:controller="LoginController">
    <children>
       <HBox id="hBox1" AnchorPane.leftAnchor="20.0" AnchorPane.rightAnchor="20.0" spacing="10" style="-fx-padding: 10">
          <children>
             <VBox id="vBox1" alignment="BASELINE_LEFT"  spacing="10" style="-fx-padding: 10" prefWidth="350.0" >
                <children>
                  <Label id="label1" text="Provide Login Details" />
                  <Label id="label1" text="Username" />
                  <TextField id="textField1" fx:id="userId" prefWidth="200.0"  />
                  <Label id="label2" text="Password" />
                  <PasswordField id="passwordField1" fx:id="password" prefWidth="200.0" />
                  <Button id="button1" fx:id="login" defaultButton="true" onAction="#processLogin" prefHeight="50.0" prefWidth="200.0" text="Login" />
                </children>
              </VBox>
              <VBox id="vBox2"  spacing="10" style="-fx-padding: 10">
                 <children>
                   <Label id="label3" text="Console" />
                   <TextArea id="console" fx:id="console" />
                 </children>
              </VBox>
           </children>
         </HBox>
      </children>
</AnchorPane>
 
Use the fully qualified class name for the controller. Runtime errors can be caused due to several reasons :- if the FXML file cannot be parsed properly, the controller can’t be found or the function provided for some action is not found in the controller. 

Controller

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;

public class LoginController extends AnchorPane implements Initializable {

    @FXML
    TextField userId;
    @FXML
    PasswordField password;
    @FXML
    Button login;
    @FXML
    TextArea console;

    private LoginModel model;
    private BotApp application;
    private ReadOnlyObjectProperty<Worker.State> stateProperty;

    public void setApp(BotApp application){
        this.application = application;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        model = new LoginModel();
        console.textProperty().bind(model.worker.messageProperty());
        stateProperty = model.worker.stateProperty();
        login.disableProperty().bind(stateProperty.isNotEqualTo(Worker.State.READY));
    }

    public void processLogin(ActionEvent event) throws Exception {
        model.setUsernameAndPassword(userId.getText(), password.getText());
        new Thread((Runnable) model.worker).start();
        stateProperty.addListener(new ChangeListener<State>() {
            public void changed(ObservableValue ov, State oldState, State newState) {
                if (newState == Worker.State.SUCCEEDED) {
                    application.setClient(model.worker.valueProperty().get());
                    application.gotoFollow();
                }
            }
        });
    }
}

Avoid any heavy processing on the JavaFX application thread. Delegate work to Worker threads using Tasks or Services. To interact in any way with the worker threads, use bindings or listeners on the properties of the worker threads. Notice the value property return the type of object associated with the worker.

Model

import javafx.concurrent.Worker; 

public class LoginModel {
        public Worker<Client> worker;

        public LoginModel() {
            worker = new Login();
        }

        public void setUsernameAndPassword(String username, String password) {
            ((Login)worker).setUsernameAndPassword(username, password);
        }

}

 

Task
 
import javafx.concurrent.Task;  

public class Login extends Task<Client>{

    String username = null;                           
    String password = null;
    String loginURL = “https://abc.xyz/login”;                           
    String log = "";

    private Client login() {
      Client client = new Client();   //Wrapper for HttpURLConnection
      String content = client.post(loginURL , payload);
       //doSomething
       //update Progress property

        log += "Payload = " + payload +"\n";
        updateMessage(log);

        //doSomething
        //update properties as required
        return client;
    }

    public void setUsernameAndPassword(String username, String password) {
        this.username = username;
        this.password = password;
    }

     @Override
     protected Client call() throws Exception {
         return login();
     }

}

Use the protected update methods of Task class to set values for progress and messages. You can follow it up with binding properties like attaching one ReadOnlyStringProperty with another ReadOnlyStringProperty as shown in the controller to attach the console text area's text property with the message property of the worker.


This post first appeared on Night Without End, please read the originial post: here

Share the post

JavaFX Seed (MVC with FXML + Worker/Tasks + PropertyBinding)

×

Subscribe to Night Without End

Get updates delivered right to your inbox!

Thank you for your subscription

×