不要公开 UI 控件或维护对其他控制器的引用。相反,在控制器中公开一些可观察的数据,并使用绑定将所有内容绑定在一起。
例如,假设您有一个 Table.fxml 文件,其中显示了 TableView<Person>(Person 只是示例数据类)并具有相应的控制器:
public class TableController {
@FXML
private TableView<Person> table ;
private final ReadOnlyObjectWrapper<Person> selectedPerson = new ReadOnlyObjectWrapper<>();
public ReadOnlyObjectProperty<Person> selectedPersonProperty() {
return selectedPerson.getReadOnlyProperty() ;
}
public final Person getSelectedPerson() {
return selectedPersonProperty().get();
}
public void initialize() {
selectedPerson.bind(table.getSelectionModel().selectedItemProperty());
}
}
还有第二个 FXML(可能包含用于编辑与表中所选项目关联的数据的文本字段)Editor.fxml 以及相应的EditorController 类:
public class EditorController {
@FXML
private TextField nameTextField ;
private ObjectProperty<Person> person = new SimpleObjectProperty<>();
public ObjectProperty<Person> personProperty() {
return person ;
}
public final Person getPerson() {
return personProperty().get();
}
public final void setPerson(Person person) {
personProperty().set(person);
}
public void initialize() {
// update text field bindings when person changes:
personProperty().addListener((obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
oldPerson.nameProperty().unbindBidirectional(nameTextField.textProperty());
}
if (newPerson != null) {
newPerson.nameProperty().bindBidirectional(nameTextField.textProperty());
}
}
}
}
现在,当您加载 FXML 文件时,您只需绑定两个公开的属性:
FXMLLoader tableViewLoader = new FXMLLoader(getClass().getResource("Table.fxml"));
Parent tableView = tableViewLoader.load();
TableController tableController = tableViewLoader.getController();
FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("Editor.fxml"));
Parent editorView = editorLoader.load();
EditorController editorController = editorLoader.getController();
// assemble views...
// bind properties from controllers:
editorController.personProperty().bind(tableController.selectedPersonProperty());
这里有多种管理细节的方法,例如您可以使用侦听器而不是绑定来获得更多关于何时更新值等的控制权。但基本思想是从控制器公开必要的数据并观察它,而不是将不同的控制器紧密耦合在一起。
如果有足够的数据需要在两个控制器之间共享而变得笨拙,那么您可以将这些数据捆绑到 Model 类中,并使用完全相同的技术在控制器之间共享模型。如果您愿意放弃在 FXML 中使用 fx:controller 属性设置控制器,您可以让控制器在其构造函数中接受 Model 引用;例如
public class TableController {
@FXML
private TableView<Person> table ;
private Model model ;
public TableController(Model model) {
this.model = model ;
}
public void initialize() {
table.getSelectionModel().selectedItemProperty().addListener((obs, oldPerson, newPerson)
-> model.setSelectedPerson(newPerson));
}
}
而编辑器只是观察模型中的属性:
public class EditorController {
private Model model ;
@FXML
private TextField nameTextField ;
public EditorController(Model model) {
this.model = model ;
}
public void initialize() {
model.selectedPersonProperty().addListener((obs, oldPerson, newPerson)
-> nameTextField.setText(newPerson.getName()));
}
}
那么汇编代码是这样的
Model model = new Model();
TableController tableController = new TableController(model);
FXMLLoader tableLoader = new FXMLLoader(getClass().getResource("Table.fxml"));
tableLoader.setController(tableController);
Parent tableView = tableLoader.load();
EditorController editorController = new EditorController(model);
FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("Editor.fxml"));
editorLoader.setController(editorController);
Parent editorView = editorLoader.load();
// assemble views...
在这个版本中,FXML 文件不能有fx:controller 属性。
另一个变体可以在FXMLLoader 上设置控制器工厂,以便它加载由fx:controller 属性定义的类,但您可以控制它如何加载它们(即它可以将模型传递给构造函数)。
最后,您可以考虑使用专用的依赖注入框架将模型注入控制器。 afterburner.fx 非常适合这个。