如何通过JavaFX中的RadioButtons限制最大可选择的复选框?

时间:2018-06-21 14:15:52

标签: java javafx radio-group

请看下面的图片,您将了解我的应用程序的布局。

enter image description here

我希望可以动态选择CheckBox(启用下拉菜单)的数量(固定数量)。我想用那三个RadioButton来实现。

在垂直模式下,必须全部选择4 CheckBox(不少于)。在混合模式下,仅2 CheckBox必须可用(不多也不少)。在水平模式下,仅必须选择1 CheckBox(不能更多)。用户必须具有选择特定组合ComboBox的能力,这一点很重要(例如:我们处于混合模式,选择1和2不同于选择1和3)。

解决方案

public class ConfigurationEditDialogController {

// Data Acquisition Tab

private ObservableList<String> options =
        FXCollections.observableArrayList(
                "ciao",
                "hello",
                "halo"
        );

@FXML
private PrefixSelectionComboBox<String> testBus1ComboBox = new PrefixSelectionComboBox<>();
@FXML
private PrefixSelectionComboBox<String> testBus2ComboBox = new PrefixSelectionComboBox<>();
@FXML
private PrefixSelectionComboBox<String> testBus3ComboBox = new PrefixSelectionComboBox<>();
@FXML
private PrefixSelectionComboBox<String> testBus4ComboBox = new PrefixSelectionComboBox<>();
@FXML
private CheckBox checkbox1;
@FXML
private CheckBox checkbox2;
@FXML
private CheckBox checkbox3;
@FXML
private CheckBox checkbox4;

private ObservableSet<CheckBox> selectedCheckBoxes = FXCollections.observableSet();
private ObservableSet<CheckBox> unselectedCheckBoxes = FXCollections.observableSet();

private IntegerBinding numCheckBoxesSelected = Bindings.size(selectedCheckBoxes);
private int maxNumSelected =  2;

@FXML
private RadioButton verticalMode;
@FXML
private RadioButton horizontalMode;
@FXML
private RadioButton hybridMode;


private Stage dialogStage;
private Configuration configuration;
private boolean okClicked = false;

/**
 * Initializes the controller class. This method is automatically called
 * after the fxml file has been loaded.
 */
@FXML
private void initialize() {
    testBus1ComboBox.setItems(options);
    testBus2ComboBox.setItems(options);
    testBus3ComboBox.setItems(options);
    testBus4ComboBox.setItems(options);
    configureCheckBox(checkbox1);
    configureCheckBox(checkbox2);
    configureCheckBox(checkbox3);
    configureCheckBox(checkbox4);

    numCheckBoxesSelected.addListener((obs, oldSelectedCount, newSelectedCount) -> {
        if (newSelectedCount.intValue() >= maxNumSelected) {
            unselectedCheckBoxes.forEach(cb -> cb.setDisable(true));
        } else {
            unselectedCheckBoxes.forEach(cb -> cb.setDisable(false));
        }
    });


    testBus1ComboBox.disableProperty().bind(checkbox1.selectedProperty().not());
    testBus2ComboBox.disableProperty().bind(checkbox2.selectedProperty().not());
    testBus3ComboBox.disableProperty().bind(checkbox3.selectedProperty().not());
    testBus4ComboBox.disableProperty().bind(checkbox4.selectedProperty().not());

}


private void configureCheckBox(CheckBox checkBox) {

    if (checkBox.isSelected()) {
        selectedCheckBoxes.add(checkBox);
    } else {
        unselectedCheckBoxes.add(checkBox);
    }

    checkBox.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
        if (isNowSelected) {
            unselectedCheckBoxes.remove(checkBox);
            selectedCheckBoxes.add(checkBox);
        } else {
            selectedCheckBoxes.remove(checkBox);
            unselectedCheckBoxes.add(checkBox);
        }

    });

}

FXML个文件的标签 我想在此选项卡中实现fabian解决方案,但是不需要像以前那样使用fxml。

