Задача / поток JavaFX не заполняют табличное представление последовательно - PullRequest
0 голосов
/ 27 апреля 2019

Итак, мне нужно заполнить табличное представление, используя поток JavaFX, но таблица заполняется только ~ 70% времени.Я смотрю на свой код, и я действительно не могу найти, откуда проблема, я предполагаю, что задача каким-то образом выполняется до того, как данные успешно извлечены / обработаны из БД.Спасибо заранее :)

private Executor exec;
private ObservableList<User> cellData = FXCollections.observableArrayList();
.
.
.
public void fillTable(HashMap<String,Object> whereClause){
        Task<List<User>> task = new Task<List<User>>(){
            @Override
            public ObservableList<User> call(){
                cellData.clear();
                cellData.addAll(userRepository.getAll(whereClause));
                userId.setCellValueFactory(new PropertyValueFactory<>("userID"));
                userName.setCellValueFactory(new PropertyValueFactory<>("userName"));
                userMail.setCellValueFactory(new PropertyValueFactory<>("userMail"));
                userPhone.setCellValueFactory(new PropertyValueFactory<>("userPhone"));
                isAdmin.setCellValueFactory(cellData -> {
                    String isAdminAsString = cellData.getValue().isAdmin() ? "Admin" : "Medic";
                    return new ReadOnlyStringWrapper(isAdminAsString);
                });
                isDeleted.setCellValueFactory(cellData -> {
                    String isActiveUser = cellData.getValue().isDeleted() ? "No" : "Yes";
                    return new ReadOnlyStringWrapper(isActiveUser);
                });
                logger.info("Cell values set");
                return cellData;
            }
        };
        exec.execute(task);
        task.setOnFailed(e -> System.out.println(task.getException().getMessage()));
        task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));
        logger.info("Fill user Table Task executed");

1 Ответ

3 голосов
/ 27 апреля 2019

Вы не даете достаточно контекста для правильного, полностью уверенного ответа, но я предполагаю, что вы сталкиваетесь с проблемами, связанными с потоками.JavaFX не является потокобезопасным;использование неправильного потока для обновления пользовательского интерфейса может привести к неопределенному поведению , так как данные появляются только в ~ 70% случаев.В JavaFX есть важное правило, которому вы должны всегда следовать:

  • Никогда не читайте и не записывайте состояние объектов, которые прямо или косвенно связаны с графом живой сцены напоток, отличный от Поток приложения JavaFX .

Ваш код не соответствует этому правилу.Внутри call метода вашего Task вы структурно модифицируете cellData и устанавливаете cellValueFactory различных TableColumn с.Это приводит к тому, что указанные объекты модифицируются тем потоком, который выполняет Task.Если Executor является какой-либо подсказкой, этот поток определенно не является Потоком приложения JavaFX .

Я не уверен, почему вы устанавливаете cellValueFactory вашего TableColumns внутри метода call в первую очередь.Фабрика значений ячеек - это конфигурация, которую необходимо выполнить только один раз - при создании TableColumn (или вскоре после этого).Другими словами, настройка фабрики значений ячеек в методе call неверна не только потому, что это происходит в фоновом потоке, но и потому, что это происходит каждый раз, когда вы выполняете Task.Удалите код set-the-cell-value-factory из метода call и переместите его, если необходимо, туда, где вы создаете TableColumn s.Если вы используете FXML, и TableColumn s созданы для вас и введены, то метод контроллера initialize является хорошим местом для такого рода конфигурации.

Ваш список cellData подключенна ваш TableView, если не сначала, то определенно после первого успешного выполнения вашего Task.Изменение cellData в фоновом потоке уведомит TableView об этих изменениях в том же потоке (слушатели вызываются в том же потоке, который внес изменение).Самое простое решение - заставить ваш Task вернуть новый List, а затем обновить TableView в случае успеха.

Task<List<User>> task = new Task<List<User>>() {
    @Override protected List<User> call() throws Exception {
        return userRepository.getAll(whereClause);
    }
});
task.setOnSucceeded(event -> userTable.getItems().setAll(task.getValue()));
task.setOnFailed(event -> task.getException().printStackTrace());
exec.execute(task);

setAll метод ObservableListсначала очистит список, затем добавит все элементы данной коллекции (или массива).Это несколько более эффективно, чем вызов clear с последующим addAll, потому что это приводит только к одному событию изменения.Кроме того, если вы хотите продолжать использовать cellData, вы можете, предполагая, что вы ранее установили его как элементы своей таблицы;вместо этого просто используйте cellData.setAll(task.getValue()).


Что касается использования:

task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));

Поскольку вы явно ожидаете, что ObservableList<User> будет возвращено, вы должны использовать Task<ObservableList<User>>вместо Task<List<User>>.Это будет означать, что getValue() возвращает ObservableList<User>, и, следовательно, приведение становится ненужным.Однако, если вы последуете совету выше, это не имеет значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...