11

绑定到本身包装在属性中的对象的属性似乎在典型应用程序中做了很多事情,在 JavaFX 中是否有比我在下面做的更好的方法来做到这一点?

需要解释的更多细节:我想在 JavaFX 2.2 中制作 GUI,用于管理许多项目。我创建了一个小示例来测试所有内容,其中项目是人。一组人员以自定义方式显示(不是列表或树,但我认为这不重要),我可以选择一个。

在侧面板中,我可以编辑当前选择的人。更新在一组人员中立即可见,当我选择另一个人时,编辑面板会更新。

JavaFX 的双向绑定似乎非常适合此目的。我目前有这个用于“人员编辑”窗格的 fx:controller :

public class PersonEditor implements ChangeListener<Person> {
    @FXML private TextField nameField;
    @FXML private TextField ageField;
    @FXML private TextField heightField;

    public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
        selectedPersonProperty.addListener(this);
    }

    @Override
    public void changed(ObservableValue<? extends Person> observable, Person oldVal, Person newVal) {
        if (oldVal != null) {
            nameField.textProperty().unbindBidirectional(oldVal.nameProperty());
            ageField.textProperty().unbindBidirectional(oldVal.ageProperty());
            heightField.textProperty().unbindBidirectional(oldVal.heightProperty());
        }
        if (newVal != null) {
            nameField.textProperty().bindBidirectional(newVal.nameProperty());
            ageField.textProperty().bindBidirectional(newVal.ageProperty());
            heightField.textProperty().bindBidirectional(newVal.heightProperty());
        }
    }
}

我想知道是否有更好的方法,也许在 JavaFX 中可以绑定到可以更改的对象的属性?我不喜欢我必须手动取消绑定所有属性的事实,感觉就像重复的代码。或者这在 JavaFx 中是否尽可能简单?

4

3 回答 3

3

就个人而言,我认为您在事件处理程序中编写的代码并没有那么笨拙或笨拙。这是事件处理程序通常在 GUI 中做的事情,imo。

问问你自己,虽然......在你的情况下真的有必要绑定吗?

如果您必须对在一个面板中所做的编辑进行实时更新以反映在另一个面板中,那么您可能已经实施了最简单的解决方案。然而,这种 UI 设计存在固有的困难,它可能不是所有情况下的最佳选择。如果用户需要取消他所做的编辑怎么办?如果他改变主意,你有办法回滚编辑吗?有时,来自编辑的实时更改是不可取的,在这种情况下,将数据模型对象绑定到 UI 对象可能不是一个好主意。

于 2013-05-16T14:36:16.870 回答
2

似乎这不是在 JavaFX 中可以更优雅地完成的事情。绑定和解除绑定似乎是最干净的方式。

我自己实现了一种方法来做到这一点。不确定我最终是否会使用它(因为它只是用难以阅读的代码替换了代码重复)。但它有效,它是我自己问题的答案,所以我在这里添加了它。

新的 PersonEditor 类:

public class PersonEditor implements Initializable {
    private SelectedObjectPropertyBinder<Person> selectedObjectPropertyBinder = 
          new SelectedObjectPropertyBinder<Person>();

    @FXML private TextField nameField;
    @FXML private TextField ageField;
    @FXML private TextField heightField;

     @Override
    public void initialize(URL url, ResourceBundle rb) {
        selectedObjectPropertyBinder.getBinders().add(
            new ObjectPropertyBindHelper<Person>(nameField.textProperty()) {
                @Override public Property objectProperty(Person p) 
                { return p.nameProperty(); }
        });
        selectedObjectPropertyBinder.getBinders().add(
            new ObjectPropertyBindHelper<Person>(ageField.textProperty()) {
                @Override public Property objectProperty(Person p) 
                { return p.ageProperty(); }
        });
        selectedObjectPropertyBinder.getBinders().add(
            new ObjectPropertyBindHelper<Person>(heightField.textProperty()) {
                @Override public Property objectProperty(Person p) 
                { return p.heightProperty(); }
        });
    }

    public void setSelection(ObjectProperty<Person> selectedPersonProperty) {
        selectedObjectPropertyBinder.
            setSelectedObjectProperty(selectedPersonProperty);
    }
}

助手类:

public class SelectedObjectPropertyBinder<T> implements ChangeListener<T> {
    private List<ObjectPropertyBindHelper<T>> binders = 
           new ArrayList<ObjectPropertyBindHelper<T>>();

    public void setSelectedObjectProperty(Property<T> selectionProperty) {
        selectionProperty.addListener(this);
    }

    public List<ObjectPropertyBindHelper<T>> getBinders() {
        return binders;
    }

    @Override
    public void changed(ObservableValue<? extends T> observable, 
                        T oldVal, T newVal) {
        if (oldVal != null)
            for (ObjectPropertyBindHelper b : binders)
                b.unbindBi(oldVal);
        if (newVal != null)
            for (ObjectPropertyBindHelper b : binders)
                b.bindBi(newVal);
    }
}

public abstract class ObjectPropertyBindHelper<T> {
    private Property boundProperty;

    public ObjectPropertyBindHelper(Property boundProperty) {
        this.boundProperty = boundProperty;
    }
    public void bindBi(T o) {
        boundProperty.bindBidirectional(objectProperty(o));
    }
    public void unbindBi(T o) {
        boundProperty.unbindBidirectional(objectProperty(o));
    }
    public abstract Property objectProperty(T t);
    public Property getBoundProperty() {
        return boundProperty;
    }
}

