diff --git a/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroup.java b/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroup.java new file mode 100644 index 0000000..7e9a0fa --- /dev/null +++ b/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroup.java @@ -0,0 +1,441 @@ +package com.imyeyu.fx.ui.components; + +import com.imyeyu.fx.TimiFX; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.input.MouseEvent; + +/** + * 单选按钮组组件 + * + * @author 夜雨 + * @since 2026-06-26 + */ +public class RadioButtonGroup extends Control { + + /** + * 按钮组模式 + * + * @author 夜雨 + * @since 2026-06-26 + */ + public enum Mode { + + /** 按钮模式 */ + BUTTON, + + /** 滑块模式 */ + SLIDER + } + + /** 根样式 */ + public static final String STYLE_CLASS = "radio-button-group"; + + /** 按钮模式样式 */ + public static final String STYLE_CLASS_BUTTON_MODE = "button-mode"; + + /** 滑块模式样式 */ + public static final String STYLE_CLASS_SLIDER_MODE = "slider-mode"; + + /** 容器样式 */ + public static final String STYLE_CLASS_BOX = "radio-button-group-box"; + + /** 滑块样式 */ + public static final String STYLE_CLASS_SLIDER = "radio-button-group-slider"; + + /** 按钮样式 */ + public static final String STYLE_CLASS_BUTTON = "radio-button-group-button"; + + /** 首按钮样式 */ + public static final String STYLE_CLASS_FIRST = "first"; + + /** 中间按钮样式 */ + public static final String STYLE_CLASS_MIDDLE = "middle"; + + /** 末按钮样式 */ + public static final String STYLE_CLASS_LAST = "last"; + + /** 单按钮样式 */ + public static final String STYLE_CLASS_SINGLE = "single"; + + /** 按钮列表 */ + private final ObservableList buttons; + + /** 按钮组 */ + private final ToggleGroup toggleGroup; + + /** 当前选中按钮 */ + private final ReadOnlyObjectWrapper selectedButton; + + /** 当前选中下标 */ + private final IntegerProperty selectedIndex; + + /** 按钮间距 */ + private final DoubleProperty spacing; + + /** 布局对齐方式 */ + private final ObjectProperty alignment; + + /** 当前模式 */ + private final ObjectProperty mode; + + /** true 为允许清空选中 */ + private final BooleanProperty allowClearSelection; + + /** 禁止重复点击清空 */ + private final javafx.event.EventHandler keepSelectionEvent = event -> { + if (!isAllowClearSelection() && event.getSource() instanceof ToggleButton button && button.isSelected()) { + event.consume(); + } + }; + + /** + * 默认构造器 + */ + public RadioButtonGroup() { + buttons = FXCollections.observableArrayList(); + toggleGroup = new ToggleGroup(); + selectedButton = new ReadOnlyObjectWrapper<>(); + selectedIndex = new SimpleIntegerProperty(-1); + spacing = new SimpleDoubleProperty(); + alignment = new SimpleObjectProperty<>(Pos.CENTER_LEFT); + mode = new SimpleObjectProperty<>(Mode.BUTTON); + allowClearSelection = new SimpleBooleanProperty(); + + getStyleClass().addAll(STYLE_CLASS, STYLE_CLASS_BUTTON_MODE); + setFocusTraversable(false); + + initSelectionSync(); + initButtonsListener(); + initModeStyleClass(); + } + + /** + * 初始化选中同步 + */ + private void initSelectionSync() { + toggleGroup.selectedToggleProperty().addListener((obs, o, toggle) -> { + if (toggle instanceof ToggleButton button) { + selectedButton.set(button); + selectedIndex.set(buttons.indexOf(button)); + return; + } + selectedButton.set(null); + selectedIndex.set(-1); + }); + selectedIndex.addListener((obs, o, newIndex) -> { + int index = newIndex == null ? -1 : newIndex.intValue(); + if (index < 0) { + if (isAllowClearSelection()) { + toggleGroup.selectToggle(null); + } + return; + } + if (index < buttons.size()) { + toggleGroup.selectToggle(buttons.get(index)); + } + }); + } + + /** + * 初始化按钮监听 + */ + private void initButtonsListener() { + buttons.addListener((ListChangeListener) change -> { + while (change.next()) { + if (change.wasRemoved()) { + for (ToggleButton button : change.getRemoved()) { + detachButton(button); + } + } + if (change.wasAdded()) { + for (ToggleButton button : change.getAddedSubList()) { + attachButton(button); + } + } + } + syncSelectionState(); + }); + } + + /** + * 初始化模式样式 + */ + private void initModeStyleClass() { + TimiFX.toggleStyleClass4Binding(this, mode.isEqualTo(Mode.SLIDER), STYLE_CLASS_SLIDER_MODE, STYLE_CLASS_BUTTON_MODE); + } + + /** + * 挂载按钮到当前组件 + * + * @param button 按钮 + */ + private void attachButton(ToggleButton button) { + if (button == null) { + throw new IllegalArgumentException("button must not be null"); + } + button.setToggleGroup(toggleGroup); + button.addEventFilter(MouseEvent.MOUSE_PRESSED, keepSelectionEvent); + } + + /** + * 卸载按钮 + * + * @param button 按钮 + */ + private void detachButton(ToggleButton button) { + button.removeEventFilter(MouseEvent.MOUSE_PRESSED, keepSelectionEvent); + if (button.getToggleGroup() == toggleGroup) { + button.setToggleGroup(null); + } + } + + /** + * 同步当前选中状态 + */ + private void syncSelectionState() { + ToggleButton currentSelectedButton = getSelectedButton(); + if (currentSelectedButton != null && !buttons.contains(currentSelectedButton)) { + if (isAllowClearSelection()) { + toggleGroup.selectToggle(null); + } + else if (!buttons.isEmpty()) { + toggleGroup.selectToggle(buttons.getFirst()); + } + return; + } + int index = selectedIndex.get(); + if (index < 0 || buttons.size() <= index) { + if (!buttons.isEmpty() && !isAllowClearSelection() && toggleGroup.getSelectedToggle() == null) { + toggleGroup.selectToggle(buttons.getFirst()); + } + return; + } + toggleGroup.selectToggle(buttons.get(index)); + } + + @Override + protected Skin createDefaultSkin() { + return new RadioButtonGroupSkin(this); + } + + /** + * 获取按钮列表 + * + * @return 按钮列表 + */ + public ObservableList getButtons() { + return buttons; + } + + /** + * 添加按钮 + * + * @param buttons 按钮列表 + */ + public void addButton(ToggleButton... buttons) { + getButtons().addAll(buttons); + } + + /** + * 添加文本按钮 + * + * @param text 按钮文本 + * @return 新增按钮 + */ + public ToggleButton addButton(String text) { + ToggleButton button = new ToggleButton(text); + getButtons().add(button); + return button; + } + + /** + * 设置当前选中按钮 + * + * @param button 当前选中按钮 + */ + public void setSelectedButton(ToggleButton button) { + if (button == null) { + if (isAllowClearSelection()) { + toggleGroup.selectToggle(null); + } + return; + } + if (!buttons.contains(button)) { + throw new IllegalArgumentException("button must belong to current group"); + } + toggleGroup.selectToggle(button); + } + + /** + * 获取当前选中按钮 + * + * @return 当前选中按钮 + */ + public ToggleButton getSelectedButton() { + return selectedButton.get(); + } + + /** + * 获取当前选中按钮属性 + * + * @return 当前选中按钮属性 + */ + public ReadOnlyObjectProperty selectedButtonProperty() { + return selectedButton.getReadOnlyProperty(); + } + + /** + * 设置当前选中下标 + * + * @param selectedIndex 当前选中下标 + */ + public void setSelectedIndex(int selectedIndex) { + this.selectedIndex.set(selectedIndex); + } + + /** + * 获取当前选中下标 + * + * @return 当前选中下标 + */ + public int getSelectedIndex() { + return selectedIndex.get(); + } + + /** + * 获取当前选中下标属性 + * + * @return 当前选中下标属性 + */ + public IntegerProperty selectedIndexProperty() { + return selectedIndex; + } + + /** + * 设置按钮间距 + * + * @param spacing 按钮间距 + */ + public void setSpacing(double spacing) { + this.spacing.set(Math.max(0, spacing)); + } + + /** + * 获取按钮间距 + * + * @return 按钮间距 + */ + public double getSpacing() { + return spacing.get(); + } + + /** + * 获取按钮间距属性 + * + * @return 按钮间距属性 + */ + public DoubleProperty spacingProperty() { + return spacing; + } + + /** + * 设置布局对齐方式 + * + * @param alignment 布局对齐方式 + */ + public void setAlignment(Pos alignment) { + this.alignment.set(alignment); + } + + /** + * 获取布局对齐方式 + * + * @return 布局对齐方式 + */ + public Pos getAlignment() { + return alignment.get(); + } + + /** + * 获取布局对齐方式属性 + * + * @return 布局对齐方式属性 + */ + public ObjectProperty alignmentProperty() { + return alignment; + } + + /** + * 设置当前模式 + * + * @param mode 当前模式 + */ + public void setMode(Mode mode) { + this.mode.set(mode == null ? Mode.BUTTON : mode); + } + + /** + * 获取当前模式 + * + * @return 当前模式 + */ + public Mode getMode() { + return mode.get(); + } + + /** + * 获取当前模式属性 + * + * @return 当前模式属性 + */ + public ObjectProperty modeProperty() { + return mode; + } + + /** + * 设置是否允许清空选中 + * + * @param allowClearSelection true 为允许清空选中 + */ + public void setAllowClearSelection(boolean allowClearSelection) { + this.allowClearSelection.set(allowClearSelection); + if (!allowClearSelection && toggleGroup.getSelectedToggle() == null && !buttons.isEmpty()) { + toggleGroup.selectToggle(buttons.getFirst()); + } + } + + /** + * 获取是否允许清空选中 + * + * @return true 为允许清空选中 + */ + public boolean isAllowClearSelection() { + return allowClearSelection.get(); + } + + /** + * 获取是否允许清空选中属性 + * + * @return 是否允许清空选中属性 + */ + public BooleanProperty allowClearSelectionProperty() { + return allowClearSelection; + } +} diff --git a/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroupSkin.java b/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroupSkin.java new file mode 100644 index 0000000..69d460b --- /dev/null +++ b/src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroupSkin.java @@ -0,0 +1,246 @@ +package com.imyeyu.fx.ui.components; + +import com.imyeyu.fx.bean.Interpolates; + +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.collections.ListChangeListener; +import javafx.geometry.Bounds; +import javafx.scene.control.SkinBase; +import javafx.scene.control.ToggleButton; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; + +/** + * 单选按钮组皮肤 + * + * @author 夜雨 + * @since 2026-06-26 + */ +public class RadioButtonGroupSkin extends SkinBase { + + /** 高度补偿 */ + private static final double HEIGHT_COMPENSATE = 2; + + /** 动画时长 */ + private static final Duration SLIDER_DURATION = Duration.millis(180); + + /** 按钮容器 */ + private final HBox box; + + /** 滑块 */ + private final Region slider; + + /** 滑块 x */ + private final DoubleProperty sliderX; + + /** 滑块 y */ + private final DoubleProperty sliderY; + + /** 滑块宽度 */ + private final DoubleProperty sliderWidth; + + /** 滑块高度 */ + private final DoubleProperty sliderHeight; + + /** 滑块动画 */ + private Timeline sliderTimeline; + + /** + * 构造按钮组皮肤 + * + * @param control 按钮组控件 + */ + public RadioButtonGroupSkin(RadioButtonGroup control) { + super(control); + + sliderX = new SimpleDoubleProperty(); + sliderY = new SimpleDoubleProperty(); + sliderWidth = new SimpleDoubleProperty(); + sliderHeight = new SimpleDoubleProperty(); + sliderTimeline = new Timeline(); + + box = new HBox(); + box.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_BOX); + + slider = new Region(); + slider.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_SLIDER); + slider.setManaged(false); + slider.setMouseTransparent(true); + + sliderX.addListener((obs, o, n) -> applySliderBounds()); + sliderY.addListener((obs, o, n) -> applySliderBounds()); + sliderWidth.addListener((obs, o, n) -> applySliderBounds()); + sliderHeight.addListener((obs, o, n) -> applySliderBounds()); + + box.alignmentProperty().bind(control.alignmentProperty()); + box.spacingProperty().bind(Bindings.createDoubleBinding( + () -> control.getMode() == RadioButtonGroup.Mode.BUTTON && control.getSpacing() < 1 ? -1 : control.getSpacing(), + control.modeProperty(), + control.spacingProperty() + )); + getChildren().add(new StackPane() {{ + getStyleClass().add(RadioButtonGroup.STYLE_CLASS_BOX); + getChildren().addAll(slider, box); + }}); + + rebuildButtons(); + initListeners(control); + updateMode(); + } + + /** + * 初始化监听 + * + * @param control 控件 + */ + private void initListeners(RadioButtonGroup control) { + control.getButtons().addListener((ListChangeListener) change -> rebuildButtons()); + control.selectedButtonProperty().addListener((obs, o, n) -> syncSlider()); + control.modeProperty().addListener((obs, o, n) -> { + updateMode(); + syncSlider(); + }); + box.layoutBoundsProperty().addListener((obs, o, n) -> { + syncSlider(); + }); + } + + /** + * 重建按钮结构 + */ + private void rebuildButtons() { + box.getChildren().setAll(getSkinnable().getButtons()); + syncButtonStyleClass(); + syncSlider(); + } + + /** + * 同步按钮样式 + */ + private void syncButtonStyleClass() { + int size = getSkinnable().getButtons().size(); + for (int i = 0; i < size; i++) { + ToggleButton button = getSkinnable().getButtons().get(i); + button.getStyleClass().removeAll( + RadioButtonGroup.STYLE_CLASS_BUTTON, + RadioButtonGroup.STYLE_CLASS_FIRST, + RadioButtonGroup.STYLE_CLASS_MIDDLE, + RadioButtonGroup.STYLE_CLASS_LAST, + RadioButtonGroup.STYLE_CLASS_SINGLE + ); + button.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_BUTTON); + button.selectedProperty().addListener((obs, o, n) -> button.setViewOrder(n ? -1 : 0)); + button.setViewOrder(button.isSelected() ? -1 : 0); + button.layoutBoundsProperty().addListener((obs, o, n) -> syncSlider()); + button.boundsInParentProperty().addListener((obs, o, n) -> syncSlider()); + + if (size == 1) { + button.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_SINGLE); + continue; + } + if (i == 0) { + button.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_FIRST); + continue; + } + if (i == size - 1) { + button.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_LAST); + continue; + } + button.getStyleClass().add(RadioButtonGroup.STYLE_CLASS_MIDDLE); + } + } + + /** + * 刷新按钮尺寸策略 + */ + private void refreshButtonSizing() { + boolean sliderMode = getSkinnable().getMode() == RadioButtonGroup.Mode.SLIDER; + for (ToggleButton button : getSkinnable().getButtons()) { + button.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + button.setPrefHeight(Region.USE_COMPUTED_SIZE); + if (sliderMode) { + HBox.setHgrow(button, Priority.NEVER); + button.setMaxWidth(Region.USE_PREF_SIZE); + } + else { + HBox.setHgrow(button, Priority.ALWAYS); + button.setMaxWidth(Double.MAX_VALUE); + } + } + } + + /** + * 应用滑块位置和尺寸 + */ + private void applySliderBounds() { + slider.resizeRelocate(sliderX.get(), sliderY.get(), sliderWidth.get(), sliderHeight.get()); + } + + /** + * 更新模式 + */ + private void updateMode() { + boolean sliderMode = getSkinnable().getMode() == RadioButtonGroup.Mode.SLIDER; + slider.setVisible(sliderMode); + } + + /** + * 同步滑块位置 + */ + private void syncSlider() { + if (getSkinnable().getMode() != RadioButtonGroup.Mode.SLIDER) { + sliderTimeline.stop(); + sliderX.set(0); + sliderY.set(0); + sliderWidth.set(0); + sliderHeight.set(0); + return; + } + ToggleButton button = getSkinnable().getSelectedButton(); + if (button == null || !box.getChildren().contains(button)) { + sliderTimeline.stop(); + sliderX.set(0); + sliderY.set(0); + sliderWidth.set(0); + sliderHeight.set(0); + return; + } + Bounds bounds = button.getBoundsInParent(); + double targetX = bounds.getMinX() + 2; + double targetWidth = Math.max(0, bounds.getWidth() - 4); + double targetHeight = Math.max(0, box.getHeight() - 4); + double targetY = 2; + + if (sliderWidth.get() <= 0 || sliderHeight.get() <= 0) { + sliderTimeline.stop(); + sliderX.set(targetX); + sliderY.set(targetY); + sliderWidth.set(targetWidth); + sliderHeight.set(targetHeight); + return; + } + + sliderTimeline.stop(); + sliderTimeline = new Timeline( + new KeyFrame( + SLIDER_DURATION, + new KeyValue(sliderX, targetX, Interpolates.EASE_OUT_EXPO.getValue()), + new KeyValue(sliderY, targetY, Interpolates.EASE_OUT_EXPO.getValue()), + new KeyValue(sliderWidth, targetWidth, Interpolates.EASE_OUT_EXPO.getValue()), + new KeyValue(sliderHeight, targetHeight, Interpolates.EASE_OUT_EXPO.getValue()) + ) + ); + sliderTimeline.play(); + } +} + + diff --git a/src/main/resources/timifx/style/components/radio-button-group.css b/src/main/resources/timifx/style/components/radio-button-group.css new file mode 100644 index 0000000..b030b37 --- /dev/null +++ b/src/main/resources/timifx/style/components/radio-button-group.css @@ -0,0 +1,49 @@ +.radio-button-group .radio-button-group-box { + -fx-spacing: 0; + -fx-alignment: center-left; +} +.radio-button-group .radio-button-group-button { + -fx-padding: .25em .833333em; + -fx-background-insets: 0; + -fx-border-insets: 0; + -fx-background-radius: 0; + -fx-border-radius: 0; + -fx-border-style: solid; + -fx-border-width: 1; +} +.radio-button-group.button-mode .radio-button-group-button { + -fx-background-color: -timi-fx-white-color; + -fx-border-color: -timi-fx-border-color; + -fx-text-fill: -timi-fx-dark-gray-color; +} +.radio-button-group.button-mode .radio-button-group-button:hover { + -fx-background-color: -timi-fx-gray-white-color; + -fx-border-color: -timi-fx-border-color; + -fx-text-fill: -timi-fx-dark-gray-color; +} +.radio-button-group.button-mode .radio-button-group-button:selected, +.radio-button-group.button-mode .radio-button-group-button:selected:hover { + -fx-background-color: -timi-fx-color; + -fx-border-color: -timi-fx-color; + -fx-text-fill: -timi-fx-white-color; +} +.radio-button-group.slider-mode { + -fx-background-color: -timi-fx-light-gray-color; + -fx-background-radius: 0; +} +.radio-button-group.slider-mode .radio-button-group-button, +.radio-button-group.slider-mode .radio-button-group-button:hover, +.radio-button-group.slider-mode .radio-button-group-button:selected, +.radio-button-group.slider-mode .radio-button-group-button:selected:hover { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-style: solid; + -fx-border-width: 1; + -fx-text-fill: -timi-fx-dark-gray-color; +} +.radio-button-group .radio-button-group-slider { + -fx-background-color: -timi-fx-white-color; + -fx-background-radius: 0; + -fx-border-radius: 0; + -fx-effect: null; +} diff --git a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ButtonDemo.java b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ButtonDemo.java index b34434a..cb95fc4 100644 --- a/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ButtonDemo.java +++ b/src/test/java/com/imyeyu/fx/ui/examples/view/pages/ButtonDemo.java @@ -1,7 +1,7 @@ package com.imyeyu.fx.ui.examples.view.pages; import com.imyeyu.fx.ui.TimiFXUI; -import com.imyeyu.fx.ui.components.SegmentedButtonGroup; +import com.imyeyu.fx.ui.components.RadioButtonGroup; import com.imyeyu.fx.ui.examples.component.AbstractDemoPane; import com.imyeyu.fx.ui.examples.component.sidebar.SidebarItem; import com.imyeyu.inject.annotation.Component; @@ -10,6 +10,7 @@ import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ToggleButton; import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; /** @@ -25,24 +26,25 @@ public class ButtonDemo extends AbstractDemoPane { title.setText(SidebarItem.BUTTON.getText()); document.setText("暂无"); document.setUrl(""); - source.setText("src/main/java/com/imyeyu/fx/ui/components/SegmentedButtonGroup.java"); + source.setText("src/main/java/com/imyeyu/fx/ui/components/RadioButtonGroup.java"); source.setUrl(""); - tips.setText("演示 SegmentedButtonGroup 的标准控件写法,按钮列表、选中状态和允许取消选择都是组件对外暴露的标准属性"); + tips.setText("演示 RadioButtonGroup 的按钮模式和滑块模式,按钮列表、选中状态和允许取消选择都是组件对外暴露的标准属性"); - SegmentedButtonGroup defaultGroup = new SegmentedButtonGroup(); - defaultGroup.addButton("全部"); - defaultGroup.addButton("启用"); - defaultGroup.addButton("停用"); - defaultGroup.setSelectedIndex(0); + RadioButtonGroup buttonModeGroup = new RadioButtonGroup(); + buttonModeGroup.addButton("全部"); + buttonModeGroup.addButton("启用"); + buttonModeGroup.addButton("停用"); + buttonModeGroup.setSelectedIndex(0); - SegmentedButtonGroup spacingGroup = new SegmentedButtonGroup(); - spacingGroup.setSpacing(6); - spacingGroup.addButton("左对齐"); - spacingGroup.addButton("居中"); - spacingGroup.addButton("右对齐"); - spacingGroup.setSelectedIndex(1); + RadioButtonGroup sliderModeGroup = new RadioButtonGroup(); + sliderModeGroup.setMode(RadioButtonGroup.Mode.SLIDER); + sliderModeGroup.addButton("左对齐"); + sliderModeGroup.addButton("居中"); + sliderModeGroup.addButton("右对齐"); + sliderModeGroup.setSelectedIndex(1); - SegmentedButtonGroup clearableGroup = new SegmentedButtonGroup(); + RadioButtonGroup clearableGroup = new RadioButtonGroup(); + clearableGroup.setMode(RadioButtonGroup.Mode.SLIDER); clearableGroup.setAllowClearSelection(true); clearableGroup.addButton("日"); clearableGroup.addButton("周"); @@ -63,7 +65,7 @@ public class ButtonDemo extends AbstractDemoPane { }); clearableGroup.selectedIndexProperty().addListener((obs, o, index) -> selectedIndexText.setText("当前下标: " + index.intValue())); - SegmentedButtonGroup customButtonGroup = new SegmentedButtonGroup(); + RadioButtonGroup customButtonGroup = new RadioButtonGroup(); ToggleButton primary = new ToggleButton("概览"); ToggleButton analysis = new ToggleButton("分析"); ToggleButton history = new ToggleButton("历史"); @@ -80,9 +82,9 @@ public class ButtonDemo extends AbstractDemoPane { setVgap(10); int row = 0; - addRow(row++, TimiFXUI.label("默认单选"), defaultGroup); - addRow(row++, TimiFXUI.label("带间距"), spacingGroup); - addRow(row++, TimiFXUI.label("允许取消"), clearableGroup); + addRow(row++, TimiFXUI.label("按钮模式"), buttonModeGroup); + addRow(row++, TimiFXUI.label("滑块模式"), sliderModeGroup); + addRow(row++, TimiFXUI.label("允许取消"), new HBox(clearableGroup)); addRow(row, TimiFXUI.label("外部按钮"), customButtonGroup); }}, selectedButtonText,