auto numbered table rows (javafx)
Asked Answered
J

4

12

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

Jacobah answered 5/5, 2013 at 13:11 Comment(0)
A
26

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);
        }
    }
} 
Axon answered 6/5, 2013 at 21:11 Comment(5)
Very thanks it is very useful for my project how to learn this callback function.Redfaced
I tried the simple alternate solution mentioned in this. I have a problem. auto increment works like a charm, no doubt. However, I'm writing the contents of the table to a file. How can I get the value of the numberCol?Stans
Please ask a new question naveen, you can reference this answer in your question so you don't have to copy and paste a lot of stuff.Axon
What can we do to get the value of serial number using a get() method. Is there any way to do it ?Louannlouanna
@MayurPatel ask a question.Axon
C
18

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())));
Cultivated answered 13/2, 2015 at 19:29 Comment(3)
This is a bad idea: There is no guarantee that items are unique. If a item occurs in the list multiple times, all but the first number will be incorrect...Houston
how to start from 1 ,without 0Plasty
@zcon just add one to the result of indexOf(). (But @Houston is right.)Allopatric
C
4

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

Centro answered 29/3, 2017 at 19:39 Comment(0)
A
0

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

Allopatric answered 19/12, 2017 at 19:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.