正如 scottb 在他的回答中指出的那样,绑定链接这并不总是你想要的。如果您希望能够取消/提交更改,您也可以使用类似的方法来实现它(但可能会更难阅读生成的代码)。

于 2013-05-30T08:53:55.060 回答
0

很久以前就问过这个问题:)无论如何,最近我一直在寻找解决方案并以这种方式实现-单例Form类实现了抽象类:

    public abstract class InputForm {

    private ArrayList<BindingPair> bindedProps;
    private ArrayList<ListenerPair> listenerPairs;

    public InputForm() {
        bindedProps = new ArrayList();
        listenerPairs = new ArrayList();
    }

    public void resetBindings() {
        unbindAll();
        bindedProps = new ArrayList();
    }

    public void resetListeners() {
        removeAllListeners();
        listenerPairs = new ArrayList();
    }

    private void unbindAll() {
        for (BindingPair pair : bindedProps) {
            unbind(pair);
        }
    }

    private void removeAllListeners() {
        for (ListenerPair listenerPair : listenerPairs) {
            removeListener(listenerPair);
        }
    }

    public void bind(BindingPair bindingPair) {
        BindingUtils.bind(bindingPair);
        bindedProps.add(bindingPair);
    }

    public void addListener(ListenerPair listenerPair) {
        ListenerUtils.addListener(listenerPair);
        listenerPairs.add(listenerPair);
    }

    private void unbind(BindingPair bindingPair) {
        BindingUtils.unbind(bindingPair);
    }

    private void removeListener(ListenerPair listenerPair) {
        ListenerUtils.removeListener(listenerPair);
    }
}

public class BindingUtils {

    public static void bind(BindingPair bindingPair) {
        if (bindingPair.isBidirectionalBinding()) {
            if (bindingPair.getStringConverter() != null)
                Bindings.bindBidirectional(bindingPair.propertyToBindProperty(), bindingPair.propertyBindToProperty(), bindingPair.getStringConverter());
            else
                bindingPair.propertyToBindProperty().bindBidirectional(bindingPair.propertyBindToProperty());
        } else {
            bindingPair.propertyToBindProperty().bind(bindingPair.propertyBindToProperty());
        }
    }

    public static void unbind(BindingPair bindingPair) {
        if (bindingPair.isBidirectionalBinding()) {
            bindingPair.propertyToBindProperty().unbindBidirectional(bindingPair.propertyBindToProperty());
        } else {
            bindingPair.propertyToBindProperty().unbind();
        }

    }
}

public class ListenerUtils {

    public static void addListener(ListenerPair listenerPair) {
        if (listenerPair.propertyProperty() != null)
            listenerPair.propertyProperty().addListener(listenerPair.getListener());
        if (listenerPair.roPropertyProperty() != null)
            listenerPair.roPropertyProperty().addListener(listenerPair.getListener());
    }

    public static void removeListener(ListenerPair listenerPair) {
        if (listenerPair.propertyProperty() != null)
            listenerPair.propertyProperty().removeListener(listenerPair.getListener());
        if (listenerPair.roPropertyProperty() != null)
            listenerPair.roPropertyProperty().removeListener(listenerPair.getListener());
    }
}


public class BindingPair {

    private Property propertyToBind;
    private Property propertyBindTo;

    private boolean bidirectionalBinding;
    private StringConverter stringConverter;

    public BindingPair(Property propertyToBind, Property propertyBindTo, boolean bidirectionalBinding, StringConverter stringConverter) {
        this.propertyToBind = propertyToBind;
        this.propertyBindTo = propertyBindTo;
        this.bidirectionalBinding = bidirectionalBinding;
        this.stringConverter = stringConverter;
    }

    public Property propertyToBindProperty() {
        return propertyToBind;
    }

    public Property propertyBindToProperty() {
        return propertyBindTo;
    }

    public boolean isBidirectionalBinding() {
        return bidirectionalBinding;
    }

    public StringConverter getStringConverter() {
        return stringConverter;
    }
}

public class ListenerPair {

    private Property property = null;
    private ChangeListener listener;
    private ReadOnlyObjectProperty roProperty = null;

    public ListenerPair(Property property, ChangeListener listener) {
        this.property = property;
        this.listener = listener;
    }

    public ListenerPair(ChangeListener listener, ReadOnlyObjectProperty roProperty) {
        this.listener = listener;
        this.roProperty = roProperty;
    }

    public Property propertyProperty() {
        return property;
    }

    public ChangeListener getListener() {
        return listener;
    }

    public Object getRoProperty() {
        return roProperty.get();
    }

    public ReadOnlyObjectProperty roPropertyProperty() {
        return roProperty;
    }
}

在 Form 类(实现 InputForm)中,每次通过此表单“渲染”新对象时都会调用两个方法:

private void doBinding() {

        resetBindings();

        bind(new BindingPair(dataXlsFileUrlProp, getControllerMain().getSceneRenderer().getLoadedSceneInfo().xlsDataFileProperty(), false, null));

    }

    private void addListeners() {

        resetListeners();

        addListener(new ListenerPair(dataXlsFileUrlProp, (observable, oldValue, newValue) -> {
            //do smth
        }));
    }

希望有人会帮助这个。

于 2021-02-14T20:47:23.303 回答