JavaFX控制器到控制器 - 访问UI控件

时间:2014-12-19 10:13:46

标签: controller javafx controls

在研究了我的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);

是否可以通过这种方式设置值,何时可以设置? -

是否有其他(更好)的方式来满足这种需求?

2 个答案:

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

}