<Tab closable="false" text="Data Acquisition">
           <content>
                <GridPane prefHeight="254.0" prefWidth="404.0">
                    <columnConstraints>
                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="218.0" minWidth="10.0" prefWidth="111.0" />
                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="519.0" minWidth="10.0" prefWidth="490.0" />
                    <ColumnConstraints hgrow="SOMETIMES" maxWidth="316.0" minWidth="10.0" prefWidth="71.0" />
                    </columnConstraints>
                    <rowConstraints>
                    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    </rowConstraints>
                    <children>
                        <Label text="Test Bus 1" GridPane.rowIndex="2" />
                        <Label text="Test Bus 2" GridPane.rowIndex="3" />
                        <Label text="Test Bus 3" GridPane.rowIndex="4" />
                        <Label text="Test Bus 4" GridPane.rowIndex="5" />
                    <PrefixSelectionComboBox fx:id="testBus1ComboBox" prefHeight="31.0" prefWidth="300.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
                    <PrefixSelectionComboBox fx:id="testBus2ComboBox" prefWidth="300.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="3" />
                    <PrefixSelectionComboBox fx:id="testBus3ComboBox" prefWidth="300.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
                    <PrefixSelectionComboBox fx:id="testBus4ComboBox" prefWidth="300.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="5" />
                    <CheckBox fx:id="checkbox1" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="2" />
                    <CheckBox fx:id="checkbox2" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="3" />
                    <CheckBox fx:id="checkbox3" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="4" />
                    <CheckBox fx:id="checkbox4" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="5" />
                    <Label text="Sample Mode" GridPane.rowIndex="1" />
                    <RadioButton fx:id="verticalMode" mnemonicParsing="false" selected="true" text="Vertical " GridPane.columnIndex="1" GridPane.rowIndex="1">
                       <toggleGroup>
                          <ToggleGroup fx:id="SampleModeGroup" />
                       </toggleGroup>
                    </RadioButton>
                    <RadioButton fx:id="hybridMode" mnemonicParsing="false" text="Hybrid" toggleGroup="$SampleModeGroup" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
                    <RadioButton fx:id="horizontalMode" mnemonicParsing="false" text="Horizontal" toggleGroup="$SampleModeGroup" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="1" />
                    </children>
                </GridPane>
           </content>
        </Tab>

我只能通过private int maxNumSelected = 2;手动设置哪个数字是允许的最大数字(但不是最小数字)。但是我想通过RadioButton来操纵它们。

2 个答案:

答案 0 :(得分:2)

您可以在所有RadioButton上设置一个侦听器,并选择一个侦听器,为每个ComboBox / CheckBox节点禁用或启用容器。

这是一个演示此情况的示例应用程序。我使用纯Java(没有FXML)构建UI只是为了将所有内容都保留在一个发布中。重要的部分是添加到RadioButtons的三个侦听器。

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        // Root layout
        VBox root = new VBox(5);
        root.setPadding(new Insets(10));
        root.setAlignment(Pos.CENTER);

        // Radio buttons
        HBox hbRadios = new HBox(10);
        hbRadios.setAlignment(Pos.CENTER);
        ToggleGroup tglRadioSelections = new ToggleGroup();
        RadioButton rdoVertical = new RadioButton("Vertical");
        RadioButton rdoHybrid = new RadioButton("Hybrid");
        RadioButton rdoHorizontal = new RadioButton("Horizontal");
        tglRadioSelections.getToggles().addAll(rdoVertical, rdoHybrid, rdoHorizontal);
        hbRadios.getChildren().addAll(rdoVertical, rdoHybrid, rdoHorizontal);

        // ComboBoxes and CheckBoxes
        VBox vbSelections = new VBox(10);
        ComboBox cbo1 = new ComboBox();
        ComboBox cbo2 = new ComboBox();
        ComboBox cbo3 = new ComboBox();
        ComboBox cbo4 = new ComboBox();
        CheckBox chk1 = new CheckBox();
        CheckBox chk2 = new CheckBox();
        CheckBox chk3 = new CheckBox();
        CheckBox chk4 = new CheckBox();

        // Create the containers for each selection group
        HBox hbSelection1 = new HBox(10);
        hbSelection1.getChildren().addAll(cbo1, chk1);
        HBox hbSelection2 = new HBox(10);
        hbSelection2.getChildren().addAll(cbo2, chk2);
        HBox hbSelection3 = new HBox(10);
        hbSelection3.getChildren().addAll(cbo3, chk3);
        HBox hbSelection4 = new HBox(10);
        hbSelection4.getChildren().addAll(cbo4, chk4);

        // Add listeners for each radio button to enable appropriate selections
        rdoVertical.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
                hbSelection1.setDisable(!newValue);
                hbSelection2.setDisable(!newValue);
                hbSelection3.setDisable(!newValue);
                hbSelection4.setDisable(!newValue);
        });
        rdoHybrid.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
            hbSelection1.setDisable(!newValue);
            hbSelection2.setDisable(!newValue);
            hbSelection3.setDisable(!newValue);
            hbSelection4.setDisable(newValue);
        });
        rdoHorizontal.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
            hbSelection1.setDisable(!newValue);
            hbSelection2.setDisable(newValue);
            hbSelection3.setDisable(newValue);
            hbSelection4.setDisable(newValue);
        });

        // Build the scene
        vbSelections.getChildren().addAll(hbSelection1, hbSelection2, hbSelection3, hbSelection4);
        root.getChildren().addAll(hbRadios, vbSelections);
        stage.setScene(new Scene(root));

        stage.show();
    }
}

