2014-08-06 3 views
4

Я пытаюсь найти эффективный способ повлиять на форму и содержимое элементов GUI JavaFX, таких как простой Pane, с использованием многопоточности. Предположим, у меня есть простой Pane, на котором я показываю заполненные Circle s при заданных значениях времени, и я хочу иметь возможность ответить на них, например. нажав соответствующий ключ. До сих пор для этой цели я попытался использовать класс с реализацией интерфейса Runnable и создания в нем классического объекта Thread, чтобы добавить и/или удалить элементы из внешнего JavaFX Pane, который был передан этому «классу потоков» "в его параметре конструктора из основного класса Application. Скажем, оба класса, 1) приложения и 2) класс резьбы, выглядит следующим образом:Каков наилучший способ управления многопотоковой обработкой в ​​JavaFX 8?

import javafx.application.Application; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.stage.Stage; 

public class ClassApplication extends Application { 

    private Pane pane; 

    public Parent createContent() { 

     /* layout */ 
     BorderPane layout = new BorderPane(); 

     /* layout -> center */ 
     pane = new Pane(); 
     pane.setMinWidth(250); 
     pane.setMaxWidth(250); 
     pane.setMinHeight(250); 
     pane.setMaxHeight(250); 
     pane.setStyle("-fx-background-color: #000000;"); 

     /* layout -> center -> pane */ 
     Circle circle = new Circle(125, 125, 10, Color.WHITE); 

     /* add items to the layout */ 
     pane.getChildren().add(circle); 

     layout.setCenter(pane); 
     return layout; 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     stage.setScene(new Scene(createContent())); 
     stage.setWidth(300); 
     stage.setHeight(300); 
     stage.show(); 

     /* initialize custom Thread */ 
     ClassThread thread = new ClassThread(pane); 
     thread.execute(); 
    } 

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

... и "класс резьбы"

import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 

public class ClassThread implements Runnable { 

    private Thread t; 
    private Pane pane; 

    ClassThread(Pane p) { 
     this.pane = p; 

     t = new Thread(this, "Painter"); 
    } 

    @Override 
    public void run() { 
     try { 
      Thread.sleep(2000); 
      Circle circle = new Circle(50, 50, 10, Color.RED); 
      pane.getChildren().clear(); 
      pane.getChildren().add(circle); 

     } catch (InterruptedException ie) { 
      ie.printStackTrace(); 
     } 
    } 

    public void execute() { 
     t.start(); 
    } 
} 

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

Exception in thread "Painter" java.lang.IllegalStateException: Not on FX application thread; currentThread = Painter 
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source) 
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source) 
    at javafx.scene.Parent$2.onProposedChange(Unknown Source) 
    at com.sun.javafx.collections.VetoableListDecorator.clear(Unknown Source) 
    at ClassThread.run(ClassThread.java:21) 
    at java.lang.Thread.run(Unknown Source) 

... где линия «21» является: pane.getChildren().clear();

Я пришел к выводу, что «существует проблема влияния основного потока JavaFX с уровня другого потока». Но в этом случае, как я могу изменить форму и содержимое элементов графического интерфейса JavaFX динамически, если я не могу (tbh более точно сказать, что это «я не знаю, как»), связывается с несколькими потоками?

ОБНОВЛЕНИЕ: 2014/08/07, 03:42

После прочтения данные ответов я пытался реализовать указанные решения в коде, для того, чтобы отобразить 10 пользовательскими Circle с на различных местах с заданными временными интервалами между каждый дисплей:

/* in ClassApplication body */ 
@Override 
public void start(Stage stage) throws Exception { 
    stage.setScene(new Scene(createContent())); 
    stage.setWidth(300); 
    stage.setHeight(300); 
    stage.show(); 

    Timeline timeline = new Timeline(); 
    for (int i = 0; i < 10; i++) { 
     Random r = new Random(); 
     int random = r.nextInt(200) + 25; 
     KeyFrame f = new KeyFrame(Duration.millis((i + 1) * 1000), 
      new EventHandler<ActionEvent>() { 
      @Override 
      public void handle(ActionEvent ae) { 
       pane.getChildren().add(new Circle(
        random, random, 10, Color.RED)); 
      } 
     }); 
     timeline.getKeyFrames().add(f); 
    } 
    timeline.setCycleCount(1); 
    timeline.play(); 
} 

Решение выше работает просто отлично. Большое спасибо.

ответ

7

В дополнение к использованию низкоуровневых Thread API и Platform.runLater(...) планировать код, который будет выполняться на FX Application тему, как в ответ Tomas' , другой вариант - использовать API параллелизма FX. Это обеспечивает классы Service и Task, которые предназначены для выполнения по фоновым потокам, а также методы обратного вызова, которые гарантированно выполняются в потоке прикладных программ FX.

Для вашего простого примера это похоже на слишком большой код шаблона, но для реального кода приложения это довольно приятно, обеспечивая чистое разделение между фоновым заданием и обновлением пользовательского интерфейса, которое выполняется при завершении. Кроме того, Task s может быть отправлен в Executor s.

import javafx.application.Application; 
import javafx.concurrent.Task ; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.stage.Stage; 

public class ClassApplication extends Application { 

    private Pane pane; 

