java - 为什么自定义控件不随其子级一起调整大小?
问题描述
考虑以下最小的可重现示例:
public class Launcher extends Application {
public static final double SCENE_SIZE = 500;
@Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setGridLinesVisible(true);
grid.getColumnConstraints().setAll(
new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, HPos.LEFT, false),
new ColumnConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.LEFT, false)
);
grid.getRowConstraints().setAll(
new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false),
new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false),
new RowConstraints(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, USE_COMPUTED_SIZE, Priority.NEVER, VPos.TOP, false)
);
CustomControl customControl = new CustomControl("Lorem Ipsum ".repeat(10));
customControl.prefWidthProperty().bind(grid.widthProperty());
TextFlow textFlow = new TextFlow(new Text("Lorem Ipsum ".repeat(10)));
textFlow.prefWidthProperty().bind(grid.widthProperty());
grid.add(new Label("0"), 0, 0);
grid.add(customControl, 1, 0);
grid.add(new Label("1"), 0, 1);
grid.add(textFlow, 1, 1);
grid.add(new Label("2"), 0, 2);
grid.add(new Label("..."), 1, 2);
Scene scene = new Scene(grid, SCENE_SIZE, SCENE_SIZE);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(t -> Platform.exit());
primaryStage.show();
}
static class CustomControl extends Control {
private final StringProperty text = new SimpleStringProperty();
public CustomControl(String text) { setText(text); }
public String getText() { return text.get(); }
public StringProperty textProperty() { return text; }
public void setText(String text) { this.text.set(text); }
@Override
protected Skin<?> createDefaultSkin() { return new CustomControlSkin(this); }
}
static class CustomControlSkin extends SkinBase<CustomControl> {
TextFlow textFlow;
public CustomControlSkin(CustomControl control) {
super(control);
textFlow = new TextFlow();
textFlow.getChildren().setAll(new Text(control.getText()));
control.textProperty().addListener(
(obs, old, value) -> textFlow.getChildren().setAll(new Text(control.getText()))
);
control.heightProperty().addListener((observable, oldValue, newValue) -> System.out.println("control = " + newValue));
textFlow.heightProperty().addListener((observable, oldValue, newValue) -> { System.out.println("textFlow = " + newValue); });
getChildren().setAll(textFlow);
}
}
}
窗口调整大小会触发 TextFlow 自动换行,因此 TextFlow 会更改其高度。问题是它不会导致 CustomControl 高度变化,它保持不变(注意控制台输出)。
同时,下一个网格行上的“纯”TextFlow 的行为正是 CustomControl 所期望的。它随着窗口大小的调整来回改变其高度(以及 GridPane 行高)。
我认为任何控件都会根据孩子的高度自动改变自己的高度(SkinBase 包含很多方法),但似乎我错了。简单地在 TextFlow 监听器中设置控制高度是没有帮助的,因为这样只能增加它。
更新:
好的,我找到了问题所在。发生这种情况是因为 TextFlow 需要实际宽度来计算其首选高度,但 SkinBase 使用负宽度值。解决方案:
control.widthProperty().addListener((observable, oldValue, newValue) -> {
control.setPrefHeight(textFlow.prefHeight(newValue.doubleValue()));
});
如果有更好的方法随时分享:)
更新 2:
根据@Slaw 的建议,这个也可以。奇怪的是width
arg 值总是-1
,所以我们必须从 skinnable 中获取宽度值。
static class CustomControlSkin extends SkinBase<CustomControl> {
// ...
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return textFlow.prefHeight(getSkinnable().getWidth() != 0 ? getSkinnable().getWidth() : -1);
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
}
解决方案
根据@Slaw 的建议,这是一种可能的解决方案。奇怪的是width
arg 值总是-1
,所以我们必须从 skinnable 中获取宽度值。
static class CustomControlSkin extends SkinBase<CustomControl> {
// ...
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return textFlow.prefHeight(getSkinnable().getWidth() != 0 ? getSkinnable().getWidth() : -1);
}
@Override
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
double leftInset) {
return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
}
每次 Control 宽度更改时,它都会调用computeMaxHeight()
它,这只是将计算委托给SkinBase
. 然后我们只是强制 Control 始终使用 TextFlow 首选项高度。
推荐阅读
- c# - 在依赖注入中注册多个 .Net Core IHostedService - .net core 中的后台服务
- macos - Mac 上 MAC 供应商文件的位置
- javascript - 以毫秒为单位显示图像
- powershell - 如何使用Powershell在文件中用引号和括号替换字符串
- reactjs - 如何在 react-dnd 中实现基于类的组件中的拖放
- jquery - 在无线电选择上触发 jQuery 函数以获取特定值
- javascript - 切换 div,因此一次只能打开一个,但也可以将它们全部关闭 javascript
- javascript - 在 React 中创建广义锚标记
- rest - 如何在没有登录名/密码的情况下保护来自 SPA 登录页面的 mailchimp 订阅的端点?
- java - 在 Android 上存储加密密钥