Графика c ButtonCell ComboBox - PullRequest
1 голос
/ 20 января 2020

Пользователю предоставляется выбор из двух фигур (круг и квадрат). ComboBox также может иметь пустой выбор. Графики c для кнопки ComboBox отображаются только в том случае, если элемент перечисления добавлен в ComboBox.

Я хочу, чтобы пользователь мог выбирать только из двух фигур, поэтому я не добавляю опцию Пустой в качестве элемента ComboBox.

Проблема: при выборе опции Пустой строка не отображается на кнопке.

enum Shapes{
    Circle, Square, Empty;
}

@Override
public void start(Stage stage) {

    ComboBox<Shapes> shapeBox = new ComboBox<>();
    shapeBox.setCellFactory(param-> new ShapeListCell());
    shapeBox.setButtonCell(new ShapeListCell());

    shapeBox.getItems().add(Shapes.Circle);
    shapeBox.getItems().add(Shapes.Square);
    //shapeBox.getItems().add(Shapes.Empty);
    shapeBox.setValue(Shapes.Empty);

    Button clearBtn = new Button("Clear selection");
    clearBtn.setOnAction(e->shapeBox.setValue(Shapes.Empty));

    HBox root = new HBox(shapeBox,clearBtn);
    root.setSpacing(10);
    Scene scene = new Scene(root, 400,200);
    stage.setScene(scene);
    stage.show();
}


private class ShapeListCell extends ListCell<Shapes> {
    double r = 10;
    @Override
    public void updateItem(Shapes item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            setText(item.toString());

            switch (item) {
            case Circle:
                setGraphic(new Circle(r, r, r));
                break;
            case Empty:
                setGraphic(new Line(0, r, r*2, r));
                break;
            case Square:
                setGraphic(new Rectangle(r*2, r*2));
                break;
            }
        }
    }
}

Когда Пусто добавлено в ComboBox:

enter image description here

Когда Пусто НЕ добавляется в ComboBox:

enter image description here

Я использую java версия "1.8.0_202"

1 Ответ

3 голосов
/ 20 января 2020

Это ошибка : ComboBoxListViewSkin делает очень грязные вещи, когда пытается настроить buttonCell для неконтролируемого значения. Один из виновников - updateDisplayText, который изменяет свойство text / graphi c кнопки buttonCell под ногами, то есть без прохождения updateItem:

final StringConverter<T> c = comboBox.getConverter();
final String promptText = comboBox.getPromptText();
String s = item == null && promptText != null ? promptText :
           c == null ? (item == null ? null : item.toString()) : c.toString(item);
// here it sets the text - this is why the "Empty" is showing initially
cell.setText(s);
// here it unconditionally nulls the graphic - this is why the line never shows up
cell.setGraphic(null);

Чтобы взломать, нам нужна очень специальная ячейка, которая знает это неправильное поведение и делает все возможное (что, очевидно, сильно зависит от реализации, так что будьте осторожны!) справиться с ним. По сути, он должен выполнить две вещи

  • , в особом случае - его состояние в updateItem, когда он находится в роли buttonCell, а значение комбо не содержится в списке
  • и позаботиться его начального состояния и отменить любые начальные тряски из скина

, первые из которых зацепляются всякий раз, когда неподдерживаемое значение выбирается позднее во время жизни комбо, последнее необходимо из-за "раннего" ленивого внутреннего скина конфигурация.

Пример ячейки:

public class ShapeListCell2 extends ListCell<Shapes> {
    double r = 10;
    Circle circle = new Circle(r, r, r);
    Line line = new Line(0, r, r*2, r);
    Rectangle rect = new Rectangle(r*2, r*2);
    ComboBox<Shapes> combo;
    InvalidationListener gl = obs -> graphicUpdated();

    /**
     * Use this constructor in the combo's cellFactory.
     */
    public ShapeListCell2() {
        this(null);
    }

    /**
     * Use this constructor when being the button cell.
     * @param combo
     */
    public ShapeListCell2(ComboBox combo) {
        this.combo = combo;
        if (isButtonCell()) {
            // initialize with empty text/graphic
            resetButtonCell();
            // register listener to reset on first nulling by skin
            graphicProperty().addListener(gl);
        }
    }

    private void graphicUpdated() {
        // remove listener
        graphicProperty().removeListener(gl);
        resetButtonCell();
    }

    protected void resetButtonCell() {
        setText(Shapes.Empty.toString());
        setGraphic(line);
    }

    protected boolean isButtonCell() {
        return combo != null;
    }

    @Override
    public void updateItem(Shapes item, boolean empty) {
        super.updateItem(item, empty);

        // special case: buttonCell with uncontained value
        if (isButtonCell() && getIndex() < 0 && combo.getValue() != null) {
            resetButtonCell();
            return;
        }
        if (empty || item == null) {
            setText(null);
            setGraphic(null);
        } else {
            setText(item.toString());
            switch (item) {
            case Circle:
                setGraphic(circle);
                break;
            case Empty:
                setGraphic(line);
                break;
            case Square:
                setGraphic(rect);
                break;
            }
        }
    }
}

Осторожно: это хак для fx11 +, похоже, что он не помогает на fx8 в соответствии с обратной связью от OP.

Обратите внимание, что я переместил все экземпляры узла из updateItem - он не должен иметь большой нагрузки:)

...