Томас Микула имеет библиотеку под названием ReactFX , которая включает в себя версии свойств JavaFX, которые, как правило, работают намного лучше и обладают нужной вам функциональностью. Посмотрите на интерфейсы Val
и Var
в пакете org.reactfx.value
. Похоже, что это не находится в процессе активного обслуживания.
Причина, по которой вы видите исключения переполнения стека в вашем примере, заключается в том, что изменение каждой отдельной координаты вызывает изменение координат в другом представлении. Эти отдельные изменения, конечно же, не являются противоположностями друг другу, поэтому вы в конечном итоге получите бесконечное число изменений oop. (Например, изменение x
приводит к изменению az
, что приводит к тому, что сферическое представление является точкой, отличной от той, которую представляло изменение в x
, так что это приводит к дальнейшему изменению x
, et c.) Если вы «распыляете» три изменения в каждом представлении, то теоретически вы останавливаете бесконечную рекурсию, пока две функции являются точными противоположностями друг другу.
Вероятно, лучший способ достичь этого - использовать один объект вместо трех разных значений. Это дает дополнительное преимущество, заключающееся в том, что при изменении точки в трехмерном пространстве вы наблюдаете одно изменение вместо трех. Поэтому здесь я бы использовал ObjectProperty<T>
для некоторого подходящего типа T
.
Последнее, на что следует обратить внимание: когда вы работаете с арифметикой с плавающей запятой c, как в вашем примере, вы по-прежнему уязвимы к исключениям переполнения стека, поскольку численные ошибки в вычислениях мешают функциям быть точной противоположностью друг другу.
Ваша идея установить и снять флаг, когда они меняются, - правильный подход; Вы просто должны выяснить, где сохранить этот флаг. В этом примере я создаю отдельный класс BidirectionalBinding
, который позволяет хранить флаг, а также отсоединять обоих слушателей с помощью метода в классе.
import java.util.function.Function;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
public class BidirectionalBinding<T,U> {
private final Property<T> source ;
private final Property<U> target ;
private boolean changing = false ;
private ChangeListener<? super T> sourceListener;
private ChangeListener<? super U> targetListener;
public BidirectionalBinding(Property<T> source, Property<U> target,
Function<T,U> mapping, Function<U,T> inverseMapping) {
this.source = source ;
this.target = target ;
target.setValue(mapping.apply(source.getValue()));
sourceListener = (obs, oldSourceValue, newSourceValue) -> {
if (! changing) {
changing = true ;
target.setValue(mapping.apply(newSourceValue));
}
changing = false ;
};
source.addListener(sourceListener);
targetListener = (obs, oldTargetValue, newTargetValue) -> {
if (! changing) {
changing = true ;
source.setValue(inverseMapping.apply(newTargetValue));
}
changing = false ;
};
target.addListener(targetListener);
}
public void unbind() {
source.removeListener(sourceListener);
target.removeListener(targetListener);
}
public Property<T> getSource() {
return source;
}
public Property<U> getTarget() {
return target;
}
public static class Cartesian {
private final double x ;
private final double y ;
private final double z ;
public Cartesian(double x, double y, double z) {
super();
this.x = x;
this.y = y;
this.z = z;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
@Override
public String toString() {
return String.format("[x=%f, y=%f, z=%f]", x, y, z);
}
@Override
public boolean equals(Object o) {
if (! (o instanceof Cartesian)) return false ;
if (o == this) return true ;
Cartesian other = (Cartesian) o ;
return x == other.x && y == other.y && z == other.z ;
}
}
public static class Spherical {
private final double az ;
private final double el ;
private final double rho ;
public Spherical(double az, double el, double rho) {
super();
this.az = az;
this.el = el;
this.rho = rho;
}
public double getAz() {
return az;
}
public double getEl() {
return el;
}
public double getRho() {
return rho;
}
@Override
public String toString() {
return String.format("[az=%f, el=%f, rho=%f]", az, el, rho);
}
@Override
public boolean equals(Object o) {
if (! (o instanceof Spherical)) return false ;
if (o == this) return true ;
Spherical other = (Spherical) o ;
return az == other.az && el == other.el && rho == other.rho ;
}
}
// test case:
public static void main(String[] args) {
ObjectProperty<Cartesian> cartesian = new SimpleObjectProperty<>(new Cartesian(0,0,0));
ObjectProperty<Spherical> spherical = new SimpleObjectProperty<>(new Spherical(0,0,0));
Function<Cartesian, Spherical> cartesianToSpherical = cart -> {
double x = cart.getX();
double y = cart.getY();
double z = cart.getZ();
double az = Math.atan2(y, x);
double el = Math.atan2(Math.sqrt(x*x + y*y), z);
double rho = Math.sqrt(x*x + y*y + z*z) ;
return new Spherical(az, el, rho);
};
Function<Spherical, Cartesian> sphericalToCartesian = spher -> {
double az = spher.getAz();
double el = spher.getEl();
double rho = spher.getRho();
double x = rho*Math.sin(el)*Math.cos(az);
double y = rho*Math.sin(el)*Math.sin(az);
double z = rho*Math.cos(el);
return new Cartesian(x, y, z);
};
BidirectionalBinding<Cartesian, Spherical> binding = new BidirectionalBinding<>(cartesian, spherical,
cartesianToSpherical, sphericalToCartesian);
System.out.println(cartesian.get());
System.out.println(spherical.get());
System.out.println("\nSetting cartesian to [1,1,1]");
cartesian.set(new Cartesian(1, 1, 1));
System.out.println(cartesian.get());
System.out.println(spherical.get());
System.out.println("\nSetting sphercial to [pi/4, pi/4, 1]");
spherical.set(new Spherical(Math.PI/4, Math.PI/4, 1));
System.out.println(cartesian.get());
System.out.println(spherical.get());
binding.unbind();
}
}
Если вы хотите сохранить три отдельных свойства для три координаты в каждом представлении, тогда я думаю, что вы все еще могли бы использовать эту общую стратегию, но вам, вероятно, понадобится определенный класс c, который принимает шесть свойств в качестве полей и задает c для этой проблемы вместо будучи обобщенным c как в этом примере.
С ReactFX (и классами Cartesian
и Spherical
, которые я определил), я думаю, вы можете сделать что-то вроде
ObjectProperty<Cartesian> cartesian = new SimpleObjectProperty<>(new Cartesian(0,0,0));
Property<Spherical> = Var.mapBidirectional(cartesian, cartesianToSpherical, sphericalToCartesian);
Если Томас все еще активен на этом сайте, он ' скорее всего, даст вам некоторые дополнительные идеи (и, возможно, более надежные реализации, чем те, которые я предлагаю).