Обновление пользовательского интерфейса из разных streamов в JavaFX

Я разрабатываю приложение с несколькими объектами TextField которые необходимо обновить, чтобы отразить изменения в связанных свойствах back-end. TextField не редактируются, только TextField может изменять их содержимое.

Насколько я понимаю, правильный способ – запустить тяжелые вычисления в отдельном streamе, чтобы не блокировать пользовательский интерфейс. Я сделал это с помощью javafx.concurrent.Task и javafx.concurrent.Task одно значение обратно в stream JavaFX с помощью updateMessage() , который работал хорошо. Тем не менее, мне нужно больше, чем одно значение, которое нужно обновить, так как он делает хруст.

Поскольку textProperty значения хранятся в качестве свойств JavaFX, я попробовал просто textProperty их к textProperty каждого элемента GUI и позволить привязкам выполнять эту работу. Однако это не работает; после запуска в течение нескольких секунд, TextField перестает обновляться, несмотря на то, что задняя задача все еще запущена. Никаких исключений не возникает.

Я также попытался использовать Platform.runLater() для активного обновления TextField а не привязки. Проблема здесь в том, что задачи runLater() планируются быстрее, чем платформа может их запускать, и поэтому графический интерфейс становится вялым и требует времени, чтобы «догнать» даже после завершения конечной задачи.

Я нашел здесь несколько вопросов:

Записи регистратора, переведенные на пользовательский интерфейс, перестают обновляться со временем

Пользовательский интерфейс в многопоточности в JavaFX

но моя проблема сохраняется.

Вкратце: у меня есть внутренние изменения, внесенные в свойства, и я хочу, чтобы эти изменения отображались в графическом интерфейсе. Back-end – это генетический алгоритм, поэтому его работа разбивается на дискретные поколения. Я бы хотел, чтобы TextField s обновлялся хотя бы один раз между поколениями, даже если это задерживает следующее поколение. Более важно, чтобы графический интерфейс отвечал хорошо, чем GA работает быстро.

Я могу опубликовать несколько примеров кода, если я не сделал проблему ясной.

ОБНОВИТЬ

Мне удалось сделать это после предложения James_D. Чтобы решить проблему, когда серверу приходилось ждать, пока консоль будет печатать, я применил буферизованную консоль. Он хранит строки для печати в StringBuffer и фактически добавляет их в TextArea когда вызывается метод flush() . Я использовал AtomicBoolean, чтобы предотвратить следующее поколение, пока stream не будет завершен, как это сделано с помощью Platform.runLater() . Также обратите внимание, что это решение невероятно медленно.

2 Solutions collect form web for “Обновление пользовательского интерфейса из разных streamов в JavaFX”

Не уверен, что я полностью понимаю, но я думаю, что это может помочь.

Использование Platform.runLater (…) является подходящим подходом для этого.

Трюк, чтобы избежать наводнения streamа приложений FX, заключается в использовании переменной Atomic для хранения интересующего вас значения. В методе Platform.runLater (…) извлеките его и установите для него значение сторожевой. Из вашего фонового streamа обновите переменную Atomic, но выпустите новую платформу Platform.runLater (…), если она вернулась к ее значению.

Я понял это, посмотрев исходный код задачи . Посмотрите, как реализуется метод updateMessage (..) (строка 1131 на момент написания).

Вот пример, который использует ту же технику. У этого просто есть (занятый) stream фона, который считается настолько быстрым, насколько это возможно, обновляя IntegerProperty. Наблюдатель наблюдает за этим свойством и обновляет AtomicInteger с новым значением. Если текущее значение AtomicInteger равно -1, оно планирует Platform.runLater ().

