java - 使用 JavaFX SceneGraph

标签 java javafx javafx-8

我创建了一个要点,我的问题在GraphGymnastic中进行了描述。 。使用 Thread.sleep() 对该问题进行了高度抽象,但它达到了目的。

问题描述

我想在过滤事件时(使用事件过滤器)遍历场景图。当事件到达时,我想计算图中所有节点上的一些内容。对于 2 个节点,使用 Platform.runLater() 可以正常工作,但稍后,将会有 n 个节点,计算可能需要一些时间。我不希望 FX 线程在此计算期间被阻塞。

因此我们考虑将计算委托(delegate)给第二个线程。说完了,做到了。现在我们面临的问题是,我们在第二个线程上进行计算,但该线程持有对图形的引用,该引用可以同时更改(我们同意,这在大多数情况下不会发生,但要考虑)。也就是说,我们基于“动态 View ”进行计算。对此该怎么办?复制图表?然后我们从一开始就阻塞 UI 线程来复制图表。

这是一个纯粹的设计决策,如果有其他想法或方法,如果有,如何解决这个优雅不一些建筑工地。

感谢任何可以提供帮助的人。

PS:在要点中,您必须取消注释并用我的注释注释这两行才能看到问题(按下按钮后,尝试移动窗口)

编辑:与要点不同,这里是代码。

public class GraphGymnastic extends Application {
  final ExecutorService serv = Executors.newFixedThreadPool(2);
  public static void main(String argv[]) {
    launch(argv);
  }

  @Override public void start(Stage primaryStage) throws Exception {
    //Setup UI
    primaryStage.setTitle("Demo");
    final List<Node> nodesInGraph = new ArrayList<>();
    final FlowPane p = new FlowPane() {{
      setId("flowpane");
      getChildren().addAll(new Label("Label") {{
        setId("label");
      }}, new Button("Button") {{
        setId("button");
        // setOnMouseClicked(event -> handle(event, nodesInGraph)); //Uncomment and comment below to see effects!
        setOnMouseClicked(event -> handleAsync(event, nodesInGraph));
      }});
      setHgap(5);
    }};

    //Assume that this goes recursive and deep into a scene graph but still
    // returns a list
    //Here it takes the two childs for simplicity
    nodesInGraph.addAll(p.getChildrenUnmodifiable());

   //Show stage
    primaryStage.setScene(new Scene(p));
    primaryStage.show();
  }

  public void handle(MouseEvent ev, List<Node> nodesInGraph) {
    if (null != nodesInGraph)
      Platform.runLater(() -> nodesInGraph.forEach(node -> {
        //This will block the UI thread, so there is the need for a second
        //thread
        System.out.println(
            "Calculating heavy on node " + node.toString() + " with event from "
                + ev.getSource().toString());
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }));
  }

  public void handleAsync(MouseEvent ev, List<Node> nodesInGraph) {
    if (null != nodesInGraph)
      serv.submit(() -> nodesInGraph.forEach(node -> {
        //Now there is a second thread but it works on a LIVE view object
        // list, which is ugly
        //Option 1: Keep it like this, drawbacks? :S
        //Option 2: Copy the graph, costs performance... How deep should it
        // copy? :S
        System.out.println(
            "Calculating heavy on node " + node.toString() + " with event from "
                + ev.getSource().toString());
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }));
  }
}

最佳答案

不是并发专家,所以这只是为了说明我的评论:提取 fx 线程上的一些状态,然后将该状态传递给另一个线程进行处理

成分:

  • 实时节点列表
  • 一个 State 对象,可以携带节点属性的快照(在请求时),然后对其进行一些冗长的处理
  • 任务(从 fx 并发开始):在后台线程中,它创建一个状态对象,切换到 fx 线程,复制相关状态,切换回后台,开始处理并在就绪时设置其值
  • 监听任务值的处理程序

代码:

// the worker
public static class SceneGraphWorker extends Task<NodeState> {

    private List<Node> nodes;
    private int current;

    public SceneGraphWorker(List<Node> nodes) {
        this.nodes = nodes;
    }

    @Override
    protected NodeState call() throws Exception {
        while (current >= 0) {
            NodeState state = new NodeState(current);
            CountDownLatch latch = new CountDownLatch(1);
            Platform.runLater(() -> {
                Node node = nodes.get(current);
                Bounds bounds = node.localToScene(node.getBoundsInLocal());
                state.setState(bounds.getMinX(), bounds.getMinY());
                state.setID(node.getId());
                current = current == nodes.size() - 1 ? -1 : current + 1;
                latch.countDown(); 
            });
            latch.await();
            state.process();
            updateValue(state);
        }
        return null;
    }

}

// the handler: listens to value
public void handleWithWorker(MouseEvent ev, List<Node> nodesInGraph) {
    Task worker = new SceneGraphWorker(nodesInGraph);
    worker.valueProperty().addListener((src, ov, nv) -> {
        progress.setText(nv != null ? nv.toString() : "empty");
    });
    new Thread(worker).start();
}

// the state object
public static class NodeState {
    double x, y;
    int index;
    private String name;
    public NodeState(int index) {
        this.index = index;
    }
    public void setState(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public void setID(String name) {
        this.name = name;
    }
    public void process() throws InterruptedException {
        Thread.sleep(2000);
    }
    @Override
    public String toString() {
        return "[" + name + " x: " + x + " y: " + y + "]";
    }
}

关于java - 使用 JavaFX SceneGraph,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31605531/

相关文章:

java - 查找并消除轮廓opencv

java - Play Framework 应用程序默认使用多少最大堆空间

JavaFX UI 创建和进度条更新

JavaFX CheckboxTreeItem : only handle the selected checkbox

javafx-8 - 如何在向上滚动时触发事件,javafx

java - 在启动脚本中使用 getopts 时丢失 java 主参数

java - 将字节数据写入目录

java - 如何在 JavaFX 中将任何组件的尺寸绑定(bind)到父容器?

java - 排序 ObservableList 理解比较器和谓词

java - 每个上下文一个实例的依赖注入(inject)