首页 > 解决方案 > ButtonCell ComboBox 的图形

问题描述

用户可以选择两个图形(圆形和方形)。ComboBox 也可能有一个 Empty 选项。只有将枚举元素添加到 ComboBox 时,才会显示 ComboBox 按钮的图形。

我希望用户只能从两个形状中进行选择,所以我没有添加 Empty 选项作为 ComboBox 的元素。

The problem : when the Empty option is selected, the line does not appear on the Button.

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;
            }
        }
    }
}

Empty添加到 ComboBox 时:

在此处输入图像描述

Empty未添加到 ComboBox 时:

在此处输入图像描述

我使用 java 版本“1.8.0_202”

标签: javafxcomboboxjavafx-8

解决方案


这是一个错误:ComboBoxListViewSkin 在尝试将 buttonCell 配置为未包含的值时会做一些很脏的事情。罪魁祸首之一是 updateDisplayText,它改变了它脚下 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+ 的 hack,根据 OP 的反馈,它似乎对 fx8 没有帮助。

请注意,我将所有节点实例移出 updateItem - 它不能有任何重负载:)


推荐阅读