Restrict checkboxes checked. Javafx - javafx

How to restrict the number of checkboxes that a user can select? I want to make it so the user can only select 3. I think I should use isSelected, but I don't know how to link all the checkboxes.
CheckBox cb1 = new CheckBox("Pepperoni");
CheckBox cb2 = new CheckBox("Cheese");
CheckBox cb3 = new CheckBox("Tomato");
CheckBox cb4 = new CheckBox("Olives");
CheckBox cb5 = new CheckBox("Chicken");
//if 3 are already selected, it should not be possible for the user to select more

Simply keep track of the selected CheckBoxes using a suitable data structure (LinkedHashSet would allow you to use Set functionality and allow you to determine the first one inserted).
Updating the data structure can be done from a listener to the selected properties of the CheckBoxes.
#Override
public void start(Stage primaryStage) throws Exception {
String[] ingredients = new String[]{
"Pepperoni",
"Cheese",
"Tomato",
"Olives",
"Chicken"
};
final int maxCount = 3;
final Set<CheckBox> activeBoxes = new LinkedHashSet<>();
ChangeListener<Boolean> listener = (o, oldValue, newValue) -> {
// get checkbox containing property
CheckBox cb = (CheckBox) ((ReadOnlyProperty) o).getBean();
if (newValue) {
activeBoxes.add(cb);
if (activeBoxes.size() > maxCount) {
// get first checkbox to be activated
cb = activeBoxes.iterator().next();
// unselect; change listener will remove
cb.setSelected(false);
}
} else {
activeBoxes.remove(cb);
}
};
VBox root = new VBox();
// create checkboxes
for (int i = 0; i < ingredients.length; i++) {
CheckBox cb = new CheckBox(ingredients[i]);
cb.selectedProperty().addListener(listener);
root.getChildren().add(cb);
}
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Note: If you want the user to be simply unable to select more CheckBoxes when there are 3 selected, simply disable all unselected CheckBoxes when you reach 3 selected CheckBoxes:
final CheckBox[] checkBoxes = new CheckBox[ingredients.length];
ChangeListener<Boolean> listener = new ChangeListener<Boolean>() {
private int activeCount = 0;
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
if (newValue) {
activeCount++;
if (activeCount == maxCount) {
// disable unselected CheckBoxes
for (CheckBox cb : checkBoxes) {
if (!cb.isSelected()) {
cb.setDisable(true);
}
}
}
} else {
if (activeCount == maxCount) {
// reenable CheckBoxes
for (CheckBox cb : checkBoxes) {
cb.setDisable(false);
}
}
activeCount--;
}
}
};
VBox root = new VBox();
// create checkboxes
for (int i = 0; i < ingredients.length; i++) {
CheckBox cb = new CheckBox(ingredients[i]);
cb.selectedProperty().addListener(listener);
root.getChildren().add(cb);
checkBoxes[i] = cb;
}

Related

Using filteredList in an editable ListView

I'm using a FilteredList for my ListView to enable searching. The problem is that FilteredList does not allow mutating the data in any way, it only responds to changes in underlying ObservableList.
Also, it is declared final, so I can't simply extend it to forward the edit requests to the source.
So, how can I use it in an Editable ListView?
Here is the code to reproduce the problem
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
//problematic code
var observableList = FXCollections.observableArrayList("name", "name 2", "name 3");
FilteredList<String> filteredList = new FilteredList<>(observableList);
var list = new ListView<>(filteredList);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
//boilerplate code
VBox wrapper = new VBox(list);
primaryStage.setScene(new Scene(wrapper));
primaryStage.show();
}
}
Edit: added an minimal reproducible example
The problem - as you noticed - is that none of the concrete implementations of TransformationList (Sorted/FilteredList) is modifiable. So the default commit handler fails (with UnsupportedOperationException) while trying to set the newValue:
private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
int index = t.getIndex();
List<T> list = getItems();
if (index < 0 || index >= list.size()) return;
list.set(index, t.getNewValue());
};
The way out is a custom commit handler. Its implementation depends on context, it can
set a new item in the underlying source list
modify a property of the item
Code snippet for setting an item:
// monolithic items
ObservableList<String> data = FXCollections.observableArrayList("afirst", "abString", "other");
FilteredList<String> filteredData = new FilteredList<>(data);
filteredData.setPredicate(text -> text.contains("a"));
// set up an editable listView
ListView<String> list = new ListView<>(filteredData);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
// commitHandler resetting the underlying data element
list.setOnEditCommit(v -> {
ObservableList<String> items = list.getItems();
int index = v.getIndex();
if (items instanceof TransformationList<?, ?>) {
TransformationList transformed = (TransformationList) items;
items = transformed.getSource();
index = transformed.getSourceIndex(index);
}
items.set(index, v.getNewValue());
});
Code snippet for changing a property of an item:
// items with properties
ObservableList<MenuItem> data = FXCollections.observableArrayList(
new MenuItem("afirst"), new MenuItem("abString"), new MenuItem("other"));
FilteredList<MenuItem> filteredData = new FilteredList<>(data);
// filter on text property
filteredData.setPredicate(menuItem -> menuItem.getText().contains("a"));
// set up an editable listView
ListView<MenuItem> list = new ListView<>(filteredData);
list.setEditable(true);
// converter for use in TextFieldListCell
StringConverter<MenuItem> converter = new StringConverter<>() {
#Override
public String toString(MenuItem menuItem) {
return menuItem != null ? menuItem.getText() : null;
}
#Override
public MenuItem fromString(String text) {
return new MenuItem(text);
}
};
list.setCellFactory(TextFieldListCell.forListView(converter));
// commitHandler changing a property of the item
list.setOnEditCommit(v -> {
ObservableList<MenuItem> items = list.getItems();
MenuItem column = items.get(v.getIndex());
MenuItem standIn = v.getNewValue();
column.setText(standIn.getText());
});