    public Parent createContent() { 

     /* layout */ 
     BorderPane layout = new BorderPane(); 

     /* layout -> center */ 
     pane = new Pane(); 
     pane.setMinWidth(250); 
     pane.setMaxWidth(250); 
     pane.setMinHeight(250); 
     pane.setMaxHeight(250); 
     pane.setStyle("-fx-background-color: #000000;"); 

     /* layout -> center -> pane */ 
     Circle circle = new Circle(125, 125, 10, Color.WHITE); 

     /* add items to the layout */ 
     pane.getChildren().add(circle); 

     layout.setCenter(pane); 
     return layout; 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     stage.setScene(new Scene(createContent())); 
     stage.setWidth(300); 
     stage.setHeight(300); 
     stage.show(); 

     Task<Void> task = new Task<Void>() { 
      @Override 
      public Void call() throws Exception { 
       Thread.sleep(2000); 
       return null ; 
      } 
     }; 

     task.setOnSucceeded(event -> { 
      Circle circle = new Circle(50, 50, 10, Color.RED); 
      pane.getChildren().setAll(circle); 
     }); 

     new Thread(task).run(); 
    } 

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

Если все, что вы делаете паузу, вы даже можете уйти с использованием (или злоупотребление?) Анимация API.Там в PauseTransition, который делает паузу в течение определенного времени, и вы можете использовать его onFinished обработчик выполнить обновление:

import javafx.application.Application; 
import javafx.animation.PauseTransition ; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.stage.Stage; 
import javafx.util.Duration ; 

public class ClassApplication extends Application { 

    private Pane pane; 

    public Parent createContent() { 

     /* layout */ 
     BorderPane layout = new BorderPane(); 

     /* layout -> center */ 
     pane = new Pane(); 
     pane.setMinWidth(250); 
     pane.setMaxWidth(250); 
     pane.setMinHeight(250); 
     pane.setMaxHeight(250); 
     pane.setStyle("-fx-background-color: #000000;"); 

     /* layout -> center -> pane */ 
     Circle circle = new Circle(125, 125, 10, Color.WHITE); 

     /* add items to the layout */ 
     pane.getChildren().add(circle); 

     layout.setCenter(pane); 
     return layout; 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     stage.setScene(new Scene(createContent())); 
     stage.setWidth(300); 
     stage.setHeight(300); 
     stage.show(); 

     PauseTransition pause = new PauseTransition(Duration.millis(2000)); 
     pause.setOnFinished(event -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED))); 
     pause.play(); 
    } 

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

Если вам необходимо выполнить паузу несколько раз, вы можете использовать Timeline и вызовите setCycleCount(...):

import javafx.application.Application; 
import javafx.animation.Timeline ; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.stage.Stage; 
import javafx.util.Duration ; 

public class ClassApplication extends Application { 

    private Pane pane; 

    public Parent createContent() { 

     /* layout */ 
     BorderPane layout = new BorderPane(); 

     /* layout -> center */ 
     pane = new Pane(); 
     pane.setMinWidth(250); 
     pane.setMaxWidth(250); 
     pane.setMinHeight(250); 
     pane.setMaxHeight(250); 
     pane.setStyle("-fx-background-color: #000000;"); 

     /* layout -> center -> pane */ 
     Circle circle = new Circle(125, 125, 10, Color.WHITE); 

     /* add items to the layout */ 
     pane.getChildren().add(circle); 

     layout.setCenter(pane); 
     return layout; 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     stage.setScene(new Scene(createContent())); 
     stage.setWidth(300); 
     stage.setHeight(300); 
     stage.show(); 

     KeyFrame keyFrame = new KeyFrame(Duration.millis(2000), 
      event -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED))); 
     Timeline timeline = new Timeline(keyFrame); 

     // Repeat 10 times: 
     timeline.setCycleCount(10); 

     timeline.play(); 
    } 

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

И если злоупотребление анимации API еще слишком много шаблоннога, ReactFX имеет [обертку] (http://www.reactfx.org/javadoc/org/reactfx/util/FxTimer.html) вокруг него : 'FxTimer.runLater (Duration.ofMillis (2000),() -> pane.getChildren(). SetAll (новый круг (50, 50, 10, цвет.RED)));' –

+0

@James_D Проблема с этим решением заключается в том, что он хорош для отдельных событий с задержкой. Но в случае отображения нескольких «Кругов» в определенные промежутки времени это проблематично. Скажем, у меня есть 10 'Circle' и я хочу, чтобы они отображались один за другим в, например. 2000 мс на одной и той же панели. Я также пытался использовать «Timeline», и это «KeyValue» и «KeyFrame», но проблема в том, что в объекте KeyValue я не могу добавлять и удалять элементы из «Панели». – bluevoxel

+0

Добавлен пример, который повторяется 10 раз. Вы можете установить количество циклов в 'Animation.INDEFINITE' для повторения на неопределенный срок. И @TomasMikula, без сомнения, имеет однострочный интерфейс от ReactFX для того же самого;). –

2

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

Platform.runLater(() -> { 
    Circle circle = new Circle(50, 50, 10, Color.RED); 
    pane.getChildren().clear(); 
    pane.getChildren().add(circle); 
}); 
Смежные вопросы