gpt4 book ai didi

binding - 如何处理相关的数据绑定(bind)?

转载 作者:行者123 更新时间:2023-12-04 15:58:37 35 4
gpt4 key购买 nike

我经常发现自己遇到问题,即控件的两个(相关)值被更新,并且两者都会触发昂贵的操作,或者控件可能会暂时处于不一致的状态。

例如,考虑一个数据绑定(bind),其中两个值 (x,y) 相互减去,最终结果用作某个其他属性 z 的除数:

z/(x - y)

如果 x 和 y 绑定(bind)到某个外部值,那么一次更新它们可能会导致意外除以零错误,这取决于首先更新哪个属性以及另一个属性的旧值是什么。更新属性 z 的代码只是监听 x 和 y 的变化——它无法提前知道另一个属性的另一个更新即将到来。

现在这个问题很容易避免,但是还有其他类似的情况,比如设置宽度和高度......我是立即调整窗口大小还是等待另一个更改?我是立即为指定的宽度和高度分配内存还是等待?如果宽度和高度是 1 和 100 万,然后更新为 100 万和 1,那么暂时我将有 100 万 x 100 万的宽度和高度......

这可能是一个相当普遍的问题,尽管对我来说它特别适用于 JavaFX 绑定(bind)。我感兴趣的是如何处理这些情况,而不会遇到未定义的行为或执行昂贵的操作,一旦另一个绑定(bind)发生变化就需要重做。

到目前为止,为了避免这些情况,我所做的事情是在设置新值之前首先清除与已知值的绑定(bind),但这对更新绑定(bind)的代码来说是一个负担,它确实不需要知道。

最佳答案

我现在才学习 JavaFX,所以对这个答案持保留态度......欢迎任何更正。我对此很感兴趣,所以做了一些研究。

失效监听器

这个问题的部分答案是 InvalidationListener .您可以详细阅读文档 here , 但本质是一个 ChangeLister立即传播更改,而 InvalidationListener注意到一个值是无效的,但会推迟计算直到需要它。基于“z/(x - y)”计算的两种情况的示例:

首先,琐碎的东西:

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableNumberValue;
import javafx.beans.value.ObservableValue;

public class LazyExample
{
public static void main(String[] args) {
changeListenerCase();
System.out.println("\n=====================================\n");
invalidationListenerCase();
}
...
}

2 种情况(更改和失效监听器)将设置 3 个变量, x , y , z , 计算表达式 z / (x - y)和适当的听众。然后他们调用 manipulate()改变值的方法。记录所有步骤:
    public static void changeListenerCase() {
SimpleDoubleProperty x = new SimpleDoubleProperty(1);
SimpleDoubleProperty y = new SimpleDoubleProperty(2);
SimpleDoubleProperty z = new SimpleDoubleProperty(3);

NumberBinding nb = makeComputed(x,y,z);

nb.addListener(new ChangeListener<Number>() {
@Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println("ChangeListener: " + oldValue + " -> " + newValue);
}
});

// prints 3 times, each after modification
manipulate(x,y,z);

System.out.println("The result after changes with a change listener is: " + nb.doubleValue());
}

public static void invalidationListenerCase() {
SimpleDoubleProperty x = new SimpleDoubleProperty(1);
SimpleDoubleProperty y = new SimpleDoubleProperty(2);
SimpleDoubleProperty z = new SimpleDoubleProperty(3);

NumberBinding nb = makeComputed(x,y,z);

nb.addListener(new InvalidationListener() {
@Override public void invalidated(Observable observable) {
System.out.println("Invalidated");
}
});

// will print only once, when the result is first invalidated
// note that the result is NOT calculated until it is actually requested
manipulate(x,y,z);

System.out.println("The result after changes with an invalidation listener is: " + nb.doubleValue());
}

以及常用的方法:
    private static NumberBinding makeComputed(final ObservableNumberValue x, final ObservableNumberValue y, final ObservableNumberValue z) {
return new DoubleBinding() {
{
bind(x,y,z);
}
@Override protected double computeValue() {
System.out.println("...CALCULATING...");
return z.doubleValue() / (x.doubleValue()-y.doubleValue());
}
};
}

private static void manipulate(SimpleDoubleProperty x, SimpleDoubleProperty y, SimpleDoubleProperty z) {
System.out.println("Changing z...");
z.set(13);
System.out.println("Changing y...");
y.set(1);
System.out.println("Changing x...");
x.set(2);
}

输出是:

...CALCULATING...
Changing z...
...CALCULATING...
ChangeListener: -3.0 -> -13.0
Changing y...
...CALCULATING...
ChangeListener: -13.0 -> Infinity
Changing x...
...CALCULATING...
ChangeListener: Infinity -> 13.0
The result after changes with a change listener is: 13.0

=====================================

...CALCULATING...
Changing z...
Invalidated
Changing y...
Changing x...
...CALCULATING...
The result after changes with an invalidation listener is: 13.0

所以在第一种情况下,计算量过多, infinity案子。在第二种情况下,数据在第一次更改时被标记为无效,然后仅在需要时重新计算。

脉搏

绑定(bind)图形属性怎么样,例如某物的宽度和高度(如您的示例)?似乎 JavaFX 的基础架构不会立即将更改应用到图形属性,而是根据一个名为 Pulse 的信号。 .脉冲是异步调度的,在执行时,将根据节点属性的当前状态更新 UI。动画中的每一帧和 UI 属性的每次更改都会安排一个脉冲运行。

我不知道在您的示例情况下会发生什么,初始宽度=1px 和高度=106px,代码设置宽度=106px(一步,调度脉冲)然后高度=1px(第二步)。如果第一步尚未处理,第二步是否会发出另一个脉冲?从 JavaFX 的角度来看,合理的做法是让管道只处理 1 个脉冲事件,但我需要一些引用。但是,即使处理了两个事件,第一个事件也应该处理整个状态变化(宽度和高度),因此变化发生在一个视觉步骤中。

开发人员必须考虑我相信的架构。假设有一个单独的任务(伪代码):
width = lengthyComputation();
Platform.runLater(node.setWidth(width));
height = anotherLengthyComputation();
Platform.runLater(node.setHeight(height));

我猜如果第一个脉冲事件有机会运行,那么用户会看到宽度的变化 - 暂停 - 高度的变化。最好把它写成(同样,总是在后台任务中)(伪代码):
width = lengthyComputation();
height = anotherLengthyComputation();
Platform.runLater(node.setWidth(width));
Platform.runLater(node.setHeight(height));

更新(来自 john16384 的评论):根据 this不能直接听脉搏。但是,可以扩展 javafx.scene.Parent 的某些方法。每个脉冲运行一次并达到相同的效果。所以你要么扩展 layoutChildren() ,如果不需要更改子树或 computePrefHeight(double width) 中的任何一个/ computePrefWidth(double height) , 如果子树将被修改。

关于binding - 如何处理相关的数据绑定(bind)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19749389/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com