Путаница в привязке выпадающего списка JavaFX

#java #scala #javafx

#java #scala #javafx

Вопрос:

У меня есть реализация I18N, которая связывает элементы пользовательского интерфейса JavaFX с помощью свойств, например:

 def translateLabel(l: Label, key: String, args: Any*): Unit =
    l.textProperty().bind(createStringBinding(key, args))
 

Привязка свойств проста и хорошо работает. Однако я борюсь с ComboBox, поскольку он принимает ObservableList (строк в моем случае), и я понятия не имею, как привязать к этому мои функции переводчика. Я не согласен с различием между ObservableValue ObservableList и Property интерфейсами, поскольку все они звучат одинаково.

У него есть itemsProperty() , и valueProperty() , однако, документация для них отсутствует и расплывчата, поэтому я не уверен, где их можно использовать.

Что я хочу сделать, так это иметь поле со списком, в котором все элементы (или, по крайней мере, выбранный / видимый) динамически изменяют язык (I18N), как если бы он был привязан, точно так же, как свойство.

Редактировать:

Просто чтобы упростить понимание, моя текущая реализация:

 private def setAggregatorComboBox(a: Any): Unit = {

    val items: ObservableList[String] = FXCollections.observableArrayList(
        noneOptionText.getValue,
        "COUNT()",
        "AVG()",
        "SUM()"
    )

    measureAggregatorComboBox.getItems.clear()

    measureAggregatorComboBox.getItems.addAll(items)
}
 

Где noneOptionText a StringProperty , который уже привязан к a StringBinding , который преобразуется при создании экземпляра класса таким образом:

 def translateString(sp: StringProperty, key: String, args: Any*): Unit =
        sp.bind(createStringBinding(key, args))
 

Комментарии:

1. items не будет работать так, как вы хотите; noneOptionText.getValue будет оцениваться при items создании; значение первого элемента не изменится при изменении значения noneOptionText .

Ответ №1:

Это itemsProperty() список элементов, отображаемых во всплывающем окне со списком; его значение равно an ObservableList .

Это valueProperty() выбранный элемент (или значение, введенное пользователем, если поле со списком доступно для редактирования).

Что я бы порекомендовал, так это сделать так, чтобы данные в поле со списком были списком ключей, и использовать пользовательские ячейки для привязки текста в каждой ячейке к переводу этих ключей. Я не говорю на scala, но на Java это выглядит так:

 ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll(getAllKeys());

class TranslationCell extends ListCell<String> {

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        textProperty().unbind();
        if (empty || item == null) {
            setText("");
        } else {
            textProperty().bind(createStringBinding(item));
        }
    }
}

comboBox.setCellFactory(lv -> new TranslationCell());
comboBox.setButtonCell(new TranslationCell());
 

Теперь обратите внимание, что valueProperty() содержит ключ для выбранного значения.

Если вы действительно хотите привязать элементы к an ObservableValue<ObservableList<String>> , вы можете сделать что-то вроде:

 comboBox.itemsProperty().bind(Bindings.createObjectBinding(() ->
    FXCollections.observableArrayList(...),
    ...));
 

где первое ... — это varargs String значений, а второе ... — наблюдаемое значение, изменения в котором потребуют пересчета списка. (Итак, в вашем случае, я предполагаю, что у вас есть ObservableValue<Locale> где-то, представляющее текущую локаль; вы бы использовали это для второго аргумента.)

В вашем конкретном случае использования (где интернационализируется только первый элемент списка) может быть проще просто использовать прослушиватель:

 comboBox.getItems().setAll(
    noneOptionTest.getValue(), 
    "COUNT()",
    "AVG()",
    "SUM");
noneOptionTest.addListener((obs, oldVal, newVal) ->
    comboBox.getItems().set(0, newVal));
 

хотя я согласен, что это немного менее элегантно.

Для полноты:

Я не согласен с различием между ObservableValue ObservableList и Property интерфейсами, поскольку все они звучат одинаково.

ObservableValue<T> : представляет единственное значение типа T , которое можно наблюдать (это означает, что код может быть выполнен при его изменении).

Property<T> : представляет собой доступный для записи ObservableValue<T> ; предполагается, что реализации будут иметь фактическую переменную, представляющую значение. Он определяет дополнительные функциональные возможности, позволяющие привязывать его значение к другим ObservableValue<T> .

Так, например:

 DoubleProperty x = new SimpleDoubleProperty(6);
DoubleProperty y = new SimpleDoubleProperty(9);
ObservableValue<Number> product = x.multiply(y);
 

x и y то и Property<Number> другое; реализация SimpleDoubleProperty имеет фактическую double переменную, представляющую это значение, и вы можете делать что-то вроде y.set(7); изменения значения.

С другой стороны, product это не a Property<Number> ; вы не можете изменить его значение (потому что это нарушило бы привязку: объявленный инвариант, который product.getValue() == x.getValue() * y.getValue() ); однако он заметен, поэтому вы можете привязаться к нему:

 BooleanProperty answerCorrect = new SimpleBooleanProperty();
answerCorrect.bind(product.isEqualTo(42));
 

и т.д.

An ObservableList несколько отличается: это a java.util.List (набор элементов), и вы можете наблюдать, как он реагирует на операции в списке. Т.Е. Если вы добавляете слушателя в an ObservableList , слушатель может определить, были ли добавлены или удалены элементы и т.д.

Комментарии:

1. значение немного сложнее, если поле со списком доступно для редактирования .. что вы имеете в виду? Это всегда (за исключением случаев, когда значение привязано) выбранный элемент, не зависящий от возможности редактирования

2. @kleopatra Если он доступен для редактирования, value он может не быть «элементом» в смысле элемента getItems() ; т. Е. Это может быть не что-то выбранное из списка элементов. (Мы могли бы поспорить о семантике того, что подразумевается под «выбранным элементом»; это то, что набрано в «выбранном» и т. Д. Но …) Отредактировано для ясности.

3. тем не менее, SelectedItem (в терминах selectionModel) это значение, которое может содержаться в элементах или нет :). Инвариант: assertEquals(box.getValue(), box.getSelectionModel().getSelectedItem() должен сохраняться всегда и не должен зависеть от возможности редактирования (есть некоторые ошибки, нарушающие его 😉

4. @kleopatra Это правда? В документах говорится: «Очистка выделения в модели выбора не обнуляет свойство valueProperty; оно остается таким же, как и раньше». Итак, после очистки модели выбора, либо getSelectionModel().getSelectedItem() != getValue() или getSelectionModel().getSelectedItem() != null amp;amp; getSelectionModel().getSelectedIndices().size() == 0 , что кажется еще более странным…

5. Однако @ ObjectProperty<ObservableList<T>> Shobe является реализацией ObservableValue<ObservableList<T>> , поэтому также верно, что itemsProperty() возвращает an ObservableValue<ObservableList<T>> . И то, что я на самом деле заявил, касалось привязки : любой Property<S> может быть привязан к любому ObservableValue<S> . So itemsProperty().bind(...) может принимать любой ObservableValue<ObservableList<T>> (который возвращается Bindings.createObjectBinding(...) кодом, который я использовал). (Я согласен, что API несколько перегружен.)