不使用FXML使用JavaFX控制器

10

是否有可能在JavaFX GUI中使用控制器而不使用FXML?

我注意到FXML文件包含一个"fx-controller"属性来绑定控制器,但我觉得这不是一个容易处理的方式。

是否有什么想法可以在JavaFX中使用MVC架构而不使用FXML文件或JavaFX Scene Builder?


你不必使用FXML或SceneBuilder。你可以自己创建对象并将它们添加到你的场景/舞台中。你完全可以自由决定如何实现它。我已经实现了一个屏幕管理库,它可以处理FXML或手动创建的屏幕。这是可行的 :) - ManoDestra
3个回答

36

我觉得你的问题并不是特别清晰:你只是创建了一些类,然后用监听器将它们联系在一起。我不知道这是否有所帮助,但以下是一个简单的例子,它只有几个文本字段和一个显示它们总和的标签。这就是我认为的"经典MVC":视图观察模型,如果模型发生变化,则更新UI元素。它在UI元素上注册处理程序,并在事件发生时委托给控制器:控制器反过来处理输入(如果需要)并更新模型。

模型:

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();
    }



}

控制器:

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);
    }
}

查看:

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 ;
        }));
    }
}

应用程序类:

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);
    }
}

2
这就是我所说的。在JavaFX上使用“Classical MVC”而不使用FXML;谢谢,这很公平。 - Hassam Abdelillah
1
你为什么不直接在你的视图类中扩展GridPane呢?为什么要创建getAsParent方法呢? - hubbabubba
6
基础API设计。如果视图扩展了GridPane,那么客户端代码可以将其实例分配给变量,类型为GridPane,例如GridPane view = new AdditionView(...);。这意味着我永远不能更改使用不同的布局。布局应该是视图类的实现细节,而不是公共API的一部分。按照我的设置,我只保证它是某种“父类”,因此以后可以更改为使用不同的布局,而不影响任何其他代码。 - James_D
@petersaints 我认为你所描述的是模型-视图-表示器(MVP), 这就是我会说FXML方法基于此的原因。这是一个完全合适的设计模式,但它并不是“经典”的MVC。 - James_D
@petersaints:“视图只能呈现模型,订阅模型更改...并向负责操作模型的控制器分派用户生成的事件”。这难道不正是它所做的吗? - James_D
显示剩余2条评论

4

我广泛使用JavaFX,不使用FXML或scenebuilder。因此,我可以保证它是可行的。

以下是我的IDE生成的自动代码,用于获取JavaFX主类。这将是你的应用程序的根。然后你可以在它上面添加内容来创建你的应用程序。

public class NewFXMain extends Application {

@Override
public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Say 'Hello World'");
    btn.setOnAction(new EventHandler<ActionEvent>() {

        @Override
        public void handle(ActionEvent event) {
            System.out.println("Hello World!");
        }
    });

    StackPane root = new StackPane();
    root.getChildren().add(btn);

    Scene scene = new Scene(root, 300, 250);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

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

}

1
谢谢你的回答,但我想知道的是如何在没有FXML的情况下将控制器绑定到视图,因为我希望它成为MVC三层。 - Hassam Abdelillah
3
@HassamAbdelillah 什么视图?如果您没有使用FXML,则没有任何绑定。您只需直接将UI组件的调用传递给控制器组件即可。当您创建组件时,无论如何都会在代码中将它们的操作绑定到控制器操作,因此在这种情况下没有任何需要绑定的内容。您只需要该方法来处理FXML。您可以在Jose上面的答案中看到将操作绑定到控件的操作。 - ManoDestra

1

对于我们中的其他人...这里有一个非常简单的例子,展示如何创建一个JavaFX表单,而不使用任何FXML文件。此示例可用于已经运行的应用程序中,因此我跳过了Main类等内容...它只是为了显示JavaFX的简单性。

简而言之,您只需基于容器(例如AnchorPane)创建场景,然后创建您的Stage并将Scene分配给该Stage...添加控件,然后显示该Stage即可。

package javafx;

import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class SimpleFX {

    private AnchorPane anchorPane;

    private TextArea textArea () {
        TextArea textArea = new TextArea();
        textArea.setLayoutX(20);
        textArea.setLayoutY(20);
        textArea.setMaxWidth(450);
        textArea.setMinHeight(380);
        return textArea;
    }

    private TextField textField () {
        TextField textField = new TextField();
        textField.setLayoutX(20);
        textField.setLayoutY(410);
        textField.setMinWidth(450);
        textField.setMinHeight(25);
        return textField;
    }

    private Button button() {
        Button button = new Button("Button");
        button.setLayoutX(240);
        button.setLayoutY(450);
        return button;
    }


    private void addControls    () {
        anchorPane.getChildren().add(0,textArea());
        anchorPane.getChildren().add(1,textField());
        anchorPane.getChildren().add(2,button());
    }

    public void startForm () {
        anchorPane = new AnchorPane();
        Scene scene = new Scene(anchorPane, 500, 500);
        Stage stage = new Stage();
        stage.setScene(scene);
        addControls();
        stage.show();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接