问题
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 ComboBox
es (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 CheckBox
es/ComboBox
es and counts for the cases in different places. Use the same value for both creating the CheckBox
es/ComboBox
es and modifying the allowed number of selected CheckBox
es. 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 CheckBox
es 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