Jay Black
Jay Black

Reputation: 107

Set JavaFX FXML table row text color based on an undisplayed java bean value in the List Element used to populate the table

I'm trying to transition an application I built in swing to JavaFx. This includes overhauling the GUI design. The application processes data as follows:

Data is retrieved from a database. Each row in the database is parsed to a java bean and each bean is added to an ArrayList. The array list is then returned to the calling method and then parsed to an ObservableList to make it compatible with the JavaFX tableview. I then populate the table by adding each List element -which is a java bean - to it.

Significantly, the Java bean that makes up each row of the table has 12 elements. The table displays only 9 of these to the user in its 9 columns. What I am trying to do it take one of the other undisplayed values in the rows List element and use this to determine if the text color of the displayed row is set to red or green. I cannot seem to manage this. I've looked at several other similar questions on Stack and other forums and they seem to solve the problem of setting a cell text color for a specific cell or column but not a row. They also seem to do this relying on a visible displayed value. I've tried several approaches but none of them seem to work, and they seem complicated. There must be a much more straight forward approach to what I'm trying to do as this must be a fairly common requirement. Can someone please show me how to do this?

My table is defined in FXML as follows:

 <TableView fx:id="toDoTable" editable="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
           <columns>
              <TableColumn fx:id="todoID" prefWidth="59.0" text="ID" />
              <TableColumn id="columnHeader" fx:id="Charity" prefWidth="77.0" text="Charity" />
              <TableColumn fx:id="todoFunder" prefWidth="101.0" text="Funder" />
              <TableColumn fx:id="todoType" prefWidth="92.0" text="Task Type" />
              <TableColumn fx:id="todoInternalDeadline" prefWidth="145.0" text="Internal Deadline" />
              <TableColumn fx:id="todoExternalDeadline" prefWidth="145.0" text="External Deadline" />
              <TableColumn fx:id="todoHrs" prefWidth="140.0" text="Target Hours" />
              <TableColumn fx:id="todoActualHrs" prefWidth="110.0" text="Actual Hours" />
              <TableColumn fx:id="todoDescription" prefWidth="110.0" text="Description" />
           </columns>
        </TableView>

The table is populated in the initialisation method in the corresponding controller class as follows:

public void initialize(URL url, ResourceBundle rb) {

    todoID.setCellValueFactory(new PropertyValueFactory<>("taskID"));
    todoClient.setCellValueFactory(new PropertyValueFactory<>("Charity"));
    todoFunder.setCellValueFactory(new PropertyValueFactory<>("taskFunder"));
    todoType.setCellValueFactory((new PropertyValueFactory<>("taskType")));
    todoInternalDeadline.setCellValueFactory((new PropertyValueFactory<>("internalDeadline")));
    todoExternalDeadline.setCellValueFactory((new PropertyValueFactory<>("externalDeadline")));
    todoHrs.setCellValueFactory((new PropertyValueFactory<>("assignedHours")));
    todoActualHrs.setCellValueFactory((new PropertyValueFactory<>("hoursCompleted")));
    todoDescription.setCellValueFactory((new PropertyValueFactory<>("taskDescription")));

    ObservableList<Task> list = FXCollections.observableArrayList(parseTaskBeans());//parseTaskBeans();
    toDoTable.getItems().addAll(list);
    GuiUtility.autoResizeColumns(toDoTable);
    //toDoTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
    toDoTable.autosize(); 
}

While the above will populate the whole table, I think what I need to do is process the rows individually and color them before adding them to the view. I'm assuming color must be a property of the table element rather than the List since the list is just the data. My most recent attempt is below but I think it must be completely wrong as I cant get a method of getting at the text in the row to define its color. So I've left comments as where I think I need to add code to solve this:

    for(int i = 0; i<list.size(); i++){
        System.out.println(list.get(i).getTaskReturned());
        if(list.get(i).getTaskReturned().equalsIgnoreCase("false")){
            //set Color red
        }
            else{
            //set color green
            }
            toDoTable.getItems().add(list.get(i));
        }

Another thought I had was use a lambda to process the table row content but again I can see how I get at the actual row. It seems really convoluted to set each element cell by cell so there must be a completely different way to think about this that I'm not getting. If someone could explain and show me how to do this I'd appreciate it.

Upvotes: 1

Views: 737

Answers (1)

Sarel Foyerlicht
Sarel Foyerlicht

Reputation: 927

example of code, hope it will help your case:

