How to put constrains on maximum selectable CheckBoxes via RadioButtons in JavaFX?

扶醉桌前 提交于 2019-12-24 04:17:28

问题


Please have a look at the picture below, you will understand the layout of my application.

I would like to have the possibility to dynamically choose how many CheckBox (which enable the drop down menus) are selectable (a fixed number). And I want to achieve this with those 3 RadioButton.

In the vertical Mode all 4 CheckBox must be selected (not less). In the Hybrid mode only 2 CheckBox must be available (not more and not less). In the Horizontal mode only 1 CheckBox must be selected (not more). It is important that the user has the capability to choose a specific combination ComboBoxes (eg: we are in the hybrid mode, choosing 1 and 2 is different than choosing 1 and 3).

Solution

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);
        }

    });

}

Tab of FXML File I would like to implement fabian solution in this tab, however It is not needed to use an fxml as I did.

<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>

I have only managed to set manually which number is the maximum (but not the minimum) allowed using private int maxNumSelected = 2;. However I would like to manipulate them via RadioButton.


回答1:


I do not recommend hardcoding the CheckBoxes/ComboBoxes and counts for the cases in different places. Use the same value for both creating the CheckBoxes/ComboBoxes and modifying the allowed number of selected CheckBoxes. However this pervents you from creating the look in fxml alone.

Unless you want some pretty confusing behavior for the user, you need to allow the user to select less than the required number of CheckBoxes though, since you cannot really tell which CheckBox to select when unselecting one. You could disable/enable a button for submitting the form or some similar control though...

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;
    }

}



回答2:


You could set a listener on all of your RadioButton and when one is selected, disable or enable the containers for each of your ComboBox/CheckBox nodes.

Here is a sample application that demonstrates this. I built the UI with pure Java (no FXML) just to keep everything in one posting. The important part is the the three listeners added to the 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();
    }
}

Just a couple of things to note. You will need named containers to enable/disable the appropriate selection areas. Here they are called hbSelection#, which is where we add the checkboxes and comboboxes.

In the listeners, you will see I am basically just setting each HBox's disabled property based on newValue which is true if the RadioButton is selected and false if it is not.

There may be a more efficient way of handling this, but this is definitely one method.



来源:https://stackoverflow.com/questions/50970771/how-to-put-constrains-on-maximum-selectable-checkboxes-via-radiobuttons-in-javaf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!