【问题标题】:JavaFX controller to controller - access to UI ControlsJavaFX 控制器到控制器 - 访问 UI 控件
【发布时间】:2014-12-19 10:13:45
【问题描述】:

在研究了我的 MVC javaFX - 超过 2 天没有任何解决方案的问题后,我决定现在发布它:

我有一个应用程序窗口,由不同的拆分窗格划分,每个窗格都有自己的 .fxml 格式,由其自己的控制器控制。

我的问题是:如何访问外部 UI 控件? 例如:单击 TableView 行应该会影响填充另一个表单中的文本字段。

我目前(不工作)的解决方案如下:

第一个控制器:提供一个实例

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

是否可以以这种方式设置值?如果可以,如何设置?

是否有其他(更好的)方法可以满足这一需求?

【问题讨论】:

    标签: controller javafx controls


    【解决方案1】:

    不要公开 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 非常适合这个。

    【讨论】:

    • 非常感谢您的及时回答 - 我认为需要几个小时才能了解并应用它 - 看起来非常实用
    • 我试图让它以你的方式运行,但它不起作用 - 你能看看我的例子吗?如何提供源代码?
    【解决方案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);
            });
        }
    
    }
    

    【讨论】:

    • 你能举个例子吗?提前谢谢
    • @heinrichekam 我添加了一个示例,说明我过去是如何做到的。看看这是否适合您的需求。
    猜你喜欢
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 2014-12-03
    • 2016-02-29
    • 2016-05-30
    • 1970-01-01
    相关资源
    最近更新 更多