简体   繁体   English

如何强制更新 JavaFX XYChart 的布局

[英]How to force layout update of a JavaFX XYChart

I'm trying to force the update of a custom XYChart in a timer method, but the only thing that seems to work is resizing the window.我试图在计时器方法中强制更新自定义 XYChart,但似乎唯一有效的是调整窗口大小。

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;

import java.util.*;
import java.util.stream.Collectors;

public class TestCustomLayoutUpdate extends Application {

    private LineChart<Number, Number> chart;
    private NumberAxis xAxis;
    private NumberAxis yAxis;

    private ShopItem currentShopItem;

    class ShopItem {
        private double price;

        public ShopItem(double price) {
            this.price = price;
        }
    }

    @Override
    public void start(Stage primaryStage) {

        createChart();

        Scene scene = new Scene(chart, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.setHeight(600);
        primaryStage.setWidth(400);
        primaryStage.show();

        Random rng = new Random();

        // Note since this is a regular timer not javafx timer that we should use platform run later.
        TimerTask repeatedTask = new TimerTask() {
            public void run() {
                currentShopItem = new ShopItem(rng.nextDouble() * 100);
                Platform.runLater(() -> {
                    chart.layout();
                    chart.requestLayout();
                    xAxis.layout();
                });
            }
        };
        Timer timer = new Timer("Timer");

        long delay  = 1000L;
        long period = 1000L;
        timer.scheduleAtFixedRate(repeatedTask, delay, period);
    }

    public void createChart() {
        xAxis = new NumberAxis();
        yAxis = new NumberAxis();
        xAxis.setAutoRanging(false);
        xAxis.setUpperBound(100);

        chart = new LineChart<Number, Number>(xAxis, yAxis) {
            private List<Shape> shapes = new ArrayList<>();

            @Override
            public void layoutPlotChildren() {
                super.layoutPlotChildren();

                getPlotChildren().removeAll(shapes);
                shapes.clear();

                if (currentShopItem != null) {
                    Rectangle rect = new Rectangle(0, 0, 10, currentShopItem.price);

                    rect.getTransforms().setAll(chartDisplayTransform(xAxis, yAxis));
                    rect.setFill(Color.RED);
                    shapes.add(rect);
                    getPlotChildren().addAll(shapes);
                }
            }
        };
    }

    private Transform chartDisplayTransform(NumberAxis xAxis, NumberAxis yAxis) {
        return new Affine(xAxis.getScale(), 0, xAxis.getDisplayPosition(0), 0, yAxis.getScale(),
                yAxis.getDisplayPosition(0));
    }

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

JavaFX will automatically layout and redraw any parts of the scene graph if properties of any of the nodes that are part of the graph change.如果作为图形一部分的任何节点的属性发生更改,JavaFX 将自动布局和重绘场景图形的任何部分。 The problem with the way you have structured the code is that you only change the scene graph (change the dimensions of the rectangle and/or change the plot children of the chart) in the layoutPlotChildren() method, which (I believe) is called as part of the layout process.您构建代码的方式的问题在于您只在layoutPlotChildren()方法中更改场景图(更改矩形的尺寸和/或更改图表的绘图子项),该方法(我相信)被称为作为布局过程的一部分。 So when you request a layout, JavaFX checks to see if anything in the scene graph has changed, sees that it hasn't, and so doesn't perform a layout.因此,当您请求布局时,JavaFX 会检查场景图中是否有任何内容发生了变化,发现它没有发生变化,因此不会执行布局。 Thus layoutPlotChildren() isn't called, and so the scene graph isn't changed...因此layoutPlotChildren()没有被调用,所以场景图没有改变......

So to fix this, you just need to make sure the existing rectangle is updated, or that the list of plot children change, when the underlying data change.因此,要解决此问题,您只需要确保在基础数据更改时更新现有矩形,或者更改绘图子项列表。 You can accomplish this by using JavaFX properties, and observing them from your chart subclass.您可以通过使用 JavaFX 属性并从您的图表子类中观察它们来完成此操作。 (There are other ways too, I suppose, such as defining a method in the chart subclass that updates the rectangle, and invoking it from the animation loop. But observing a JavaFX property is the API-preferred way to do this.) (我想还有其他方法,例如在图表子类中定义一个更新矩形的方法,并从动画循环中调用它。但观察 JavaFX 属性是 API 首选的方法。)

As an aside, if you want to change anything periodically that updates graphics, the preferred way to do this in JavaFX is with a Timeline , which operates entirely on the JavaFX thread and avoids the need to think about synchronization of variables, etc.顺便说一句,如果您想定期更改更新图形的任何内容,在 JavaFX 中执行此操作的首选方法是使用Timeline ,它完全在 JavaFX 线程上运行并且无需考虑变量的同步等。

Here's a version of your example with these changes, which works as desired:这是具有这些更改的示例版本,可按需要工作:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TestCustomLayoutUpdate extends Application {

    private LineChart<Number, Number> chart;
    private NumberAxis xAxis;
    private NumberAxis yAxis;


    private ObjectProperty<ShopItem> currentShopItem;

    class ShopItem {
        private double price;

        public ShopItem(double price) {
            this.price = price;
        }
    }

    @Override
    public void start(Stage primaryStage) {

        currentShopItem = new SimpleObjectProperty<>();


        createChart();

        Scene scene = new Scene(chart, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.setHeight(600);
        primaryStage.setWidth(400);
        primaryStage.show();

        Random rng = new Random();

        Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),  
                evt -> currentShopItem.set(new ShopItem(rng.nextDouble() * 100))
        ));
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }

    public void createChart() {
        xAxis = new NumberAxis();
        yAxis = new NumberAxis();
        xAxis.setAutoRanging(false);
        xAxis.setUpperBound(100);

        chart = new LineChart<Number, Number>(xAxis, yAxis) {

            private List<Shape> shapes = new ArrayList<>();

            private Rectangle rect ;

            // anonymous class constructor:
            {
                rect = new Rectangle(0,0, Color.RED);

                currentShopItem.addListener((obs, oldItem, newItem) -> {
                    if (newItem == null) {
                        rect.setWidth(0);
                        rect.setHeight(0);
                    } else {
                        rect.setWidth(10);
                        rect.setHeight(newItem.price);
                    }

                });
            }

            @Override
            public void layoutPlotChildren() {
                super.layoutPlotChildren();

                getPlotChildren().removeAll(shapes);
                shapes.clear();

                if (currentShopItem != null) {
                    rect.getTransforms().setAll(chartDisplayTransform(xAxis, yAxis));
                    shapes.add(rect);
                    getPlotChildren().addAll(shapes);
                }
            }
        };
    }

    private Transform chartDisplayTransform(NumberAxis xAxis, NumberAxis yAxis) {
        return new Affine(xAxis.getScale(), 0, xAxis.getDisplayPosition(0), 0, yAxis.getScale(),
                yAxis.getDisplayPosition(0));
    }

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM