Реализовать Spinner в TableView для отображения/редактирования значения пользовательского объекта

У меня есть ObservableList пользовательских объектов RecipeObject_Fermentable, свойства которых я показываю пользователю через TableView. По большей части это работает хорошо; когда я заполняю ObservableList новым элементом, TableView отображает его содержимое.

Использование метода .setCellValueFactory() для каждого столбца позволяет мне очень легко отображать простые объекты (строки и двойники) в виде текста в моем TableView. Например, я могу получить доступ к свойству name типа String с...

private TableColumn<RecipeObject_Fermentable, String> tableColumn_rf_name;
tableColumn_rf_name.setCellValueFactory(new PropertyValueFactory<>("name"));

Проблема в том, что я хочу показать свойство 'weight' (типа Double) внутри Spinner для столбца tableColumn_rf_weight (и чтобы оно отображало единицы измерения и редактировалось текстом, но это не основная проблема) , и я не могу понять, как я могу это сделать и как лучше всего это сделать.

Я пытался найти решение через сообщения других людей, но я не могу заставить его работать. Я приписываю это отчасти тому, что не понимаю, что на самом деле представляют собой методы .setCellFactory() и .setCellValueFactory() для каждого TableColumn и как они связаны со свойствами пользовательских объектов в моем ObservableList. Если бы кто-то мог кратко объяснить это или указать на что-то, что делает, я был бы признателен. [РЕДАКТИРОВАТЬ: Я ДЕЙСТВИТЕЛЬНО пытался исследовать это, но я не нашел полного объяснения, которое я мог бы понять из моего текущего понимания Java и JavaFX]

Итак, как я могу заставить TableView отображать Spinner для каждой записи данных в этом столбце?

Следующие фрагменты кода урезаны, чтобы показать только полезную информацию. Дайте мне знать, если я что-то пропустил.

Пользовательский объект

public class RecipeObject_Fermentable {

// Object properties
private String name;            // Displayed and referenced name of the fermentable
private Double srm;             // Colour of fermentable in SRM
private Double pkgl;            // Specific gravity points per kg per liter
private Double weight;          // Weight in kilograms
private Double percent;         // Total percent to grain bill as percentage
private BooleanProperty lateadd;// Late addition toggle

// Constructor
public RecipeObject_Fermentable(String name, Double weight, Double srm, Double pkgl, Double contribution, boolean lateadd) {
    this.name = name;
    this.srm = srm;
    this.pkgl = pkgl;
    this.weight = weight;
    this.percent = contribution;
    this.lateadd = new SimpleBooleanProperty(lateadd);
}

// Constructor from fermentable object
public RecipeObject_Fermentable(Fermentable f) {
    this.name = f.getName();
    this.srm = f.getSrm();
    this.pkgl = f.getPkgl();
    if (f.getType().equals(FermType.GRAIN)) {
        if (f.getSubtype().equals(FermSubtype.BASE_MALT)) {
            this.weight = Double.valueOf(5);
        } else {
            this.weight = Double.valueOf(1);
        }
    } else {
        this.weight = Double.valueOf(0);
    }
    this.percent = Double.valueOf(0);
    this.lateadd = new SimpleBooleanProperty(false);
}

public String getName() {
    return this.name;
}

public Double getSrm() {
    return this.srm;
}

public Double getPkgl() {
    return this.pkgl;
}

public Double getWeight() {
    return this.weight;
}

public Double getContribution() {
    return this.percent;
}

public ObservableBooleanValue isLateadd() {
    return lateadd;
}

public void setName(String name) {
    this.name = name;
}

public void setSrm(Double srm) {
    this.srm = srm;
}

public void setPkgl(Double pkgl) {
    this.pkgl = pkgl;
}

public void setWeight(Double value) {
    this.weight = value;
}

public void setContribution(Double contribution) {
    this.percent = contribution;
}

public void setLateadd(Boolean checked) {
    this.lateadd.set(checked);
}
}

Другой код в моем контроллере

// Link and define FXML objects for tableView and its columns
@FXML
private TableView<RecipeObject_Fermentable> tableview_recipeFermentables;
@FXML
private TableColumn<RecipeObject_Fermentable, String> tableColumn_rf_name;
@FXML
private TableColumn<RecipeObject_Fermentable, Double> tableColumn_rf_weight;
@FXML
private TableColumn<RecipeObject_Fermentable, Double> tableColumn_rf_percent;
@FXML
private TableColumn<RecipeObject_Fermentable, Boolean> tableColumn_rf_lateAddition;

// Set up observable list of custom object
private ObservableList<RecipeObject_Fermentable> fermentables_recipe = 
FXCollections.observableArrayList()

// Set up table data
tableview_recipeFermentables.setItems(fermentables_recipe);
tableColumn_rf_name.setCellValueFactory(new PropertyValueFactory<>("name"));
// ---> tableColumn_rf_weight.setCellValueFactory();
// ---> tableColumn_rf_weight.setCellFactory();
tableColumn_rf_percent.setCellValueFactory(new PropertyValueFactory<>("percent"));
tableColumn_rf_lateAddition.setCellValueFactory(p->p.getValue().isLateadd());
tableColumn_rf_lateAddition.setCellFactory(CheckBoxTableCell.forTableColumn( tableColumn_rf_lateAddition));

