从控制器访问域对象

时间:2014-05-01 19:08:17

标签: java model-view-controller javafx javafx-8 fxml

根据this文档,FXML可被视为MVC的“视图”部分。域对象在Java端定义(“模型”)。我找不到“M”和“C”之间的连接 - 我想从控制器修改一些域对象(或启动修改):“M”< - “C”

但是这样的代码与我调用FXMLLoader.load()的地方无关:

public class FXMLTableViewController {
    @FXML private TableView<Person> tableView;
    @FXML private TextField firstNameField;
    @FXML private TextField lastNameField;
    @FXML private TextField emailField;

    @FXML
    protected void addPerson(ActionEvent event) {
        ObservableList<Person> data = tableView.getItems();
        data.add(new Person(firstNameField.getText(),
            lastNameField.getText(),
            emailField.getText()
        ));

        firstNameField.setText("");
        lastNameField.setText("");
        emailField.setText("");
    }
}

此代码与其他应用程序代码完全分开。建立这种联系的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

不要使用静态FXMLLoader.load(URL)方法。而是创建一个FXMLLoader实例。然后,您可以自己实例化控制器并调用setController(...),也可以设置控制器工厂。

使用setController(...)

假设你有一些模型类,称之为Model。定义控制器以引用它:

public class MyController {
    private final Model model ;

    // usual @FXML-annotated fields, etc

    public MyController(Model model) {
        this.model = model ;
    }

    public void initialize() { ... }

    // handler methods, etc...
}

现在,从您的FXML文件中删除fx:controller属性,而不是执行以下操作:

final Model model = new Model();

FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("path/to/fxml/file.fxml"));
loader.setController(new MyController(model));
Parent root = loader.<Parent>load();

使用setControllerFactory(...)

如果您想要或需要使用fx:controller属性(例如,如果您在FXML中使用<fx:include ...>标签并注入嵌套控制器(可能还需要访问模型),可以改为指定一个控制器工厂,它实际上是一个将控制器类型映射到控制器实例的函数。 FXMLLoader将使用它来确定如何根据您在fx:controller属性中指定的类名创建对象。

例如:

final Model model = new Model();

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file.fxml"));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
    @Override
    public Object call(Class<?> type) {
        try {
             for (Constructor<?> constructor : type.getConstructors()) {
                 if (constructor.getParameterCount()==1 && 
                         constructor.getParameterTypes()[0]==Model.class) {
                     return constructor.newInstance(model);
                  }
             }
             // no matching constructor found, just call no-arg constructor as default:
             return type.newInstance();
        } catch (Exception exc) {
            exc.printStackTrace();
            return null ; // bail...
        }
    }
});
Parent root = loader.<Parent>load();

请注意,使用此版本:

  1. 包含的任何FXML文件都将使用相同的控制器工厂,因此如果其控制器的构造函数采用类型为Model的单个参数,则它们将接收对同一Model实例的引用
  2. 如果您在应用程序中使用多个FXMLLoader,则可以重复使用相同的控制器工厂将相同的Model实例传递给加载器,这样所有控制器都可以访问相同的{{1}实例。
  3. 关于控制器工厂的其他想法

    控制器工厂是一个非常强大和灵活的机制。例如,定义一个简单地推迟到Spring Model的控制器工厂将非常容易。这样您就可以为控制器定义接口,只需在FXML文件中指定接口名称即可。然后,您的Spring配置文件可以确定要使用的控制器接口的哪个实现,当然可以为您将模型(和域对象)注入控制器。

    另见

    如果您正在做很多事情,请查看Adam Bien's afterburner framework。 Adam定义了一个可重用的控制器工厂,它可以加载控制器并检查其中ApplicationContext个带注释的字段,并将单例实例注入这些字段。这提供了很大的灵活性,因为您可以轻松地向控制器添加更多共享资源。

答案 1 :(得分:1)

您可以创建FXMLLoader的实例并使用非静态加载方法(load(java.io.InputStream inputStream)load())。之后,您可以使用getController()方法获取控制器。