How to listen to on selected event in choice box javafx

Im using scenebuilder and I have come up with 3 choiceboxes. The second choicebox depends on the input of the first choicebox and the third depends on the 2nd. How can I achieve this?
I've tried this
#FXML
private ChoiceBox course;
course.getSelectionModel().selectedIndexProperty().addListener(
(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) -> {
//some code here
}
);
But this event only occurs if i switch value, the first selection would not trigger this event, which is not what I want.
How can I achieve this, thanks in advance.
You can do something like this where everytime an action is done it will set the values of the next one. Make note of the .getItems().clear(); this will ensure the list is emptied everytime so that you don't have old values in the list. The for loop however is not important only there to add some variety to the text values I added
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
ChoiceBox<String> choiceBoxOne = new ChoiceBox<>();
choiceBoxOne.setPrefWidth(100);
choiceBoxOne.getItems().addAll("Choice1", "Choice2", "Choice3");
ChoiceBox<String> choiceBoxTwo = new ChoiceBox<>();
choiceBoxTwo.setPrefWidth(100);
ChoiceBox<String> choiceBoxThree = new ChoiceBox<>();
choiceBoxThree.setPrefWidth(100);
choiceBoxOne.setOnAction(event -> {
choiceBoxTwo.getItems().clear();
//The above line is important otherwise everytime there is an action it will just keep adding more
if(choiceBoxOne.getValue()!=null) {//This cannot be null but I added because idk what yours will look like
for (int i = 3; i < 6; i++) {
choiceBoxTwo.getItems().add(choiceBoxOne.getValue() + i);
}
}
});
choiceBoxTwo.setOnAction(event -> {
choiceBoxThree.getItems().clear();
//The above line is important otherwise everytime there is an action it will just keep adding more
if(choiceBoxTwo.getValue()!=null) {//This can be null if ChoiceBoxOne is changed
for (int i = 6; i < 9; i++) {
choiceBoxThree.getItems().add(choiceBoxTwo.getValue() + i);
}
}
});
VBox vBox = new VBox();
vBox.setPrefSize(300, 300);
vBox.setAlignment(Pos.TOP_CENTER);
vBox.getChildren().addAll(choiceBoxOne, choiceBoxTwo, choiceBoxThree);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

Reloading a comboBox after changing it's values