Заранее спасибо.


person Joe    schedule 23.04.2018    source источник
comment
несвязанный: пожалуйста, изучите соглашения об именах Java и придерживайтесь их   -  person kleopatra    schedule 23.04.2018
comment
Не могли бы вы перечислить термины, которые мне нужно исправить/отредактировать?   -  person Joe    schedule 23.04.2018
comment
хм ... не понимаю, зачем это нужно: просто найдите соглашения об именах java и сравните свой код с правилами ... вы сразу увидите, что не подходит;)   -  person kleopatra    schedule 23.04.2018
comment
Все, что я вижу, это мое вопиющее использование символов подчеркивания в именах, но я чувствовал необходимость использовать их, поскольку многие элементы (многие из которых вы не видите) похожи, но различны. Есть ли другие проблемы, которые я пропустил?   -  person Joe    schedule 23.04.2018
comment
не знаю - дальше не читал, когда увидел их ;) Ну, код для общения (помимо функционала, конечно) - важно не столько то, что вы чувствуете, начиная с языка, сколько то, как другие могут легко , быстро понять, что вы кодировали. Эти подчеркивания очень разрушительны ... рано или поздно вы будете вынуждены применять соглашения, чем раньше, тем лучше для всех :)   -  person kleopatra    schedule 23.04.2018
comment
en.wikipedia.org/wiki/Naming_convention_(programming)#Java   -  person James_D    schedule 23.04.2018
comment
С другой стороны, я не уверен, почему мой вопрос имеет два голоса против. Я считаю, что разместил законный вопрос и достаточно подробно его описал. Как я могу улучшить его?   -  person Joe    schedule 24.04.2018


Ответы (1)


Вам нужен пользовательский TableCell. Кроме того, должен быть способ передать данные обратно элементу. Лучше всего это сделать с помощью DoubleProperty, но в вашем случае вы можете работать с JavaBeanDoubleProperty, если значения null не разрешены).

final JavaBeanDoublePropertyBuilder weightBuilder
                   = JavaBeanDoublePropertyBuilder.create()
                                                  .beanClass(RecipeObject_Fermentable.class)
                                                  .name("weight");
tableColumn_rf_weight.setCellValueFactory(cd -> weightBuilder.bean(cd.getValue()).build());
public class DoubleSpinnerCell<T> extends TableCell<T, Number> {
    private final Spinner<Double> spinner = new Spinner​(0, Double.MAX_VALUE, 0, 0.01);
    private boolean ignoreUpdate; // flag preventing updates triggered from ui/initialisation

    {
        spinner.valueProperty().addListener((o, oldValue, newValue) -> {
            if (!ignoreUpdate) {
                ignoreUpdate = true;
                WritableValue<Number> property = (WritableValue<Number>) getTableColumn().getCellObservableValue((T) getTableRow().getItem());
                property.setValue(newValue);
                ignoreUpdate = false;
            }
        });
    }


    @Override
    protected void updateItem(Number item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {
            setGraphic(null);
        } else {
            ignoreUpdate = true;
            spinner.getValueFactory().setValue(item.doubleValue());
            setGraphic(spinner);
            ignoreUpdate = false;
        }
    }
}
tableColumn_rf_weight.setCellFactory(c -> new DoubleSpinnerCell<RecipeObject_Fermentable>());

Однако для этого необходимо, чтобы weight было установлено ненулевое значение. (Я рекомендую использовать примитив double вместо Double.)

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

Вам также необходимо изменить тип элемента столбца на Number:

@FXML
private TableColumn<RecipeObject_Fermentable, Number> tableColumn_rf_weight;
person fabian    schedule 23.04.2018
comment
Спасибо за такой подробный ответ и пример. Я испытаю это как можно скорее. - person Joe; 23.04.2018
comment
Я попытался реализовать ваше решение, и оно почти принято компилятором, но строка tableColumn_rf_weight.setCellValueFactory(cd -> weightBuilder.bean(cd.getValue()).build()); дает JavaBeanDoubleProperty не может быть преобразована в ObservableValue‹Double›. Следующая строка tableColumn_rf_weight.setCellFactory(c -> new DoubleSpinnerCell<>()); дает невозможно определить аргументы типа для DoubleSpinnerCell‹›, и я не уверен, какие аргументы ей указать. Прошу прощения, если это тривиальные вопросы, но я не очень хорошо знаком с методами .setCellFactory() и .setCellValueFactory(). - person Joe; 23.04.2018
comment
@Joe Я совершенно упустил из виду, что вы используете Double в качестве параметра типа для TableColumn. Его нужно изменить на Number, так как свойства double реализуют ObservableValue<Number>. - person fabian; 23.04.2018
comment
Оно работает! Но попытка увеличить/уменьшить значение, отображаемое счетчиком, дает мне NumberFormatException, который просачивается через различные стандартные библиотеки Java. Я должен попытаться выяснить, почему это происходит... есть идеи? - person Joe; 23.04.2018
comment
@Joe Окей, кажется, у Spinner проблема с Double.POSITIVE_INFINITY. (Исправлены эта и некоторые другие ошибки.) - person fabian; 24.04.2018
comment
РАБОТАЕТ КРАСИВО. Спасибо за все время и усилия, которые вы вложили в этот ответ! очень ценится. - person Joe; 24.04.2018