Search code examples
javajavafx-8fxml

Using JavaFX controller without FXML


Is there a possibility to use a controller with a JavaFX GUI without using FXML.

I noticed that the FXML file contains an fx-controller attribute to bind the controller but i don't find it an easy way to work with it.

Any ideas about have an MVC arch with JavaFX without using the FXML file or JavaFX Scene Builder ?


Solution

  • Your question isn't particularly clear to me: you just create the classes and basically tie everything together with listeners. I don't know if this helps, but here is a simple example that just has a couple of text fields and a label displaying their sum. This is what I regard as "classical MVC": the view observes the model and updates the UI elements if the model changes. It registers handlers with the UI elements and delegates to the controller if events happen: the controller in turn processes the input (if necessary) and updates the model.

    Model:

    package mvcexample;
    
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ReadOnlyIntegerWrapper;
    import javafx.beans.property.SimpleIntegerProperty;
    
    public class AdditionModel {
        private final IntegerProperty x = new SimpleIntegerProperty();
        private final IntegerProperty y = new SimpleIntegerProperty();
        private final ReadOnlyIntegerWrapper sum = new ReadOnlyIntegerWrapper();
    
        public AdditionModel() {
            sum.bind(x.add(y));
        }
    
        public final IntegerProperty xProperty() {
            return this.x;
        }
    
        public final int getX() {
            return this.xProperty().get();
        }
    
        public final void setX(final int x) {
            this.xProperty().set(x);
        }
    
        public final IntegerProperty yProperty() {
            return this.y;
        }
    
        public final int getY() {
            return this.yProperty().get();
        }
    
        public final void setY(final int y) {
            this.yProperty().set(y);
        }
    
        public final javafx.beans.property.ReadOnlyIntegerProperty sumProperty() {
            return this.sum.getReadOnlyProperty();
        }
    
        public final int getSum() {
            return this.sumProperty().get();
        }
    
    
    
    }
    

    Controller:

    package mvcexample;
    
    public class AdditionController {
    
        private final AdditionModel model ;
    
        public AdditionController(AdditionModel model) {
            this.model = model ;
        }
    
        public void updateX(String x) {
            model.setX(convertStringToInt(x));
        }
    
        public void updateY(String y) {
            model.setY(convertStringToInt(y));
        }
    
        private int convertStringToInt(String s) {
            if (s == null || s.isEmpty()) {
                return 0 ;
            }
            if ("-".equals(s)) {
                return 0 ;
            }
            return Integer.parseInt(s);
        }
    }
    

    View:

    package mvcexample;
    
    import javafx.geometry.HPos;
    import javafx.geometry.Pos;
    import javafx.scene.Parent;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.control.TextFormatter;
    import javafx.scene.control.TextFormatter.Change;
    import javafx.scene.layout.ColumnConstraints;
    import javafx.scene.layout.GridPane;
    import javafx.scene.layout.Priority;
    
    public class AdditionView {
        private GridPane view ;
        private TextField xField;
        private TextField yField;
        private Label sumLabel;
    
        private AdditionController controller ;
        private AdditionModel model ;
    
        public AdditionView(AdditionController controller, AdditionModel model) {
    
            this.controller = controller ;
            this.model = model ;
    
            createAndConfigurePane();
    
            createAndLayoutControls();
    
            updateControllerFromListeners();
    
            observeModelAndUpdateControls();
    
        }
    
        public Parent asParent() {
            return view ;
        }
    
        private void observeModelAndUpdateControls() {
            model.xProperty().addListener((obs, oldX, newX) -> 
                    updateIfNeeded(newX, xField));
    
            model.yProperty().addListener((obs, oldY, newY) -> 
                    updateIfNeeded(newY, yField));
    
            sumLabel.textProperty().bind(model.sumProperty().asString());
        }
    
        private void updateIfNeeded(Number value, TextField field) {
            String s = value.toString() ;
            if (! field.getText().equals(s)) {
                field.setText(s);
            }
        }
    
        private void updateControllerFromListeners() {
            xField.textProperty().addListener((obs, oldText, newText) -> controller.updateX(newText));
            yField.textProperty().addListener((obs, oldText, newText) -> controller.updateY(newText));
        }
    
        private void createAndLayoutControls() {
            xField = new TextField();
            configTextFieldForInts(xField);
    
            yField = new TextField();
            configTextFieldForInts(yField);
    
            sumLabel = new Label();
    
            view.addRow(0, new Label("X:"), xField);
            view.addRow(1, new Label("Y:"), yField);
            view.addRow(2, new Label("Sum:"), sumLabel);
        }
    
        private void createAndConfigurePane() {
            view = new GridPane();
    
            ColumnConstraints leftCol = new ColumnConstraints();
            leftCol.setHalignment(HPos.RIGHT);
            leftCol.setHgrow(Priority.NEVER);
    
            ColumnConstraints rightCol = new ColumnConstraints();
            rightCol.setHgrow(Priority.SOMETIMES);
    
            view.getColumnConstraints().addAll(leftCol, rightCol);
    
            view.setAlignment(Pos.CENTER);
            view.setHgap(5);
            view.setVgap(10);
        }
    
        private void configTextFieldForInts(TextField field) {
            field.setTextFormatter(new TextFormatter<Integer>((Change c) -> {
                if (c.getControlNewText().matches("-?\\d*")) {
                    return c ;
                }
                return null ;
            }));
        }
    }
    

    Application class:

    package mvcexample;
    
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class MVCExample extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            AdditionModel model = new AdditionModel();
            AdditionController controller = new AdditionController(model);
            AdditionView view = new AdditionView(controller, model);
    
            Scene scene = new Scene(view.asParent(), 400, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }