【问题标题】:Graphic of ButtonCell ComboBoxButtonCell ComboBox 的图形
【发布时间】:2020-01-20 09:35:48
【问题描述】:

用户可以选择两个图形(圆形和方形)。 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”

【问题讨论】:

    标签: javafx combobox javafx-8


    【解决方案1】:

    It's a bug: 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 - 它不能有任何重负载:)

    【讨论】:

    • 感谢您的详细而快速的回答,我尝试了您的代码。这可行,但是当我从列表中选择一个项目然后单击“清除选择”按钮时,该行不会再次出现。
    • hmm ..worksforme ..你用的是哪个fx版本?
    • 我使用java版本“1.8.0_202”
    • 与所有依赖于实现细节的 hack 一样,这可能并且可能确实(我的是 fx11,不会尝试像 fx8 这样过时的版本)产生很大的不同;)从那时起,皮肤被移入公共范围并修复了很多错误..所以这对你来说再合适不过了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多