javafx - 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”
解决方案
这是一个错误: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 - 它不能有任何重负载:)
推荐阅读
- c# - 如何从列表中的多列添加字符串值
c# windows 窗体 - typescript - 如何配置 TypeScript 编译器以使用新环境中的特定附加语法来定位一个环境?
- css - 在手机上查看时如何更改图像的高度
- c# - Cosmos DB 参数化 SQL 查询不适用于双引号参数
- material-ui - 使用 Material-UI 和 create-react-app (react 16)
- python - 如何在kivy中只接受数字输入(双)
- firebase - Flutter Firestore:getToken由于令牌更改而中止
- javascript - 将图像缓冲区转换为 base64
- html - Flex col 增长到与最大高度相同的高度
- sql - 为什么要使用相关子查询?