javafx-8fxml

FXML, JavaFX 8, TableView: Make a delete button in each row and delete the row accordingly


I am working on a TableView (FXML) where I want to have all the rows accompanied with a delete button at the last column.

Here's a video that shows what I mean: YouTube Delete Button in TableView

Here's what I have in my main controller class:

public Button del() {
    Button del = new Button();
    del.setText("X");
    del.setPrefWidth(30);
    del.setOnAction(new EventHandler<ActionEvent>() {
        public void handle(ActionEvent event) {
            int i = index.get();
            if(i > -1) {
                goals.remove(i);
                list.getSelectionModel().clearSelection();
            }
        }
    });
    return del;
}

private SimpleIntegerProperty index = new SimpleIntegerProperty();

@Override
public void initialize(URL location, ResourceBundle resources){
    //DateFormat df = new SimpleDateFormat("dd MMM yyyy");
    sdate.setValue(LocalDate.now());
    edate.setValue(LocalDate.now());

    seq.setCellValueFactory(new PropertyValueFactory<Goals, Integer>("id"));
    gol.setCellValueFactory(new PropertyValueFactory<Goals, String>("goal"));
    sdt.setCellValueFactory(new PropertyValueFactory<Goals, Date>("sdte"));
    edt.setCellValueFactory(new PropertyValueFactory<Goals, Date>("edte"));
    prog.setCellValueFactory(new PropertyValueFactory<Goals, Integer>("pb"));
    del.setCellValueFactory(new PropertyValueFactory<Goals, Button>("x"));

    list.setItems(goals);
    list.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
        @Override
        public void changed(ObservableValue<?> observable,
                Object oldValue, Object newValue) {
            index.set(goals.indexOf(newValue));
            System.out.println("Index is: "+goals.indexOf(newValue));
        }

    });
}

Each time I launch the application, I will try to click the delete button from random rows but it always delete the first row. I guess the addListener method I use for list is not properly implemented and indexOf(newValue) is always 0 at every initialisation.

However, it will work if I click a row first and then click the delete button. But this is not what I want. I want users to be able to delete any row if they press the delete button without selecting the row.

Appreciate your help guys!


Solution

  • You need a custom cell factory defined for the column containing the delete button.

    TableColumn<Person, Person> unfriendCol = new TableColumn<>("Anti-social");
    unfriendCol.setCellValueFactory(
        param -> new ReadOnlyObjectWrapper<>(param.getValue())
    );
    unfriendCol.setCellFactory(param -> new TableCell<Person, Person>() {
        private final Button deleteButton = new Button("Unfriend");
    
        @Override
        protected void updateItem(Person person, boolean empty) {
            super.updateItem(person, empty);
    
            if (person == null) {
                setGraphic(null);
                return;
            }
    
            setGraphic(deleteButton);
            deleteButton.setOnAction(
                event -> getTableView().getItems().remove(person)
            );
        }
    });
    

    Here is a sample app. It doesn't use FXML, but you could adapt it to work with FXML very easily. Just click on an "Unfriend" button in the "Anti-social" column to delete a friend. Do it a lot and you will soon run out of friends.

    anti-social

    import javafx.application.Application;
    import javafx.beans.property.ReadOnlyObjectWrapper;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.VBox;
    import javafx.scene.text.Font;
    import javafx.stage.Stage;
    
    public class GestureEvents extends Application {
        private TableView<Person> table = new TableView<>();
        private final ObservableList<Person> data =
            FXCollections.observableArrayList(
                new Person("Jacob", "Smith"),
                new Person("Isabella", "Johnson"),
                new Person("Ethan", "Williams"),
                new Person("Emma", "Jones"),
                new Person("Michael", "Brown")
            );
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
            final Label label = new Label("Friends");
            label.setFont(new Font("Arial", 20));
    
            final Label actionTaken = new Label();
    
            TableColumn<Person, Person> unfriendCol = new TableColumn<>("Anti-social");
            unfriendCol.setMinWidth(40);
            unfriendCol.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue()));
            unfriendCol.setCellFactory(param -> new TableCell<Person, Person>() {
                private final Button deleteButton = new Button("Unfriend");
    
                @Override
                protected void updateItem(Person person, boolean empty) {
                    super.updateItem(person, empty);
    
                    if (person == null) {
                        setGraphic(null);
                        return;
                    }
    
                    setGraphic(deleteButton);
                    deleteButton.setOnAction(event -> data.remove(person));
                }
            });
    
            TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
            firstNameCol.setMinWidth(100);
            firstNameCol.setCellValueFactory(
                    new PropertyValueFactory<>("firstName"));
    
            TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
            lastNameCol.setMinWidth(100);
            lastNameCol.setCellValueFactory(
                    new PropertyValueFactory<>("lastName"));
    
            table.setItems(data);
            table.getColumns().addAll(unfriendCol, firstNameCol, lastNameCol);
            table.setPrefHeight(250);
    
            final VBox vbox = new VBox();
            vbox.setSpacing(5);
            vbox.setPadding(new Insets(10, 10, 10, 10));
            vbox.getChildren().addAll(label, table, actionTaken);
            VBox.setVgrow(table, Priority.ALWAYS);
    
            stage.setScene(new Scene(vbox));
            stage.show();
        }
    
        public static class Person {
    
            private final SimpleStringProperty firstName;
            private final SimpleStringProperty lastName;
    
            private Person(String fName, String lName) {
                this.firstName = new SimpleStringProperty(fName);
                this.lastName = new SimpleStringProperty(lName);
            }
    
            public String getFirstName() {
                return firstName.get();
            }
    
            public void setFirstName(String fName) {
                firstName.set(fName);
            }
    
            public String getLastName() {
                return lastName.get();
            }
    
            public void setLastName(String fName) {
                lastName.set(fName);
            }
        }
    }