Связывание с свойствами JavaFX объекта, который можно переключить

Привязка к свойствам объекта, который сам обернут в свойство, кажется чем-то, что делает много в типичных приложениях, есть ли лучший способ сделать это в JavaFX, чем то, что я делаю ниже?

Некоторые подробности для объяснения: я хочу сделать GUI в JavaFX 2.2 для управления несколькими элементами. Я создал небольшой пример для проверки всего, в котором элементы являются людьми. Набор людей отображается по-своему (не список или дерево, но я не думаю, что это имеет значение здесь), и я могу выбрать один.

В боковой панели я могу редактировать выбранного человека. Обновления сразу отображаются в списке лиц, и когда я выбираю другого человека, панель редактирования обновляется.

Двунаправленное связывание JavaFX кажется идеальным для этой цели. В настоящее время у меня есть это для контроллера fx: "Панель редактирования человека":

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?

Ответ 1

Кажется, это не то, что можно сделать более элегантно в 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;
    }
}

Как сказал Скоттб в своем ответе, связующая ссылка это не всегда то, что вы хотите в любом случае. Если вы хотите иметь возможность отменять/фиксировать изменения, вы можете реализовать это, используя также подобное (но, вероятно, будет еще труднее прочитать полученный код).

Ответ 2

Лично я не нахожу код, который вы написали в обработчике событий, чтобы быть громоздким или kludgy. Это то, что обычно делают обработчики событий в GUI, imo.

Спросите себя, хотя... действительно обязательна в ваших обстоятельствах?

Если вы должны иметь обновления в реальном времени для изменений, которые вы сделали на одной панели, чтобы они отражались в другой, вы, вероятно, использовали самое простое решение. Однако существуют трудности, присущие этому дизайну интерфейса, и это может быть не лучшим для всех ситуаций. Что делать, если пользователь должен отменить внесенные изменения? У вас есть способ отбросить изменения, если он передумает? Иногда изменения в режиме реального времени от редактирования не желательны, и в таких случаях привязка объектов модели данных к объектам пользовательского интерфейса может быть не очень хорошей идеей.