延迟更新javafx自定义控件

时间:2014-02-28 08:53:14

标签: java javafx javafx-8 java-8

我的自定义控件调整大小时需要特定的行为。当控件调整大小(调整resize或resizeRelocate方法)时,我首先想要缩放子节点以适应新的边界。如果没有大小变化,即1秒钟 - 进行昂贵的计算和子节点的重新布局。如果我在每次调整大小调用时重新计算 - 它会使舞台调整大小非常滞后。我怎样才能做到这一点?

这是一个例子,这里CurvePlot只是数据模型:

class ShapeCurvePlot extends Polyline {
    private final CurvePlot model;

    public ShapeCurvePlot(CurvePlot model) {
        Objects.requireNonNull(model);
        this.model = model;
        strokeProperty().bind(model.strokeProperty());
        strokeWidthProperty().bind(model.strokeWidthProperty());
    }

    @Override
    public boolean isResizable() {
        return true;
    }

    @Override
    public void resize(double width, double height) {
        // "expensive calculation"
        Series series = model.getSeries();
        double xOffset = series.valueAxis().getLeft();
        double yOffset = series.keyAxis().getLeft();
        double yScale = height / series.keyAxis().range();
        double xScale = width / series.valueAxis().range();

        getPoints().clear();
        for (Map.Entry<Double, Double> item : series.data().entrySet()) {
            double x = item.getValue();
            double y = item.getKey();
            getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
        }
    }
}

虽然在这个例子中调整大小很快,其他形状,我需要,不太容易重新计算。我要求通用解决方案延迟计算,直到X秒内没有调整大小。

PS。对不起我的英语,我不是母语人士..

1 个答案:

答案 0 :(得分:0)

我之前遇到过这个问题,包括C#和Java。我的解决方案,并不是说它是最好的解决方案,但似乎有用,就是使用某种形式的计时器来触发昂贵的重新计算。

在我的情况下,我在搜索文本框中使用了它,实际搜索仅在用户未按键一段时间(比如500ms)后触发,从而阻止搜索每个键击的搜索。 / p>

在您的情况下,您可以在resize()方法中触发计时器。当计时器用完时,它会执行昂贵的操作。在定时器等待时触发定时器会导致定时器复位。

在java中,我通过使用java.util.Timer为计时器执行此操作,然后运行我创建的java.util.TimerTask。在TimerTask的run()方法中,我创建了一个javafx.concurrent.Task并在一个新的Thread中运行它。 Task的call()方法是完成工作的地方。这确保了工作在JavaFX线程上完成,否则会出现线程问题。

希望这对你有所帮助。

编辑...这里有一些代码可以做我上面谈到的内容。 注意:此代码完全未经测试,我认为它应该可以正常工作

class ShapeCurvePlot extends Polyline {
private class ResizeTimerTask extends TimerTask {
    private ShapeCurvePlot plot;

    public ResizeTimerTask(ShapeCurvePlot plot) {
        this.plot = plot;
    }

    /* (non-Javadoc)
     * @see java.util.TimerTask#run()
     */
    @Override
    public void run() {
        Task<Object> t = new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                // "expensive calculation"
                Series series = plot.getSeries();
                double xOffset = series.valueAxis().getLeft();
                double yOffset = series.keyAxis().getLeft();
                double yScale = height / series.keyAxis().range();
                double xScale = width / series.valueAxis().range();

                plot.getPoints().clear();
                for (Map.Entry<Double, Double> item : series.data().entrySet()) {
                    double x = item.getValue();
                    double y = item.getKey();
                    plot.getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
                }
            }
        }

        new Thread(t).start();
    }
}

private static int RESIZE_DELAY_MS = 1000;
private final CurvePlot model;
private Timer resizeTimer;

public ShapeCurvePlot(CurvePlot model) {
    Objects.requireNonNull(model);
    this.model = model;
    strokeProperty().bind(model.strokeProperty());
    strokeWidthProperty().bind(model.strokeWidthProperty());
}

@Override
public boolean isResizable() {
    return true;
}

public Series getSeries() {
    return this.model.getSeries();
}

@Override
public void resize(double width, double height) {
    // cancel the current task (if any).
    if(this.resizeTimer != null) {
        this.resizeTimer.cancel();
        this.resizeTimer.purge();
        this.resizeTimer = null;
    }

    try {
        // create a new task that will be executed in 1 second.
        this.resizeTimer = new Timer();
        this.resizeTimer.schedule(new ResizeTimerTask(this), RESIZE_DELAY_MS);
    } catch (OutOfMemoryError oom) {
        oom.printStackTrace();
        if(this.resizeTimer != null) {
            this.resizeTimer.cancel();
            this.resizeTimer.purge();
            this.resizeTimer = null;
        }
    }
}

}

进一步编辑...今天花了一些时间玩JavaFX线程问题,还有另一个选项可用于在JavaFX显示线程中运行耗时的任务。您可以使用javafx.application.Platform.runLater(Runnable r)在JavaFX线程中执行Runnable,而不是使用Task。这将在将来的某个时间执行runnable,其好处是以FIFO顺序处理以这种方式运行的所有可运行的。这可以很方便地防止事情发生故障,同时仍能在JavaFX显示线程上以异步方式运行它们。

为此,您可以将ResizeTimerTask类的run()方法的实现更改为:

public void run() {
    Platform.runLater(new Runnable() {

        @Override
        public void run() {
                // "expensive calculation"
            Series series = plot.getSeries();
            double xOffset = series.valueAxis().getLeft();
            double yOffset = series.keyAxis().getLeft();
            double yScale = height / series.keyAxis().range();
            double xScale = width / series.valueAxis().range();

            plot.getPoints().clear();
            for (Map.Entry<Double, Double> item : series.data().entrySet()) {
                double x = item.getValue();
                double y = item.getKey();
                plot.getPoints().addAll((x - xOffset) * xScale, (y - yOffset) * yScale);
            }
        }
    });
}