Я перенес свой проект на 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);

    }
}
1
Phaelax z 17 Апр 2019 в 15:43

2 ответа

Лучший ответ

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

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 /

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

1
kendavidson 17 Апр 2019 в 13:02

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

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
c0der 17 Апр 2019 в 16:55