Нужно уточнить изменения данных в потоке приложения javafx - PullRequest
1 голос
/ 17 апреля 2019

Я перенес свой проект на JavaFX и начал сталкиваться с проблемами потоков.Я приложу короткий пример.После долгих поисков мне удалось разобраться в проблеме.Я не могу изменить данные tableView вне потока приложения FX.Я переключил свой код с использования SwingWorker на задачу.

Сначала это работало, пока я не добавил прослушиватель изменений в observableList таблицы.Затем я получил ошибку «Нет в потоке приложения FX;»

Ошибка произошла внутри метода onChanged, когда я попытался обновить значение метки.Я решил это, обернув его внутри Platform.runLater ().

Я просто не понимаю, почему при изменении метки написано, что ее нет в потоке приложения.На каком потоке это работало?Кроме того, правильно ли я добавляю строки в свою таблицу, используя задачу?В моем реальном приложении я мог бы добавить 50 тысяч строк, поэтому отдельный поток не должен блокировать пользовательский интерфейс.

public class Temp extends Application{  
    private ObservableList<String> libraryList = FXCollections.observableArrayList();

    public void start(Stage stage) {

        Label statusLabel = new Label("stuff goes here");

        TableView<String> table = new TableView<String>(libraryList);
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<String, String> col = new TableColumn<String, String>("Stuff");
        col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
        table.getColumns().add(col);

        libraryList.addListener(new ListChangeListener<String>() {
            public void onChanged(Change change) {
                // Problem was caused by setting the label's text (prior to adding the runLater)
                Platform.runLater(()->{
                    statusLabel.setText(libraryList.size()+" entries");
                });                 
            }               
        });

        // dummy stuff
        libraryList.add("foo");
        libraryList.add("bar");

        Button b = new Button("Press Me");
        b.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent e) {
                FileTask task = new FileTask();
                new Thread(task).start();
            }
        });

        BorderPane mainBody = new BorderPane();

        mainBody.setTop(statusLabel);
        mainBody.setCenter(table);
        mainBody.setBottom(b);
        Scene scene = new Scene(mainBody);
        stage.setScene(scene);
        stage.show();       
    }


    class FileTask extends Task<Boolean>{

        public FileTask(){

        }

        protected Boolean call() throws Exception{

            Random rand = new Random();
            for(int i = 0; i < 5; i++) {
                String s = ""+rand.nextInt(Integer.MAX_VALUE);
                libraryList.add(s);
            }

            return true;
        }   
    }     

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

    }
}

Ответы [ 2 ]

1 голос
/ 17 апреля 2019

Рассмотрите возможность инкапсуляции информации, необходимой представлению, в отдельный класс (обычно называемый моделью). Представление должно реагировать на изменения в модели с помощью слушателя или привязки.
Вы можете использовать поток или темы, чтобы обновить модель:

import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Temp extends Application{

    @Override
    public void start(Stage stage) {

        Model model = new Model();

        Label statusLabel = new Label("stuff goes here");

        TableView<String> table = new TableView<>(model.getLibraryList());
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<String, String> col = new TableColumn<>("Stuff");
        col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
        table.getColumns().add(col);
        statusLabel.textProperty().bind(Bindings.concat(model.sizeProperty.asString(), " entries"));

        // dummy stuff
        model.add("foo");  model.add("bar");

        Button b = new Button("Press Me");
        b.setOnAction(e -> {
            FileTask task = new FileTask(model);
            new Thread(task).start();
        });

        BorderPane mainBody = new BorderPane();

        mainBody.setTop(statusLabel);
        mainBody.setCenter(table);
        mainBody.setBottom(b);
        Scene scene = new Scene(mainBody);
        stage.setScene(scene);
        stage.show();
    }

    class Model {

        private final ObservableList<String> libraryList;
        private final IntegerProperty sizeProperty;

        Model(){
            libraryList = FXCollections.observableArrayList();
            sizeProperty = new SimpleIntegerProperty(0);
            libraryList.addListener((ListChangeListener<String>) change -> {
                Platform.runLater(()->sizeProperty.set(libraryList.size()));
            });
        }

        //synchronize if you want to use multithread
        void add(String string) {
            Platform.runLater(()->sizeProperty.set(libraryList.add(string)));
        }

        ObservableList<String> getLibraryList() {
            return libraryList;
        }

        IntegerProperty getSizeProperty() {
            return sizeProperty;
        }
    }

    class FileTask implements Runnable{

        private final  Model model;

        public FileTask(Model model){
            this.model = model;
        }

        @Override
        public void run() {
            Random rand = new Random();
            for(int i = 0; i < 5; i++) {
                String s = ""+rand.nextInt(Integer.MAX_VALUE);
                model.add(s);
            }
        }
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}
1 голос
/ 17 апреля 2019

Работает, как и ожидалось, у вас есть поток приложения и поток задач, они выглядят примерно так:

App ------\ ----------------------
Task       \-label.setText() Exception

Вы не можете работать с любым пользовательским интерфейсом ни на чем, кроме потока приложения, поэтомудобавление вашего RunLater делает это:

App ----\ -------------/ RunLater(label.setText()) ----------
Task     \-add to list/

, что хорошо работает.Есть несколько способов управлять этим в зависимости от того, что вы хотите сделать:

  • Если вы хотите обновить список таблиц в задаче, вы можете переместить вызов RunLater внутрь задачи, а невнутри обработчика, таким образом он все равно вернет вас к ветке приложения.Таким образом, если вы на самом деле находитесь в потоке приложения, нет необходимости вызывать RunLater в обработчике.
App ---\ -----------------------/ label.setText() ----------
Task    \-RunLater(add to list)/
  • Другой вариант - просто использовать Task>, который будет запускатьсяв другом потоке и вернуть полный список строк, которые будут добавлены.Это, скорее всего, то, что вам нужно, если вы выполняете сетевые вызовы в задаче, получаете список элементов, а затем добавляете их, как только они все загружены в таблицу.
App -----\ ------------------------------/ label.setText() ---/ add to table list-------
Task      \-build list, update progress /- return final list /

Надеюсь, форматирование останется.

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