Kachna
Kachna

Reputation: 2961

Changing table row color using a property that would not be visible in any column

I need to change the table row color using a property that would not be visible in any column of a tableview. I did the following:

  1. create a model class Person (serialNumber, first, last).
  2. create an observableList of Person using an extractor.
  3. create two tableviews(tableview1, tableview2) and one listview that all sharing the same data.
  4. tableview1 has a serialCol1 column with a visible property set to false.

I want to change tableview1 row color using the serialNumber property that is bound to a column in a tableview2.

Here is the complete program:

import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;

/**
 *
 * @author kachna
 */
public class Extractor extends Application {

    private final TableView<Person> tableView1 = new TableView<>();
    private final TableView<Person> tableView2 = new TableView<>();
    private final ListView<Person> listView = new ListView<>();
    //observable list with extractor
    private final ObservableList<Person> data = FXCollections.observableArrayList(p -> new Observable[]{p.serialNumberProperty(), p.firstProperty(), p.lastProperty()});

    static class Person {

        final IntegerProperty serialNumber;
        final StringProperty first;
        final StringProperty last;

        public Person(int serialNumber, String first, String last) {
            this.first = new SimpleStringProperty(first);
            this.last = new SimpleStringProperty(last);
            this.serialNumber = new SimpleIntegerProperty(serialNumber);
        }

        public IntegerProperty serialNumberProperty() {
            return serialNumber;
        }

        public StringProperty firstProperty() {
            return first;
        }

        public StringProperty lastProperty() {
            return last;
        }

        @Override
        public String toString() {
            return "Person{" + "first=" + first.get() + ", last=" + last.get() + '}';
        }

    }

    @Override
    public void start(Stage stage) {

        BorderPane root = new BorderPane();
        VBox vBox = new VBox(10);
        VBox.setVgrow(tableView2, Priority.ALWAYS);
        root.setPadding(new Insets(10));
        initTableViews();
        initListView();
        getData();
        Label label1 = new Label("TableView 1");
        label1.setStyle("-fx-font-size: 24px;\n"
                + "-fx-font-weight: bold;");
        Label label2 = new Label("TableView 2");
        label2.setStyle("-fx-font-size: 24px;\n"
                + "-fx-font-weight: bold;");
        vBox.getChildren().addAll(label1, tableView1,label2, tableView2);
        root.setCenter(vBox);
        root.setRight(listView);
        Scene scene = new Scene(root, 600, 400);
        stage.setScene(scene);
        stage.show();

    }

    private void initTableViews() {
        // first table view 
        tableView1.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        tableView1.setEditable(true);
        tableView1.setRowFactory(tv -> new TableRow<Person>() {

            @Override
            protected void updateItem(Person item, boolean empty) {
                super.updateItem(item, empty);
                if (item != null) {
                    if (item.serialNumber.get() % 2 == 0) {
                        setStyle("-fx-background-color: orange;");
                    } else {
                        setStyle(" ");
                    }
                } else {
                    setStyle(" ");
                }
            }

        });

        TableColumn<Person, Number> serialCol1 = new TableColumn<>("Serial Number");
        serialCol1.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
        serialCol1.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {

            @Override
            public String toString(Number object) {
                return object.toString();
            }

            @Override
            public Number fromString(String string) {
                return Integer.parseInt(string);
            }
        }));

        // make the serialCol1 column invisible
        serialCol1.setVisible(false);
        TableColumn<Person, String> firstCol1 = new TableColumn<>("First Name");
        firstCol1.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
        firstCol1.setCellFactory(TextFieldTableCell.forTableColumn());
        TableColumn<Person, String> lastCol1 = new TableColumn<>("Last Name");
        lastCol1.setCellFactory(TextFieldTableCell.forTableColumn());
        lastCol1.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
        tableView1.getColumns().addAll(serialCol1, firstCol1, lastCol1);
        tableView1.setItems(data);

        // second table view  
        tableView2.setEditable(true);
        tableView2.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        TableColumn<Person, Number> serialCol = new TableColumn<>("Serial Number");
        serialCol.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
        serialCol.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {

            @Override
            public String toString(Number object) {
                return object.toString();
            }

            @Override
            public Number fromString(String string) {
                return Integer.parseInt(string);
            }
        }));
        TableColumn<Person, String> firstCol2 = new TableColumn<>("First Name");
        firstCol2.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
        TableColumn<Person, String> lastCol2 = new TableColumn<>("Last Name");
        lastCol2.setCellFactory(TextFieldTableCell.forTableColumn());
        lastCol2.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
        tableView2.getColumns().addAll(serialCol, firstCol2, lastCol2);
        tableView2.setItems(data);
    }

    private void initListView() {
        //list view 

        listView.setCellFactory(list -> new ListCell<Person>() {
            @Override
            protected void updateItem(Person value, boolean empty) {
                super.updateItem(value, empty);
                if (!empty && value != null) {
                    if (value.serialNumber.get() % 2 == 0) {
                        setStyle("-fx-background-color: orange;");
                    } else {
                        setStyle(" ");
                    }
                    setText(String.format("%s %s %s", value.serialNumber.get(), value.firstProperty().get(), value.lastProperty().get()));
                } else {
                    setText(null);
                    setStyle(" ");
                }

            }
        });

        listView.setItems(data);
    }

    private void getData() {
        data.setAll(IntStream.range(0, 10)
                .mapToObj(i -> new Person(i, "first" + i, "last" + i))
                .collect(Collectors.toList()));
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

Problem:

enter image description here

Upvotes: 0

Views: 876

Answers (1)

Oliver Jan Krylow
Oliver Jan Krylow

Reputation: 1725

The updateItem method is not bound to the property lifecycle of its item ( an item must not be an Observable ), but rather gets called by the View (ListView/TableView) whenever it deems it necessary to update the data representation. When you scroll a Row off screen it gets nulled ( I assume for performance reasons ) and updated again when in screen.

What you want to do is to bind the stylePropertyof the row to its items serialNumberPropertylike so:

tableView1.setRowFactory( tv -> new TableRow<Person>()
    {
      @Override
      protected void updateItem( final Person item, final boolean empty )
      {
        super.updateItem( item, empty );

        if ( !empty && item != null )
        {

          this.styleProperty().bind( Bindings.createStringBinding( () ->
          {
            if ( item.serialNumber.get() % 2 == 0 )
            {
              return "-fx-background-color: orange;";
            }
            return " ";
          } , item.serialNumberProperty() ) );

        }
        else
        {
          /*
           * As per comment in the Cell API
           */
          setText( null );
          setGraphic( null );

          this.styleProperty().unbind();

          setStyle( " " );
        }
      }
    } );

I also recommend consulting the documentation of javafx.scene.control.Cell#updateitem(...) as it is marked as "Expert API".

Link to full example.

Upvotes: 3

Related Questions