i have following problem:
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (!selectionChanged(newV)) {
lististView.getSelectionModel().select(oldV);
}
});
selectionChanged(newV) just pop a Message Box with "You wanna select new value?" and return the result as a boolean value. When i click on cancel it returns false, so that the condition is true. But because of .select(oldV); it would be result in a loop. How can i cancel or rollback a selection in javafx listview?
Create a boolean flag and don't ask the user if it is not set. You need to wrap the change back to the original value (if the user vetoes the change) in a Platform.runLater(...) to avoid issues with conflicts in the selection model's selectedItems list (basically you cannot change a list while another list change is being processed).
private boolean checkSelectionChange = true ;
// ...
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (checkSelectionChange) {
checkSelectionChange = false ;
Platform.runLater(() -> {
if (!selectionChanged(newV)) {
lististView.getSelectionModel().select(oldV);
}
checkSelectionChange = true ;
});
}
});
SSCCE:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewSelectionUserVeto extends Application {
private boolean checkSelectionChange = true ;
#Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
listView.getItems().addAll("One", "Two", "Three", "Four");
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
if (checkSelectionChange) {
checkSelectionChange = false ;
Platform.runLater(() -> {
if (! verifySelectionChange(newValue)) {
listView.getSelectionModel().select(oldValue);
}
checkSelectionChange = true ;
});
}
});
Scene scene = new Scene(listView);
primaryStage.setScene(scene);
primaryStage.show();
}
private boolean verifySelectionChange(String newValue) {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setContentText("Change selection to "+newValue);
return alert.showAndWait().filter(ButtonType.OK::equals).isPresent();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I am making a highly interactive TabPane for viewing contact lists in JavaFX 8. For this I have made my own subclass of Tab, EditableTab, which has functionality for changing the name of the tab by double clicking on the name in the overview. When the user clicks the + sign to create a new contact list, I want the program to create a new tab, select it, then focus the name and select all the text - it is natural to name the contact list at once (similar to when you create a new file in windows).
My problem: This seems to be very unstable. Most of the times, it seems some kind of animation/transition problem arises, and the tab name ends up empty. Here is a screenshot of what usually, but not always, happens when the + button is clicked:
And here is what I want:
Here is the code for my EditableTab:
public class EditableTab extends Tab {
private Label lbl;
private TextField txtField;
public EditableTab(String text, Node content) {
super();
setContent(content);
lbl = new Label(text);
txtField = new TextField(text);
txtField.setStyle("-fx-background-color: transparent");
setGraphic(lbl);
setupInteractivity();
}
public TextField getTextField() {
return txtField;
}
private void setupInteractivity() {
lbl.setOnMouseClicked((mouseEvent) -> {
if (mouseEvent.getClickCount() == 2) {
showTextField();
}
});
txtField.setOnAction(event -> setGraphic(lbl));
txtField.focusedProperty().addListener(
(observable, oldValue, newValue) -> {
if (! newValue) {
lbl.setText(txtField.getText());
setGraphic(lbl);
}
});
}
public void showTextField() {
txtField.setPrefWidth(lbl.getWidth());
txtField.setText(lbl.getText());
setGraphic(txtField);
txtField.selectAll();
txtField.requestFocus();
}
}
And here is the code where the functionality is implemented:
private void addNewContactlist() {
Contactlist newList = new Contactlist();
newList.setName("New contact list");
contactlistApp.getContactlistData().add(newList);
ListView<Person> lv = new ListView<Person>(newList.getContacts());
setupListView(lv);
int position = tabPane.getTabs().size() - 1;
EditableTab tab = createEditableTab("New contact list", lv);
tabPane.getTabs().add(position, tab);
tabPane.getSelectionModel().select(tab);
tab.showTextField();
}
I suspect that the problem comes from some animation/transition timings, but that is really just a guess. I tried wrapping the showTextField() call in a Platform.runLater() with no luck.
Here is a small test app to replicate the issue:
public class TestApp extends Application {
TabPane tabPane = new TabPane();
#Override
public void start(Stage primaryStage) throws Exception {
Tab addNewContactlistTab = new Tab();
addNewContactlistTab.setClosable(false);
Label lbl = new Label("\u2795");
lbl.setOnMouseClicked(mouseEvent -> {
if (tabPane.getTabs().size() == 1) {
addNewTab();
}
});
addNewContactlistTab.setGraphic(lbl);
tabPane.getTabs().add(addNewContactlistTab);
addNewContactlistTab.selectedProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue && tabPane.getTabs().size() != 1) {
addNewTab();
}
});
Scene scene = new Scene(tabPane);
primaryStage.setScene(scene);
primaryStage.setWidth(600);
primaryStage.setHeight(400);
primaryStage.show();
}
private void addNewTab() {
int insertionIndex = tabPane.getTabs().size() - 1;
ListView<String> lv = new ListView<String>();
EditableTab tab = new EditableTab("Unnamed", lv);
tabPane.getTabs().add(insertionIndex, tab);
tabPane.getSelectionModel().select(tab);
tab.showTextField();
}
public static void main(String[] args) {
launch();
}
}
Here is my code for the RenamableTab class:
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
public class RenamableTab extends Tab {
private final Label label;
private final TextField textField;
public RenamableTab() {
this("New Tab", null);
}
public RenamableTab(String text) {
this(text, null);
}
public RenamableTab(String text, Node content) {
super();
label = new Label(text);
textField = new TextField(text);
setContent(content);
textField.setStyle("-fx-background-color: transparent");
setGraphic(label);
label.setOnMouseClicked((mouseEvent) -> {
if (mouseEvent.getClickCount() == 2) {
rename();
}
});
textField.setOnAction(event -> setGraphic(label));
textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
label.setText(textField.getText());
setGraphic(label);
}
});
}
public TextField getTextField() {
return textField;
}
public void rename() {
//textField.setPrefWidth(label.getWidth());
//textField.setText(label.getText());
setGraphic(textField);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(new Runnable() {
#Override
public void run() {
textField.selectAll();
textField.requestFocus();
}
});
}
}.start();
}
}
And here is my code for the FancyTabPane:
import javafx.event.EventHandler;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;
public class FancyTabPane extends TabPane {
public FancyTabPane() {
Tab newTabTab = new Tab();
newTabTab.setClosable(false);
Label addLabel = new Label("\u2795");
addLabel.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent paramT) {
System.out.println("mouse click");
addTab();
}
});
/*
* getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
* #Override
* public void changed(ObservableValue<? extends Tab> paramObservableValue, Tab paramT1, Tab
* paramT2) {
* System.out.println("model");
* if (paramT1 == newTabTab) {
* System.out.println("tab");
* addTab();
* }
* }
* });
*/
newTabTab.setGraphic(addLabel);
getTabs().add(newTabTab);
}
public void addTab() {
RenamableTab newTab = new RenamableTab();
getTabs().add(getTabs().size() - 1, newTab);
getSelectionModel().select(newTab);
newTab.rename();
}
}
I am having issued with the new tab button when another tab is selected, not sure how you overcame that.
I have an editable JavaFX ComboBox.
A user must only be able to
type alphabets ('a' to 'z'), space and round braces ('(', ')') to enter the string
press tab to exit
press enter to exit
How to filter out every other key, modifiers etc?
I have read about and used event handlers like Key_Pressed, Key_Released but I am unable to figure out a straight-forward way to achieve the above.
I am using Mac OS Yosemite, Java 8, latest version of JavaFX and
public static final EventType<KeyEvent> KEY_TYPED just does not work at all.
Below code is my attempt. The variable typedText stores the desired values.
comboBox.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
private final String[] allowedItems = new String[]{"a","b","c","d","e","f",
"g","h","i","j","k","l","m","n","o","p","q","r",
"s","t","u","v","w","x","y","z"," ","(",")"};
private final List data = Arrays.asList(allowedItems);
private String tempInput;
public boolean containsCaseInsensitive(String s, List<String> l){
for (String string : l) {
if (string.equalsIgnoreCase(s)){
return true;
}
}
return false;
}
public void handle(KeyEvent event) {
boolean b;
b = event.isShiftDown();
if (b) {
if (event.getText().equals("(")) {
tempInput = "(";
} else if (event.getText().equals(")")){
tempInput = ")";
}
} else {
tempInput = event.getCode().toString().toLowerCase();
}
System.out.println("tempInput:"+tempInput);
if (containsCaseInsensitive(tempInput, data)) {
typedText = tempInput;
System.out.println("typedText:"+typedText);
}
}
});
}
You can get the Editor, which is a TextField in your case, and add a TextFormatter to it which restricts the input.
Tab works out of the box, but the "enter" keypress is a different matter, I simply request the focus in this example. Usually you'd navigate to the next item in the focus traversal list, but there's no future-proof api for that yet in JavaFX.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ComboBoxSample extends Application {
#Override
public void start(Stage stage) {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setEditable(true);
comboBox.getItems().addAll("A", "B", "C", "D", "E");
comboBox.setValue("A");
// restrict input
TextField textField = comboBox.getEditor();
TextFormatter<String> formatter = new TextFormatter<String>(change -> {
change.setText(change.getText().replaceAll("[^a-z ()]", ""));
return change;
});
textField.setTextFormatter(formatter);
// dummy textfield to jump to on ENTER press
TextField dummyTextField = new TextField();
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if( e.getCode() == KeyCode.ENTER) {
dummyTextField.requestFocus();
e.consume();
}
});
HBox root = new HBox();
root.getChildren().addAll(comboBox, dummyTextField);
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have an event listener on a TableView that listens for keyboard event.
// Add event listener to table
table.setOnKeyTyped(event -> {
TablePosition<SimpleStringProperty, String> focusedCell = table.getFocusModel().getFocusedCell();
if (focusedCell != null)
{
table.getItems().get(focusedCell.getRow()).set(event.getCharacter());
table.edit(focusedCell.getRow(), focusedCell.getTableColumn());
}
});
I am having problems with updating the cell with the new data when a user clicks enter or changes focus to another cell. When you click enter or change focus, the cell becomes empty. I'm not sure why. How can I save the data and update the cell with the new data.
// Here is the full code.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewEdit extends Application
{
#Override
public void start(Stage primaryStage)
{
TableView<SimpleStringProperty> table = new TableView<SimpleStringProperty>();
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);
table.getColumns().add(this.createColumn());
ObservableList<SimpleStringProperty> rowData = FXCollections.observableArrayList();
//table.getItems().addAll(rowData);
for (int j = 0; j < 10; j++)
{
rowData.add(new SimpleStringProperty(String.format("Cell [%d", j)));
}
table.setItems(rowData);
table.setOnKeyTyped(event -> {
TablePosition<SimpleStringProperty, String> focusedCell = table.getFocusModel().getFocusedCell();
if (focusedCell != null)
{
table.getItems().get(focusedCell.getRow()).set(event.getCharacter());
table.edit(focusedCell.getRow(), focusedCell.getTableColumn());
}
});
Scene scene = new Scene(new BorderPane(table), 880, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn<SimpleStringProperty, String> createColumn()
{
TableColumn<SimpleStringProperty, String> col = new TableColumn<>("Column ");
col.setCellValueFactory(cellData -> cellData.getValue());
col.setCellFactory(column -> new EditCell());
return col;
}
private static class EditCell extends TableCell<SimpleStringProperty, String>
{
private final TextField textField = new TextField();
EditCell()
{
this.textProperty().bind(this.itemProperty());
this.setGraphic(this.textField);
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
this.textField.setOnAction(evt -> this.commitEdit(this.textField.getText()));
this.textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (!isNowFocused)
{
this.commitEdit(this.textField.getText());
}
});
}
#Override
public void startEdit()
{
super.startEdit();
this.textField.setText(this.getItem());
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
this.textField.requestFocus();
}
#Override
public void cancelEdit()
{
super.cancelEdit();
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(String text)
{
super.commitEdit(text);
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
public static void main(String[] args)
{
launch(args);
}
}
These get really tricky; I think anything "behavior-related" (i.e. standard controls reacting to user input) is hard to change and generally not well supported in JavaFX. Hopefully this is an area of the API that will be improved...
There seem to be a couple of different issues. I think that what is happening with the Enter key, is that although this generates an ActionEvent on the text field, which commits the edit, etc, the keyTyped event still propagates back to the table, causing it to re-enter editing mode. A fix for this seems to be to use a keyPressed handler on the table instead (though to be honest this doesn't feel very robust).
The code relies on the default onEditCommit handler on the table column to actually change the property value. The onEditCommit handler is invoked by the default table cell's commitEdit method. The problem with calling commitEdit(...) on losing focus is that the default commitEdit method first checks if the cell is in an editing state, and does nothing if it's not. It appears that when the cell loses focus, it is taken out of the editing state before the focusProperty listener is invoked, so the onEditCommit handler is never called. (As an aside, this also prevents example 13-11 "Alternative solution of cell editing" (sic) from working correctly in the JDK 8 u25 (the current version).)
The only fix I can see for this second issue is to directly update the property from the commitEdit(...) method. This requires the cell have a reference to the property, which breaks the nice separation between the cell and the cell value.
I rewrote the example using the usual Person example and incorporated these two fixes. This example works quite well, though as I said some parts feel as though they are not very robust:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewEditOnType extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);
table.getColumns().add(createColumn("First Name", Person::firstNameProperty));
table.getColumns().add(createColumn("Last Name", Person::lastNameProperty));
table.getColumns().add(createColumn("Email", Person::emailProperty));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
table.setOnKeyPressed(event -> {
TablePosition<Person, ?> pos = table.getFocusModel().getFocusedCell() ;
if (pos != null) {
table.edit(pos.getRow(), pos.getTableColumn());
}
});
Scene scene = new Scene(new BorderPane(table), 880, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn<Person, String> createColumn(String title, Function<Person, StringProperty> property) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(column -> new EditCell(property));
return col ;
}
private static class EditCell extends TableCell<Person, String> {
private final TextField textField = new TextField();
private final Function<Person, StringProperty> property ;
EditCell(Function<Person, StringProperty> property) {
this.property = property ;
textProperty().bind(itemProperty());
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
textField.setOnAction(evt -> {
commitEdit(textField.getText());
});
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (! isNowFocused) {
commitEdit(textField.getText());
}
});
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(getItem());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(String text) {
super.commitEdit(text);
Person person = getTableView().getItems().get(getIndex()) ;
StringProperty cellProperty = property.apply(person);
cellProperty.set(text);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final java.lang.String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final java.lang.String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
I have a listview that uses a CheckBoxListCell to display a list with checkboxes next to the items. How do I add a listener to this checkbox to know when an item as been selected or unselected?
Solution
You don't add a listener to the checkbox. You add a listener to the observable property of the object which was associated with the checkbox by the CheckBoxListCell.forListView routine.
Setting up the association:
ListView<Task> checklist = new ListView<>(tasks);
checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty));
Adding a listener for all items:
tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
// . . .
} else {
// . . .
}
}));
Documentation
The process is described in the CheckBoxListCell.forListView javadoc like so:
getSelectedProperty - A Callback that, given an object of type T
(which is a value taken out of the ListView.items list), will
return an ObservableValue that represents whether the given
item is selected or not. This ObservableValue will be bound
bidirectionally (meaning that the CheckBox in the cell will set/unset
this property based on user interactions, and the CheckBox will
reflect the state of the ObservableValue, if it changes externally).
Sample Program
A sample program which demonstrated some of the patterns which could be used with CheckBoxListCell:
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.util.*;
import java.util.stream.Collectors;
public class CheckList extends Application {
#Override
public void start(Stage stage) throws Exception{
ObservableList<Task> tasks = FXCollections.observableArrayList(
Arrays.stream(taskNames).map(Task::new).collect(Collectors.toList())
);
ListView<String> reactionLog = new ListView<>();
tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
reactionLog.getItems().add(reactionStrings.get(task.getName()));
reactionLog.scrollTo(reactionLog.getItems().size() - 1);
}
}));
ListView<Task> checklist = new ListView<>(tasks);
checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty, new StringConverter<Task>() {
#Override
public String toString(Task object) {
return object.getName();
}
#Override
public Task fromString(String string) {
return null;
}
}));
HBox layout = new HBox(10, checklist, reactionLog);
layout.setPrefSize(350, 150);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static class Task {
private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
private BooleanProperty selected = new SimpleBooleanProperty(false);
public Task(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public BooleanProperty selectedProperty() {
return selected;
}
public boolean isSelected() {
return selected.get();
}
public void setSelected(boolean selected) {
this.selected.set(selected);
}
}
public static void main(String[] args) {
launch(args);
}
private static final String[] taskNames = {
"Walk the dog",
"Skin the cat",
"Feed the pig"
};
private static final Map<String, String> reactionStrings = new HashMap<>();
static {
reactionStrings.put("Walk the dog", "The dog thanks you");
reactionStrings.put("Skin the cat", "The cat hates you");
reactionStrings.put("Feed the pig", "The pig wants more");
}
}
Sample output after selecting the first item once and the third item three times.
Here is an alternative if the item does not already have a property that indicates if it has been selected or not:
public class CheckedListViewCheckObserver<T> extends SimpleObjectProperty<Pair<T, Boolean>> {
BooleanProperty getObserverForObject(T object) {
BooleanProperty value = new SimpleBooleanProperty(false);
value.addListener((observable, oldValue, newValue) -> {
CheckedListViewCheckObserver.this.set(new Pair<>(object, newValue));
});
return value;
}
}
Then to use it, you simply do:
CheckedListViewCheckObserver observer = new CheckedListViewCheckObserver<>();
checklist.setCellFactory(CheckBoxListCell.forListView(observer::getObserverForObject));
Now you can set a listener to listen for any changes:
observer.addListener((obs, old, curr) -> {
if (curr.getValue()) {
System.out.println("You have checked " + curr.getKey());
} else {
System.out.println("You have unchecked " + curr.getKey());
}
});
The advantage of this method is that it does not depend on the objects being used; Instead, since it is generic, you can simply attach it to an already existing listview and it starts working off the bat.
Hope this helps someone.
I'm fairly new to JavaFX and I'm trying to accomplish this principle in JavaFX: I've got a TableView populated with Student objects. I want the first column to be a checkbox with which I can select each row to perform a bulk action on the selected items (as commonly seen in for example mail applications).
I figured I shouldn't add a SimpleBooleanProperty to the Student class since it is only used in the view layer, which is why I thought I could implement it like this: when a checkbox is checked, the student gets added to a List selectedStudents; when it is unchecked, it is removed. Is this a good approach?
This is the code I've got so far (mainly based on copy-pasting from similar solutions):
voornaamKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("name"));
familienaamKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("fname"));
promotorKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("comment"));
selectedKolom.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<Student, Boolean>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Student, Boolean> p) {
return new SimpleBooleanProperty(p.getValue() != null);
}
});
selectedKolom.setCellFactory(
new Callback<TableColumn<Student, Boolean>, TableCell<Student, Boolean>>() {
#Override
public TableCell<Student, Boolean> call(TableColumn<Student, Boolean> p) {
return new CheckBoxCell(studentenTabel);
}
});
studentenTabel.getItems().setAll(getModel().getStudenten());
--
private class CheckBoxCell extends TableCell<Student, Boolean> {
final CheckBox cellCheckBox = new CheckBox();
CheckBoxCell(final TableView tblView) {
cellCheckBox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
int selectedIndex = getTableRow().getIndex();
if (!cellCheckBox.isSelected()) {
getModel().selectStudent(selectedIndex); // add to selectedStudents
} else {
getModel().deselectStudent(selectedIndex); // remove from selectedStudents
}
}
});
}
//Display button if the row is not empty
#Override
protected void updateItem(Boolean t, boolean empty) {
super.updateItem(t, empty);
if (!empty) {
setGraphic(cellCheckBox);
}
}
}
The main problem with this code is that the checkboxes are not bound to the table rows. E.g. when I select the 2nd item and change the row order by sorting on another value, the 2nd item is still selected even though it represents another object. When new rows are added to the table, some of them get randomly selected too.
I know this code is probably quite dirty, like I said: I'm new to JavaFX. Any help would be appreciated.
Thanks in advance!
The data type for your check box column seems to me it should be Student; i.e. it's a TableColumn<Student, Student>. The reason for this is that you're really presenting a view of the entire object itself: is the student contained in the collection of selected students. Sort of counter-intuitive but it makes it work.
See if this example helps. I don't have the nice separation of the data into a model that your code hints at, but you should be able to factor that in too.
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SelectableTable extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> itemTable = new TableView<>();
for (int i=1; i<=40; i++) {
itemTable.getItems().add(new Item("Item "+i));
}
TableColumn<Item, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<Item, Item> selectedCol = new TableColumn<>("Select");
// Collection of items currently selected via checkboxes in the table
// This will be passed to the TableCell implementation.
ObservableSet<Item> selectedItems = FXCollections.observableSet();
selectedCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Item,Item>, ObservableValue<Item>>() {
#Override
public ObservableValue<Item> call(CellDataFeatures<Item, Item> data) {
return new ReadOnlyObjectWrapper<>(data.getValue());
}
});
selectedCol.setCellFactory(new Callback<TableColumn<Item, Item>, TableCell<Item, Item>>() {
#Override
public TableCell<Item, Item> call(
TableColumn<Item, Item> param) {
return new CheckBoxCell(selectedItems);
}
});
itemTable.getColumns().addAll(selectedCol, nameCol);
Button displayButton = new Button("Display selected");
displayButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (Item item : selectedItems) {
System.out.println(item.getName());
}
}
});
Button selectAllButton = new Button("Select all");
selectAllButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
selectedItems.addAll(itemTable.getItems());
}
});
Button selectNoneButton = new Button("Select none");
selectNoneButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
selectedItems.clear();
}
});
HBox buttons = new HBox(5);
buttons.getChildren().addAll(selectAllButton, selectNoneButton, displayButton);
BorderPane root = new BorderPane();
root.setCenter(itemTable);
root.setBottom(buttons);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class CheckBoxCell extends TableCell<Item, Item> {
private final ObservableSet<Item> selectedItems ;
private final CheckBox checkBox ;
public CheckBoxCell(ObservableSet<Item> selectedItems) {
this.selectedItems = selectedItems ;
this.checkBox = new CheckBox() ;
// listener to update the set of selected items when the
// check box is checked or unchecked:
checkBox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (checkBox.isSelected()) {
selectedItems.add(getItem());
} else {
selectedItems.remove(getItem());
}
}
});
// listener to update the check box when the collection of selected
// items changes:
selectedItems.addListener(new SetChangeListener<Item>() {
#Override
public void onChanged(Change<? extends Item> change) {
Item item = getItem();
if (item != null) {
checkBox.setSelected(selectedItems.contains(item));
}
}
});
}
#Override
public void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
checkBox.setSelected(selectedItems.contains(item));
setGraphic(checkBox);
}
}
}
public static class Item {
private final StringProperty name = new SimpleStringProperty(this, "name");
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public Item(String name) {
setName(name);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can implement getModel().selectStudent(selectedObject);
and getModel().deselectStudent(selectedObject); instead. In here, selectedObject should be the object itself and not just the index. The model should point to the objects instead of indexes.
It's good to remember that you can make your model point to the exact objects you created. You just have to make sure the correct objects are being pointed to.