在研究了我的MVC javaFX - 问题没有任何解决方案超过2天后,我决定发布它:
我有一个应用程序窗口被不同的SplitPanes划分,每个窗格都有自己的.fxml格式,由自己的控制器控制。
我的问题是:如何才能访问外部UI-Controlls? 例如:单击TableView行将影响填充其他表单中的Textfields。
我当前(不工作)的解决方案如下:
第一个控制器:提供实例
public static SpielerController instance;
public SpielerController() {};
public static SpielerController getInstance()
{
if(SpielerController.instance==null)
{
synchronized (SpielerController.class)
{
if(SpielerController.instance == null)
{
SpielerController.instance = new SpielerController();
}
}
}
return SpielerController.instance;
}
第二个控制器:获取实例并调用方法
SpielerController.getInstance().setPID(Integer.toString(pid));
结果是:
可以将值pid传递给调用的方法并将其打印出来(System.out.println(pid);
)
无法设置值,例如TextField1.setText(pid);
是否可以通过这种方式设置值,何时可以设置? -
是否有其他(更好)的方式来满足这种需求?
答案 0 :(得分:3)
不要公开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
类中,并使用完全相同的技术在控制器之间共享模型。如果您愿意放弃使用fx:controller
属性在FXML中设置控制器,则可以让控制器在其构造函数中接受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非常适合这一点。
答案 1 :(得分:2)
MVC模式有许多不同的变体。对于我使用的那个,视图反映了模型。控制器是中间的东西,使这个很复杂。 因此,控制器现在不应该相互关联或直接相互影响。
说string1
是form2中TextField中的内容。它可以根据用户的行为而改变。因此,我认为它的价值应存储在模型层中。 Form2可以监听更改并相应地更新其TextField。当用户单击form1中的TableView-Row时,form1的控制器将更新模型层中的string1
(它有权访问)。 Form2然后完成其余的工作。
Form1现在对form2的结构一无所知。它只知道String1
。这更好地反映了MVC的理想。
如果你需要一个代码示例,请告诉我,我会为你鞭打一个。 编辑:我在这里添加了一个代码示例。请注意,这可能会在静止时得到改善,绝不是最终确定的最终方式。
public class JavaFXApplication23 extends Application {
@Override
public void start(Stage stage1) throws IOException {
final SomeDataObject data = new SomeDataObject();
final Stage stage2 = new Stage();
Parent form1 = load(data, "FXMLDocument_1.fxml");
Parent form2 = load(data, "FXMLDocument_2.fxml");
Scene scene1 = new Scene(form1);
Scene scene2 = new Scene(form2);
stage1.setScene(scene1);
stage1.show();
stage2.setScene(scene2);
stage2.show();
}
private Parent load(SomeDataObject data, String resource) throws IOException {
final FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
final Parent parent = loader.load();
final Controller controller = loader.getController();
controller.setData(data);
return parent;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
public class SomeDataObject {
private final StringProperty stringProp = new SimpleStringProperty("");
public StringProperty getStringProp() {
return stringProp;
}
}
public interface Controller {
void setData(SomeDataObject data);
}
public class Form1Controller implements Controller {
private SomeDataObject data;
@FXML
private void handleButtonAction(ActionEvent event) {
data.getStringProp().set(data.getStringProp().get() + "Merry Christmas!\n");
}
@Override
public void setData(SomeDataObject data) {
this.data = data;
}
}
public class Form2Controller implements Controller {
@FXML
private Label label;
private SomeDataObject data;
@Override
public void setData(SomeDataObject data) {
this.data = data;
label.setText(data.getStringProp().get());
data.getStringProp()
.addListener((ObservableValue<? extends String> observable,
String oldValue, String newValue) -> {
label.setText(newValue);
});
}
}