13

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!

kmugi
  • 343
  • 1
  • 4
  • 13

1 Answers1

24

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);
        }
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 3
    Thanks very much! Definitely works well with FXML too. Just to provide information to others facing the same problem: I put a new TableColumn and deleted the del() method (initially i created a Person class that also accepts button in its constructor and this is where the errors were coming from). – kmugi Aug 29 '15 at 12:41
  • 1
    Just a little simplification ... The updateItem could use the getIndex() method of TableCell instead of being passed the item? This appears to work, using the correct index, even when the TableView sort order has changed. With that the column type can be , and no need to set a cell value factory. – guymac Feb 16 '21 at 01:19