1

I have a Java FX8 Table, I have populated it using an observable list. The table is displaying the populated data.

During user interaction a new row is created, I add this new data to the observable list. The table gets refreshed.

I know how to move focus as well as to scroll to this newly added row. However I want this row to be highlighted to show that it is newly added.

How do I get a reference to the whole row as a Node, so that I can use this node value to highlight the row.

abacusreader
  • 547
  • 1
  • 7
  • 21
  • See this related discussion: http://stackoverflow.com/questions/20350099/programmatically-change-the-tableview-row-appearance/ – James_D Jun 12 '14 at 01:43
  • Many thanks for this, I did notice this conversation earlier, however I have created a method which takes a node and manipulates the node's background and apply animation/effects on it. So if I want to flash a table cell which has been edited, I call flashNode(this, "updated-cell"); in the commitEdit method of the corresponding column. How do I get a complete row as a Node so that I can reuse my existing method. Is that a possible or should I reimplment the code inside the rowfactory – abacusreader Jun 12 '14 at 08:11
  • The `row` provided by the `rowFactory` is the `Node` you want. You just need to add the code in the `rowFactory` to detect when it needs to be highlighted. I'll craft an answer... – James_D Jun 12 '14 at 11:17
  • Many thanks, I tried calling my animation method inside the call method inside the RowFactory, the call method is executed as many times as there are visible rows in the table, this results in all rows getting highlighted, this is when I add my code inside the listeners that you have defined. I am not able to filter out my required row based on the styledRowIndices value, I await your further input – abacusreader Jun 12 '14 at 11:44

1 Answers1

3

Use a rowFactory on the TableView which creates a TableRow. That TableRow is the Node representing the whole row. The slightly tricky part is identifying when the row represents a "recently added row".

I would approach this as follows. I'll use the usual contact table example

  1. Define an ObjectProperty<Person> to represent the recently added person. Most of the time this will be null but when a new Person is added to the list, it will be set to that new Person:

final ObjectProperty<Person> recentlyAddedPerson = new SimpleObjectProperty<>();

  1. Register a ListListener with the table's items list. When a new item is added to the list, update the recentlyAddedPerson. Since you don't want the new person to be labeled as "new" indefinitely, start a pause transition that will reset recentlyAddedPerson to null after some delay (a second or two).:

    final Duration timeToGetOld = Duration.seconds(1.0);
    
    table.getItems().addListener((Change<? extends Person> change) -> {
        while (change.next()) {
            if (change.wasAdded()) {
                List<? extends Person> addedPeople = change.getAddedSubList();
                Person lastAddedPerson = addedPeople.get(addedPeople.size()-1);
                recentlyAddedPerson.set(lastAddedPerson);
    
                // set back to null after a short delay, unless changed since then:
                PauseTransition agingTime = new PauseTransition(timeToGetOld);
                agingTime.setOnFinished(event -> {
                    if (recentlyAddedPerson.get() == lastAddedPerson) {
                        recentlyAddedPerson.set(null);
                    }
                });
                agingTime.play();
            }
        }
    });
    
  2. Create a row factory for the table. This row factory returns a custom TableRow. This custom TableRow creates a BooleanBinding which is set to true if this row represents a recently-added row. (This will be true if the row's item is not null and is equal to the recentlyAddedPerson defined above.)

  3. To actually implement the highlighting, I would just use a CSS PseudoClass. The implementation of the highlighting then becomes trivial; just set the pseudoclass state to the value in the BooleanBinding defined in the TableRow implementation.

So you'll need something like:

    final PseudoClass newPersonPseudoClass = PseudoClass.getPseudoClass("new");

and the rowFactory looks like

    table.setRowFactory(tableView -> new TableRow<Person>() {
        // Bindings API uses weak listeners, so this needs to be a field to
        // make sure it stays in scope as long as the TableRow is in scope.
        private final BooleanBinding itemIsNewPerson 
            = Bindings.isNotNull(itemProperty())
            .and(Bindings.equal(itemProperty(), recentlyAddedPerson));

        {
            // anonymous constructor:
            itemIsNewPerson.addListener((obs, wasNew, isNew)
                    -> pseudoClassStateChanged(newPersonPseudoClass, isNew));
        }
    });

Finally, just add the following css to your stylesheet:

.table-row-cell:new {
    -fx-background-color: darkseagreen ;
}

I posted the complete example as a gist.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Cool this works as intended, I just need to get a grip with the lambda expressions – abacusreader Jun 12 '14 at 16:17
  • You can translate them all to anonymous inner classes if you want... it's exactly the same thing. You'll probably fairly quickly find that you prefer the lambda expressions. – James_D Jun 12 '14 at 16:26