几件事要注意。您将需要命名容器来启用/禁用适当的选择区域。它们在这里称为hbSelection#,在这里我们添加了复选框和组合框。

在侦听器中,您将看到我基本上只是基于newValue设置每个HBox的禁用属性,如果选择了true,则为RadioButton;如果选择了false,则为不是。

也许有一种更有效的处理方法,但这绝对是一种方法。

答案 1 :(得分:1)

我不建议在不同位置对CheckBox es / ComboBox es和计数进行硬编码。创建CheckBox es / ComboBox es和修改允许的选定CheckBox es数量时,请使用相同的值。但是,这可以防止您仅在fxml中创建外观。

除非您希望用户采取一些令人困惑的行为,否则您需要允许用户选择的数量少于所需的CheckBox,因为您无法真正确定何时选择哪个CheckBox取消选择一个。您可以禁用/启用用于提交表单或类似控件的按钮...

private static HBox createModesRadios(IntegerProperty count, Mode... modes) {
    ToggleGroup group = new ToggleGroup();
    HBox result = new HBox(10);
    for (Mode mode : modes) {
        RadioButton radio = new RadioButton(mode.getText());
        radio.setToggleGroup(group);
        radio.setUserData(mode);
        result.getChildren().add(radio);
    }
    if (modes.length > 0) {
        group.selectToggle((Toggle) result.getChildren().get(0));
        count.bind(Bindings.createIntegerBinding(() -> ((Mode) group.getSelectedToggle().getUserData()).getCount(), group.selectedToggleProperty()));
    } else {
        count.set(0);
    }
    return result;
}

private static void updateCheckBoxes(CheckBox[] checkBoxes, int requiredCount, int unmodifiedIndex) {
    if (unmodifiedIndex >= 0 && checkBoxes[unmodifiedIndex].isSelected()) {
        requiredCount--;
    }
    int i;
    for (i = 0; i < checkBoxes.length && requiredCount > 0; i++) {
        if (i != unmodifiedIndex && checkBoxes[i].isSelected()) {
            requiredCount--;
        }
    }

    for (; i < checkBoxes.length; i++) {
        if (i != unmodifiedIndex) {
            checkBoxes[i].setSelected(false);
        }
    }
}

@Override
public void start(Stage primaryStage) {
    Mode[] modes = new Mode[]{
        new Mode("Vertical", 4),
        new Mode("Hybrid", 2),
        new Mode("Horizontal", 1)
    };

    ToggleGroup group = new ToggleGroup();
    IntegerProperty elementCount = new SimpleIntegerProperty();

    HBox radioBox = createModesRadios(elementCount, modes);
    GridPane grid = new GridPane();
    VBox root = new VBox(10, radioBox);

    int count = Stream.of(modes).mapToInt(Mode::getCount).max().orElse(0);
    ObservableMap<Integer, String> elements = FXCollections.observableHashMap();

    ObservableList<String> options = FXCollections.observableArrayList(
            "ciao",
            "hello",
            "halo");

    CheckBox[] checkBoxes = new CheckBox[count];

    elementCount.addListener((o, oldValue, newValue) -> {
        // uncheck checkboxes, if too many are checked
        updateCheckBoxes(checkBoxes, newValue.intValue(), -1);
    });

    for (int i = 0; i < count; i++) {
        final Integer index = i;
        CheckBox checkBox = new CheckBox();
        checkBoxes[i] = checkBox;

        ComboBox<String> comboBox = new ComboBox<>(options);
        comboBox.valueProperty().addListener((o, oldValue, newValue) -> {
            // modify value in map on value change
            elements.put(index, newValue);
        });
        comboBox.setDisable(true);

        checkBox.selectedProperty().addListener((o, oldValue, newValue) -> {
            comboBox.setDisable(!newValue);
            if (newValue) {
                // put the current element in the map
                elements.put(index, comboBox.getValue());

                // uncheck checkboxes that exceede the required count keeping the current one unmodified
                updateCheckBoxes(checkBoxes, elementCount.get(), index);
            } else {
                elements.remove(index);
            }

        });
        grid.addRow(i, comboBox, checkBox);
    }

    Button submit = new Button("submit");
    submit.setOnAction(evt -> System.out.println(elements));

    // enable submit button iff the number of elements is correct
    submit.disableProperty().bind(elementCount.isNotEqualTo(Bindings.size(elements)));

    root.getChildren().addAll(grid, submit);

    final Scene scene = new Scene(root, 400, 400);

    primaryStage.setScene(scene);
    primaryStage.show();
}
public class Mode {

    private final String text;
    private final int count;

    public Mode(String text, int count) {
        this.text = text;
        this.count = count;
    }

    public String getText() {
        return text;
    }

    public int getCount() {
        return count;
    }

}