如何使ComposedDateTimePicker拥有DateTimePicker的完整布局行为?
问题背景
项目中存在通用接口:
public interface ValueHoldingControl<T> { T getValue(); ObjectProperty<T> valueProperty(); void setValue(T value); }
需要实现一个泛型为LocalDateTime的该接口实现类,但原DateTimePicker的相关方法泛型为LocalDate,无法通过继承实现,因此采用组合方式编写了ComposedDateTimePicker:
public class ComposedDateTimePicker extends Control implements ValueHoldingControl<LocalDateTime> { private static class ComposedDateTimePickerSkin extends SkinBase<ComposedDateTimePicker> { private ComposedDateTimePickerSkin(ComposedDateTimePicker control) { super(control); getChildren().add(control.dateTimePicker); } } private final DateTimePicker dateTimePicker = new DateTimePicker(); @Override protected Skin<?> createDefaultSkin() { return new ComposedDateTimePickerSkin(this); } @Override public LocalDateTime getValue() { return dateTimePicker.getDateTimeValue(); } @Override public void setValue(LocalDateTime dateTimeValue) { dateTimePicker.setDateTimeValue(dateTimeValue); } @Override public ObjectProperty<LocalDateTime> valueProperty() { return dateTimePicker.dateTimeValueProperty(); } }
问题
对ComposedDateTimePicker设置布局属性(如composedDateTimePicker.setMaxWidth(Double.POSITIVE_INFINITY))时,内部的DateTimePicker无响应。虽可通过重写Skin的layoutChildren手动设置宽高,但这并非通用方案。需要实现:无论对ComposedDateTimePicker施加何种布局设置,其行为均与直接继承DateTimePicker一致。
可行方案
要让组合控件完全复刻原DateTimePicker的布局行为,核心要做三件事:布局属性双向绑定、复用原控件皮肤、委托通用交互方法。
1. 双向绑定所有布局相关属性
JavaFX的Control类包含大量布局属性,把外层ComposedDateTimePicker的这些属性和内部dateTimePicker做双向绑定,就能让外层的设置直接同步到内部控件,包括代码设置和CSS样式。
在ComposedDateTimePicker的构造方法中添加绑定逻辑:
public ComposedDateTimePicker() { // 绑定宽高相关属性 minWidthProperty().bindBidirectional(dateTimePicker.minWidthProperty()); maxWidthProperty().bindBidirectional(dateTimePicker.maxWidthProperty()); prefWidthProperty().bindBidirectional(dateTimePicker.prefWidthProperty()); minHeightProperty().bindBidirectional(dateTimePicker.minHeightProperty()); maxHeightProperty().bindBidirectional(dateTimePicker.maxHeightProperty()); prefHeightProperty().bindBidirectional(dateTimePicker.prefHeightProperty()); // 绑定其他布局细节属性 alignmentProperty().bindBidirectional(dateTimePicker.alignmentProperty()); paddingProperty().bindBidirectional(dateTimePicker.paddingProperty()); insetsProperty().bindBidirectional(dateTimePicker.insetsProperty()); // 同步样式类,确保CSS样式生效 getStyleClass().addAll(dateTimePicker.getStyleClass()); }
2. 复用原DateTimePicker的皮肤逻辑
原自定义皮肤只是简单添加内部控件,没有复用原皮肤的布局逻辑。我们可以通过委托皮肤的方式,直接复用原DateTimePicker的皮肤,保证布局行为完全一致。
修改createDefaultSkin方法并添加委托皮肤类:
@Override protected Skin<?> createDefaultSkin() { // 确保内部控件已创建皮肤 if (dateTimePicker.getSkin() == null) { dateTimePicker.applyCss(); } return new DelegatedSkin(this, dateTimePicker, dateTimePicker.getSkin()); } // 自定义委托皮肤,转发布局逻辑 private static class DelegatedSkin extends SkinBase<ComposedDateTimePicker> { private final DateTimePicker innerPicker; private final Skin<?> innerSkin; public DelegatedSkin(ComposedDateTimePicker control, DateTimePicker innerPicker, Skin<?> innerSkin) { super(control); this.innerPicker = innerPicker; this.innerSkin = innerSkin; // 把原皮肤的子节点加入当前皮肤 getChildren().addAll(innerSkin.getChildren()); // 同步布局边界变化 innerSkin.getNode().layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> { control.setLayoutBounds(newBounds); }); } @Override protected void layoutChildren(double x, double y, double w, double h) { // 直接调用原皮肤的布局方法 if (innerSkin instanceof SkinBase) { ((SkinBase<?>) innerSkin).layoutChildren(x, y, w, h); } } @Override public void dispose() { // 清理原皮肤资源 innerSkin.dispose(); super.dispose(); } }
3. 委托通用交互方法
把Control类的通用交互方法(如焦点请求、样式应用)委托给内部dateTimePicker,保证控件行为和原控件一致:
@Override public void requestFocus() { dateTimePicker.requestFocus(); } @Override public void applyCss() { dateTimePicker.applyCss(); super.applyCss(); } @Override public void layout() { dateTimePicker.layout(); super.layout(); } // 其他如requestLayout()等方法按需委托
方案优势
- 通用性:不需要针对每个布局属性单独处理,所有布局相关的设置都会自动同步。
- 行为一致:完全复用原
DateTimePicker的皮肤逻辑,布局表现和直接继承的控件毫无差异。 - 可维护性:后续原
DateTimePicker的布局逻辑更新,组合控件会自动同步,无需额外修改。
内容的提问来源于stack exchange,提问作者maddingl