В Platform.runLater я извлекаю значение AtomicInteger и использую его для обновления метки, устанавливая значение -1 в процессе. Это означает, что я готов к другому обновлению пользовательского интерфейса.

 import java.text.NumberFormat; import java.util.concurrent.atomic.AtomicInteger; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; public class ConcurrentModel extends Application { @Override public void start(Stage primaryStage) { final AtomicInteger count = new AtomicInteger(-1); final AnchorPane root = new AnchorPane(); final Label label = new Label(); final Model model = new Model(); final NumberFormat formatter = NumberFormat.getIntegerInstance(); formatter.setGroupingUsed(true); model.intProperty().addListener(new ChangeListener() { @Override public void changed(final ObservableValue< ? extends Number> observable, final Number oldValue, final Number newValue) { if (count.getAndSet(newValue.intValue()) == -1) { Platform.runLater(new Runnable() { @Override public void run() { long value = count.getAndSet(-1); label.setText(formatter.format(value)); } }); } } }); final Button startButton = new Button("Start"); startButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { model.start(); } }); AnchorPane.setTopAnchor(label, 10.0); AnchorPane.setLeftAnchor(label, 10.0); AnchorPane.setBottomAnchor(startButton, 10.0); AnchorPane.setLeftAnchor(startButton, 10.0); root.getChildren().addAll(label, startButton); Scene scene = new Scene(root, 100, 100); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } public class Model extends Thread { private IntegerProperty intProperty; public Model() { intProperty = new SimpleIntegerProperty(this, "int", 0); setDaemon(true); } public int getInt() { return intProperty.get(); } public IntegerProperty intProperty() { return intProperty; } @Override public void run() { while (true) { intProperty.set(intProperty.get() + 1); } } } } 

Если вы действительно хотите «управлять» задним концом от пользовательского интерфейса: это уменьшает скорость реализации бэкэнд, чтобы вы видели все обновления, подумайте об использовании AnimationTimer . У AnimationTimer есть handle(...) который вызывается один раз для рендера кадра. Таким образом, вы можете заблокировать реализацию на заднем плане (например, используя блокирующую очередь) и освободить ее один раз за вызов метода дескриптора. Метод handle(...) вызывается в streamе приложения FX.

Метод handle(...) принимает параметр, который является меткой времени (в наносекундах), поэтому вы можете использовать это, чтобы замедлять обновления, если один раз за кадр слишком быстро.

Например:

 import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.beans.property.LongProperty; import javafx.beans.property.SimpleLongProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; public class Main extends Application { @Override public void start(Stage primaryStage) { final BlockingQueue messageQueue = new ArrayBlockingQueue<>(1); TextArea console = new TextArea(); Button startButton = new Button("Start"); startButton.setOnAction(event -> { MessageProducer producer = new MessageProducer(messageQueue); Thread t = new Thread(producer); t.setDaemon(true); t.start(); }); final LongProperty lastUpdate = new SimpleLongProperty(); final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output. AnimationTimer timer = new AnimationTimer() { @Override public void handle(long now) { if (now - lastUpdate.get() > minUpdateInterval) { final String message = messageQueue.poll(); if (message != null) { console.appendText("\n" + message); } lastUpdate.set(now); } } }; timer.start(); HBox controls = new HBox(5, startButton); controls.setPadding(new Insets(10)); controls.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(console, null, null, controls, null); Scene scene = new Scene(root,600,400); primaryStage.setScene(scene); primaryStage.show(); } private static class MessageProducer implements Runnable { private final BlockingQueue messageQueue ; public MessageProducer(BlockingQueue messageQueue) { this.messageQueue = messageQueue ; } @Override public void run() { long messageCount = 0 ; try { while (true) { final String message = "Message " + (++messageCount); messageQueue.put(message); } } catch (InterruptedException exc) { System.out.println("Message producer interrupted: exiting."); } } } public static void main(String[] args) { launch(args); } } 

Лучший способ выполнить это – использование Task в JavaFx. Это, безусловно, лучший метод, который я нашел для обновления элементов управления UI в JavaFx.

 Task task = new Task() { @Override public Void run() { static final int max = 1000000; for (int i=1; i< =max; i++) { updateProgress(i, max); } return null; } }; ProgressBar bar = new ProgressBar(); bar.progressProperty().bind(task.progressProperty()); new Thread(task).start(); 
  • В чем разница между fill_parent и wrap_content?
  • Как обновить текстовое поле в графическом интерфейсе из другого streamа
  • Редактор графического редактора Netbeans, создающий собственный непонятный код
  • Переместить макеты, когда отображается мягкая клавиатура?
  • как изменить пользовательский интерфейс в зависимости от выбора поля со списком
  • Как настроить ListField в BlackBerry?
  • JButton ActionListener - обновление GUI только после нажатия JButton
  • Как установить размер изображения с помощью RescaleOp
  • java - Как бы я динамически добавлял компонент swing в gui при нажатии?
  • Добавление JPanels от других classов к cardLayout
  • Предоставление имени JMenuItem его ActionListener
  • Давайте будем гением компьютера.