I am populating a comboBox with items taken from a sql database when initializing the thread.
If anyone's interested:
public void initialize(URL arg0, ResourceBundle arg1) {
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
}
I have a part of my code that adds a new value to the sql table, and I want to re-populate the comboBox when that happens.
if I do:
cmBox.getItems().clear();
arr = sqld.selectAll();
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
It works fine, but I can't see the new changes unless I close and re-open the window that displays the comboBox.
No errors or anything, just looking for creative ways of re-loading a comboBox and have it actually show the updated values without manually closing and re-opening the window.
You don't need a refresh button just run comboBox.setItems(...) when you add "a new value to the sql table" this should update the combo box here is an example
public class Main extends Application {
private int[] data;
private int dataCount = 0;
#Override
public void start(Stage primaryStage) throws Exception{
data = randomizeData(dataCount);
ComboBox comboBox = new ComboBox();
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
Button updateDataButton = new Button("Update values in SQL Table");
updateDataButton.setOnAction(event -> {
//Update your SQL data
updateData();
//Refresh List
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
});
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().addAll(comboBox, updateDataButton);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
private int[] randomizeData(int additional){
int[] data = new int[5+additional];
for (int i = 0; i < data.length; i++) {
data[i] = (int) (Math.random()*10);
}
return data;
}
private void updateData(){
data = randomizeData(++dataCount);
}
public static void main(String[] args) { launch(args); }
}

search a word by key enter

i have a problem with my searching method.
With this method, I can enter a word in the textfield and display the word in the textarea. However, this only happens once if i let it run. I need to expand it so, that every time I click on "enter," the program should continue with searching in the textarea. How can i do this?
And please give me code examples. i have only 2 days left for my presentation.
Thanks a lot for the helps
textfield.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.ENTER) {
String text = textarea.getText();
Labeled errorText = null;
if (textfield.getText() != null && !textfield.getText().isEmpty()) {
index = textarea.getText().indexOf(textfield.getText());
textarea.getText();
if (index == -1) {
errorText.setText("Search key Not in the text");
} else {
// errorText.setText("Found");
textarea.selectRange(index, index + textfield.getLength());
}
}
}
}
});
There's an overloaded version of the indexOf method allowing you to search starting at a specific index. Keep track of the index of your last find and start searching from this position:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
textField.setOnAction(evt -> {
String searchText = textField.getText();
if (searchText.isEmpty()) {
return; // searching for empty text doesn't make sense
}
int index = textarea.getSelection().getEnd();
// in case of the first search, start at the beginning
// TODO: adjust condition/starting index according to needs
if (textarea.getSelection().getLength() == 0) {
index = 0;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
textarea.selectRange(newStartIndex, newStartIndex + searchText.length());
}
});
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}
Edit
If you are not satisfied with searching the element after the selection ( or after the cursor, if there is no range selected), you could save the data of the end of the last match:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
class SearchHandler implements EventHandler<ActionEvent> {
int index = 0;
#Override
public void handle(ActionEvent event) {
String searchText = textField.getText();
String fullText = textarea.getText();
if (index + searchText.length() > fullText.length()) {
// no more matches possible
// TODO: notify user
return;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
index = newStartIndex + searchText.length();
textarea.selectRange(newStartIndex, index);
} else {
index = fullText.length();
// TODO: notify user
}
}
}
SearchHandler handler = new SearchHandler();
textField.setOnAction(handler);
// reset index to search from start when changing the text of the TextField
textField.textProperty().addListener((o, oldValue, newValue) -> handler.index = 0);
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}

Drag and drop item into TableView, between existing rows

Table contains the following rows (one column just for example):
A
B
C
I'm trying to figure out how to drag an item into it, and have it placed between existing rows B and C.
I am able to do drag-and-drop that results in an item added at the end of table but I can't figure out how to place it in between rows, based on where I release the mouse button.
Create a rowFactory producing TableRows that accept the gesture and decide by the mouse position, whether to add the item before or after the row:
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
Button button = new Button("A");
// d&d source providing next char
button.setOnDragDetected(evt -> {
Dragboard db = button.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.putString(button.getText());
db.setContent(content);
});
button.setOnDragDone(evt -> {
if (evt.isAccepted()) {
// next char
button.setText(Character.toString((char) (button.getText().charAt(0) + 1)));
}
});
// accept for empty table too
table.setOnDragOver(evt -> {
if (evt.getDragboard().hasString()) {
evt.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
evt.consume();
});
table.setOnDragDropped(evt -> {
Dragboard db = evt.getDragboard();
if (db.hasString()) {
table.getItems().add(new Item(db.getString()));
evt.setDropCompleted(true);
}
evt.consume();
});
TableColumn<Item, String> col = new TableColumn<>("value");
col.setCellValueFactory(new PropertyValueFactory<>("value"));
table.getColumns().add(col);
// let rows accept drop too
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow();
row.setOnDragOver(evt -> {
if (evt.getDragboard().hasString()) {
evt.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
evt.consume();
});
row.setOnDragDropped(evt -> {
Dragboard db = evt.getDragboard();
if (db.hasString()) {
Item item = new Item(db.getString());
if (row.isEmpty()) {
// row is empty (at the end -> append item)
table.getItems().add(item);
} else {
// decide based on drop position whether to add the element before or after
int offset = evt.getY() > row.getHeight() / 2 ? 1 : 0;
table.getItems().add(row.getIndex() + offset, item);
evt.setDropCompleted(true);
}
}
evt.consume();
});
return row;
});
Scene scene = new Scene(new VBox(button, table));
primaryStage.setScene(scene);
primaryStage.show();
}
public class Item {
public Item() {
}
public Item(String value) {
this.value.set(value);
}
private final StringProperty value = new SimpleStringProperty();
public String getValue() {
return value.get();
}
public void setValue(String val) {
value.set(val);
}
public StringProperty valueProperty() {
return value;
}
}

Resources