我在 javafx 中编写了一个类,其中有两个双向绑定的属性,并且正在测试一些边缘情况。我发现,如果您从无效侦听器内部更改其中一个属性的值,则会导致属性不同步。这是一个小例子:
static class A {
IntegerProperty x = new SimpleIntegerProperty(this, "x", 0);
IntegerProperty y = new SimpleIntegerProperty(this, "y", 0);
A() {
x.bindBidirectional(y);
}
}
static void testA() {
A a = new A();
a.x.addListener( o -> { //InvalidationListener
if (a.x.get() < 0) a.x.set(0);
});
// after y is set to a negative value, x and y hold different values
// until either y is set to a value that's >= 0 or x is set to any value
a.y.set(-2);
System.out.println(a.x.get());
System.out.println(a.y.get());
}
输出:
0
-2
我假设使用双向绑定时,更改一个属性总是会导致另一个属性更新。编写这样的无效侦听器似乎很少见(并且可能不明智),但我在这里是出于防御考虑。如果至少有一个这些属性被暴露,我不希望它有可能破坏我的类的任何不变量。我认为这里有三种可能的解释:
双向绑定的约定并不是它们始终保持同步(要么它们保持相同的值,要么它们被标记为无效),而只是基于尽力而为。因此,类不变量不应基于这一事实。
更改失效侦听器中的值会破坏双向绑定的前提条件,应避免这样做。否则它们始终保持同步。因此,您可以根据这一事实制定类不变量,因为要求客户端不应编写这样的失效侦听器是合理的。
这是一个错误,无论如何它们实际上应该是同步的。我猜如果这是真的,那么你很容易产生无限循环的更改通知(比如向 y 添加一个始终将值设置为 < 0 的无效侦听器)。因此,由客户端来防止这种情况发生。
这些解释中是否有任何接近事实的解释,或者我是否遗漏了其他内容?另外,我很想知道是否存在其他类型的绑定操作可以考虑到这些情况。
我认为你的第二个理论是正确的:
双向绑定操作
要了解双向绑定操作,您可以查看源代码。
例如,参见TypedNumberBidirectionBinding。它有一个标志,
updating
用于设置和检查属性是否无效。如果在无效侦听器中再次更新该属性,则更新标志将为 true,并且将跳过属性值的同步。(为了举例说明,简化了无效代码)。
推荐
InvalidationListener javadoc指出:
因此我建议您不要“从无效侦听器内部更改其中一个属性的值”。
滑盖盒
这是因为 Slider 类做了不推荐的事情。它在无效侦听器中修改了滑块值属性的值。
修改是将变化值限制在滑块范围内。
这导致 Slider 类的 valueProperty 上的双向绑定在超出范围时无法正确同步。
滑块文档说明:
如何围绕此进行设计
正如这个答案所建议的:
在 JavaFX API 的其他地方,对于类似的情况,使用具有双属性集的方法,例如节点读/写禁用属性和只读禁用属性,或Window类中的只读属性使用,例如输出比例只读和渲染比例读/写属性。
我将 jewelsea 的答案标记为正确,我认为它确实很好地澄清了问题。我只是想补充一点,对于我的特定情况,因为我希望我的绑定始终有效,解决方案是不直接公开我的绑定属性,而是使用
ReadOnlyObjectWrapper
将我的属性设置为类外的只读。由于无法设置只读属性,因此您无法将破坏绑定的那种无效侦听器附加到它们。您仍然可以通过自定义设置器或第二个属性(如果需要)允许修改。我认为(事后看来)Slider 应该采用类似的方法来实现。例如:
现在,所有绑定都可以正常工作
requestedValue
,而value
不能直接绑定。这也使代码更加自文档化 - 请求的值与实际值不同。请注意,没有必要有请求的最小值/最大值,因为它们实际上不能被否决,而是更改例如min
可能会导致max
和value
也发生变化。