public class Main extends Application {
    private TableView table = new TableView();
    @Override
    public void start(Stage primaryStage) throws Exception{
        ObservableList<Data> data = FXCollections.observableArrayList(
                new Data("Jacob", "Smith", true),
                new Data("Isabella", "Johnson",true),
                new Data("Ethan", "Williams", false),
                new Data("Emma", "Jones", true),
                new Data("Michael", "Brown", true)
        );
        TableColumn firstDataCol = new TableColumn("Data1");
        firstDataCol.setMinWidth(100);
        firstDataCol.setCellValueFactory(
                new PropertyValueFactory<Data, String>("data1"));

        TableColumn secondDataCol = new TableColumn("Data2");
        secondDataCol.setMinWidth(100);
        secondDataCol.setCellValueFactory(
                new PropertyValueFactory<Data, String>("data2"));

        /*TableColumn isGreenCol = new TableColumn("IsGreen");
        isGreenCol.setMinWidth(200);
        isGreenCol.setCellValueFactory(
                new PropertyValueFactory<Data, Boolean>("isGreen"));*/

        table.setRowFactory(new Callback<TableView<Data>, TableRow<Data>>() {
            @Override
            public TableRow<Data> call(TableView<Data> tableView) {
                final TableRow<Data> row = new TableRow<Data>() {
                    @Override
                    protected void updateItem(Data data, boolean empty){
                        super.updateItem(data, empty);
                        if (data!=null&&data.isGreen.get()) {
                           setStyle("-fx-text-background-color: green;");

                        } else {
                            setStyle("-fx-text-background-color: red;");
                        }
                    }
                };

                return row;
            }
        });

        table.setItems(data);
        table.getColumns().addAll(firstDataCol, secondDataCol);
        Parent window = new VBox();
        ((VBox) window).getChildren().add(new Label("example of small window:"));
        primaryStage.setTitle("example");
        ((VBox) window).getChildren().add(table);
        Scene scene=new Scene(window);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public class Data {
        private final SimpleStringProperty data1;
        private final SimpleStringProperty data2;
        private final SimpleBooleanProperty isGreen;

        public Data(String data1, String data2, Boolean isGreen) {
            this.data1 = new SimpleStringProperty(data1);
            this.data2 = new SimpleStringProperty(data2);
            this.isGreen = new SimpleBooleanProperty(isGreen);
        }

        public String getData1() {
            return data1.get();
        }

        public SimpleStringProperty data1Property() {
            return data1;
        }

        public String getData2() {
            return data2.get();
        }

        public SimpleStringProperty data2Property() {
            return data2;
        }

        public boolean isIsGreen() {
            return isGreen.get();
        }

        public SimpleBooleanProperty isGreenProperty() {
            return isGreen;
        }
    }
    public static void main(String[] args) {
        launch(args);
    }
}

the output screen:

enter image description here

an explanation of how it works by Java Doc:

setRowFactory:

A function which produces a TableRow. The system is responsible for reusing TableRows. Return from this function a TableRow which might be usable for representing a single row in a TableView.

Note that a TableRow is not a TableCell. A TableRow is simply a container for a TableCell, and in most circumstances it is more likely that you'll want to create custom TableCells, rather than TableRows. The primary use case for creating custom TableRow instances would most probably be to introduce some form of column spanning support.

You can create custom TableCell instances per column by assigning the appropriate function to the cellFactory property in the TableColumn class. @return rowFactory property

and the updateItem call for every cell in a row:

updateItem:

The updateItem method should not be called by developers, but it is the best method for developers to override to allow for them to customise the visuals of the cell. To clarify, developers should never call this method in their code (they should leave it up to the UI control, such as the ListView control) to call this method. However, the purpose of having the updateItem method is so that developers, when specifying custom cell factories (again, like the ListView cell factory), the updateItem method can be overridden to allow for complete customisation of the cell. It is very important that subclasses of Cell override the updateItem method properly, as failure to do so will lead to issues such as blank cells or cells with unexpected content appearing within them. Here is an example of how to properly override the updateItem method:

protected void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);

            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                setText(item.toString());
            }
        }

Note in this code sample two important points: We call the super.updateItem(T, boolean) method. If this is not done, the item and empty properties are not correctly set, and you are likely to end up with graphical issues. We test for the empty condition, and if true, we set the text and graphic properties to null. If we do not do this, it is almost guaranteed that end users will see graphical artifacts in cells unexpectedly. Overrides: updateItem in class Cell Params: data – The new item for the cell. empty – whether or not this cell represents data from the list. If it is empty, then it does not represent any domain data, but is a cell being used to render an "empty" row.

Upvotes: 2

Related Questions