Toon Boeckling
Toon Boeckling

Reputation: 121

auto numbered table rows (javafx)

My question is how to make a new Table in JavaFX with in the first column the index of the tableRow.

So i've created a class: NrCellFactory.

public class NrCellFactory<S, String> extends TableCellFactory<S,String> {

private class NrCell<S,String> extends TableCell<S,String>{

    public NrCell(){
        setText(this.getTableRow().getIndex()+"");
    }



}

@Override
protected TableCell<S, String> createTableCell(TableColumn<S, String> column) {
    return new NrCell();
}

}

and then i set my column where the numbers should be displayed:

 nrCol.setCellFactory(new NrCellFactory<Person,String>());

when I load the project, the nrCol has no data...

Can anyone solve the problem?

Thanks

Upvotes: 12

Views: 18619

Answers (4)

David Moles
David Moles

Reputation: 51073

A simple approach that doesn't depend on indexOf(item) or on updateItem() (which may or may not be the only event you would need to listen to) is to bind the text property of the TableCell to its row index:

TableColumn<S, Integer> indexColumn = new TableColumn<>();
indexColumn.setCellFactory(col -> {
  TableCell<S, Integer> indexCell = new TableCell<>();
  ReadOnlyObjectProperty<TableRow<S>> rowProperty = indexCell.tableRowProperty();
  ObjectBinding<String> rowBinding = Bindings.createObjectBinding(() -> {
    TableRow<S> row = rowProperty.get();
    if (row != null) { // can be null during CSS processing
      int rowIndex = row.getIndex();
      if (rowIndex < row.getTableView().getItems().size()) {
        return Integer.toString(rowIndex);
      }
    }
    return null;
  }, rowProperty);
  indexCell.textProperty().bind(rowBinding);
  return indexCell;
});

indexing only rows with data

If you don't care whether or not rows contain data, you can remove the rowIndex < ...size() check:

TableRow<S> row = rowProperty.get();
return row == null ? null : Integer.toString(row.getIndex());

indexing all rows

Upvotes: 0

Dave_cz
Dave_cz

Reputation: 1210

This is a universal (Generic) cell factory you can simply use anywhere:

public class LineNumbersCellFactory<T, E> implements Callback<TableColumn<T, E>, TableCell<T, E>> {

    public LineNumbersCellFactory() {
    }

    @Override
    public TableCell<T, E> call(TableColumn<T, E> param) {
        return new TableCell<T, E>() {
            @Override
            protected void updateItem(E item, boolean empty) {
                super.updateItem(item, empty);

                if (!empty) {
                    setText(this.getTableRow().getIndex() + 1 + "");
                } else {
                    setText("");
                }
            }
        };
    }
}

Usage: colRowNum.setCellFactory(new LineNumbersCellFactory());

Delete the +1 if you need 0-indexed rows.

EDIT: Added else block when deleting items

Upvotes: 4

Michael
Michael

Reputation: 346

In java 8 it can be done even easier with lambda expression:

TableColumn<Person, Number> indexColumn = new TableColumn<Person, Number>("#");
indexColumn.setSortable(false);
indexColumn.setCellValueFactory(column-> new ReadOnlyObjectWrapper<Number>(YourTable.getItems().indexOf(column.getValue())));

Upvotes: 18

jewelsea
jewelsea

Reputation: 159291

Sample Solution

Here's a solution using a cell factory:

TableColumn numberCol = new TableColumn("#");
numberCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() {
  @Override public ObservableValue<Person> call(CellDataFeatures<Person, Person> p) {
    return new ReadOnlyObjectWrapper(p.getValue());
  }
});

numberCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
  @Override public TableCell<Person, Person> call(TableColumn<Person, Person> param) {
    return new TableCell<Person, Person>() {
      @Override protected void updateItem(Person item, boolean empty) {
        super.updateItem(item, empty);

        if (this.getTableRow() != null && item != null) {
          setText(this.getTableRow().getIndex()+"");
        } else {
          setText("");
        }
      }
    };
  }
});
numberCol.setSortable(false);

Simple Alternate Solution

And a simpler sample using a cell value factory and no cell factory for the normal case where all of the items in the backing data list for the table are unique and their index can be looked up via table.getItems().indexOf(p.getValue()):

TableColumn numberCol = new TableColumn("#");
numberCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
  @Override public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
    return new ReadOnlyObjectWrapper(table.getItems().indexOf(p.getValue()) + "");
  }
});   
numberCol.setSortable(false);

Why your attempt to do this failed

I couldn't say exactly why your attempt to do this failed as I don't think there is enough code in your question to accurately diagnose the failure. My guess is that you didn't provide a cell value factory for the row and also setting the text in the cell's constructor rather than an updateItem call caused it not to work.

Executable Sample

Here is an executable sample:

tableviewnumberedrows

import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

public class NumberedTableViewSample extends Application {

    private TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "[email protected]"),
            new Person("Isabella", "Johnson", "[email protected]"),
            new Person("Ethan", "Williams", "[email protected]"),
            new Person("Emma", "Jones", "[email protected]"),
            new Person("Michael", "Brown", "[email protected]")
        );

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(470);
        stage.setHeight(500);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);

        TableColumn numberCol = new TableColumn("#");
        numberCol.setMinWidth(20);
        numberCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() {
            @Override public ObservableValue<Person> call(CellDataFeatures<Person, Person> p) {
                return new ReadOnlyObjectWrapper(p.getValue());
            }
        });

        numberCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
            @Override public TableCell<Person, Person> call(TableColumn<Person, Person> param) {
                return new TableCell<Person, Person>() {
                    @Override protected void updateItem(Person item, boolean empty) {
                        super.updateItem(item, empty);

                        if (this.getTableRow() != null && item != null) {
                            setText(this.getTableRow().getIndex()+"");
                        } else {
                            setText("");
                        }
                    }
                };
            }
        });
        numberCol.setSortable(false);

        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));

        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));

        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));

        table.setItems(data);
        table.getColumns().addAll(numberCol, firstNameCol, lastNameCol, emailCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);
        stage.setScene(scene);
        stage.show();
    }

    public static class Person {

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        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);
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

Upvotes: 26

Related Questions