Невозможно принудительно обновить JavaFX TableView из другого окна / контроллера - PullRequest
1 голос
/ 30 марта 2019

У меня два окна и два контроллера.

В первом окне есть кнопка, которая открывает второе окно и TableView. Второе окно - это форма, куда я вставляю свойства моего класса. После вставки я нажимаю «Сохранить» и добавляю новый созданный объект в статический список. Это должно быть показано в TableView. Когда я использую только одно окно и один контроллер, все работает нормально, но с разделением функциональности, как описано в двух окнах, TableView не обновляется.

Вот мой минимальный пример:

ГЛАВНЫЙ КОНТРОЛЛЕР

public class Controller {

    @FXML
    public TableView tableView;

    @FXML
    private Button openWindow;


    // called by the FXML loader after the labels declared above are injected:
    public void initialize() {

        TableColumn mailColumn = new TableColumn("E-Mail");
        tableView.getColumns().addAll(mailColumn);
        mailColumn.setCellValueFactory(new PropertyValueFactory<Person,String>("mail"));


        openWindow.setOnAction((event) -> {
            new PersonInputForm(event);
        });


    }

    // ######   Receiver TableView Action Handling #######
    public void updateReceiverList(){
        final ObservableList<Person> data = FXCollections.observableArrayList(Memory.receiverList);
        tableView.getItems().clear();
        tableView.getItems().addAll(data);
    }

}

ВТОРИЧНЫЙ КОНТРОЛЛЕР

public class PersonInputFormController {


    @FXML
    private TextField mail;

    @FXML
    private AnchorPane personInputFormAnchorPane;

    private Controller mainController ;
    Stage stage;

    public void setStage() {
        stage = (Stage) personInputFormAnchorPane.getScene().getWindow();
    }

    public void setMainController(Controller mainController){
        this.mainController = mainController;
    }

    public void save(){
        Memory.saveReceiver(mail);
        mainController.updateReceiverList();
        stage.close();
    }


}

ОСНОВНОЕ ОКНО

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{

        FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
        Parent root = loader.load();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

ВТОРИЧНОЕ ОКНО

public class PersonInputForm {

    public PersonInputForm(ActionEvent event) {

        Stage stage = new Stage();

        FXMLLoader mainFxmlLoader = new FXMLLoader(getClass().getResource("../sample.fxml"));
        try {
            mainFxmlLoader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Controller mainController = mainFxmlLoader.getController();


        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("PersonInputForm.fxml"));
        Parent root = null;
        try {
            root = (Parent)fxmlLoader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }

        stage.setScene(new Scene(root));
        stage.initOwner(
                ((Node) (event.getSource())).getScene().getWindow() );

        PersonInputFormController controller = fxmlLoader.<PersonInputFormController>getController();
        controller.setStage();
        controller.setMainController(mainController);

        stage.show();

    }
}

ПАМЯТЬ

public class Memory {

    public static sample.Person sender = new sample.Person();
    public static ArrayList<sample.Person> receiverList = new ArrayList();

    static public void saveReceiver(TextField mail){
        Person receiver = new Person();
        receiver.setMail(mail.getText());
        receiverList.add(receiver);
    }

}

ОБЪЕКТ

public class Person {

    private SimpleStringProperty mail = new SimpleStringProperty();

    public String getMail() {
        return mail.get();
    }

    public SimpleStringProperty mailProperty() {
        return mail;
    }

    public void setMail(String mail) {
        this.mail.set(mail);
    }

}

Я нашел следующие похожие темы:

Как установить элементы TableView в другом классе контроллера в JavaFX?

Javafx Обновление таблицы с другого FXML

Но я до сих пор не понимаю, как можно решить мою проблему.

Заранее спасибо!

1 Ответ

0 голосов
/ 31 марта 2019

В вашем конструкторе PersonInputForm class 'вы создаете FXMLLoader для загрузки sample.fxml. Затем вы передаете контроллер, полученный в результате этой нагрузки, - экземпляр Controller - вашему экземпляру PersonInputFormController, который вы создадите позже. Проблема в том, что экземпляр Controller, который вызвал new PersonInputForm(event), не совпадает с экземпляром , который вы передаете PersonInputFormController. Помните, что каждый раз, когда вы звоните FXMLLoader.load, вы создаете новый граф сцены, подключенный к новому экземпляру контроллера.

Поскольку вы звоните new PersonInputForm(event) из экземпляра Controller, и именно в этом экземпляре Controller вам нужно вызвать updateReceiverList() on, самое простое решение - передать this в PersonInputForm как Что ж. Затем удалите код, отвечающий за загрузку sample.fxml.

public PersonInputForm(ActionEvent event, Controller mainController) {
    // code...
    controller.setMainController(mainController);
}

Примечание. Такое поведение в конструкторе кажется неправильным.

Хотя вышесказанное решит вашу проблему (я полагаю), необходимость очищать и заменять элементы TableView не идеальный подход. Взгляните на updateReceiverList():

public void updateReceiverList(){
    // Copies Memory.receiverList into new ObservableList
    final ObservableList<Person> data = FXCollections.observableArrayList(Memory.receiverList);
    tableView.getItems().clear();
    // Copies data into the TableView's items list
    tableView.getItems().addAll(data);
}

Этот код копирует коллекцию в другую коллекцию дважды . Это может стать довольно дорогим, когда у вас есть большое количество элементов. Было бы лучше, если бы у вас была наблюдаемая модель, которую ваши контроллеры могли наблюдать и реагировать на нее.

Например, вы используете ArrayList для хранения / публикации ваших Person s, где вы могли бы использовать ObservableList. Затем вы можете использовать tableView.setItems(Memory.receiverList), и любые обновления, сделанные вашим PersonInputFormController в receiverList, будут автоматически замечены TableView. Поскольку вы, кажется, не используете многопоточность, эта настройка не должна вызывать никаких проблем. Когда или если вы начинаете использовать фоновые потоки, помните, что вы никогда не должны обновлять пользовательский интерфейс - прямо или косвенно - из фонового потока.

Однако использование глобального состояния (то есть общедоступных статических переменных) также не идеально. Я рекомендую ознакомиться с различными архитектурами приложений, такими как: Model-View-Controller (MVC), Model-View-Presenter (MVP), Model-View-ViewModel (MVVP) и т. Д. Затем попробуйте применить одну из них к вашему приложению. по мере необходимости.


Некоторые ссылки:


Я хотел бы отметить еще одну вещь в вашем коде. Вы используете много необработанных типов. Не используйте необработанные типы . Например, вы должны иметь:

@FXML private TableView<Person> tableView;

И

TableColumn<Person, String> mailColumn = new TableColumn<>("E-Mail");
// which lets you use the diamond operator here
mailColumn.setCellValueFactory(new PropertyValueFactory<>("mail"));

И

// You're missing the <> on the right hand side
public static ArrayList<Person> receiverList = new ArrayList<>();
...