Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply conditional formatting to a TableCell textfield based on two properties on a row in TableView

Tags:

java

javafx

I have a TableView which contains columns that always display a writable textfield. I would like to have the textfield change colour if the "BigDecimal" value of column1's value is larger than the column2's value. I can stylize the textfield in the EditableTextCell class (for example if the text is not a valid number), but it seems like it doesn't have access to the model to make the other comparisons. Here is my code:

EditableTextCell.java

package tester;

import java.util.Objects;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;

public class EditableTextCell<E> extends TableCell<E, String>
{

private final TextField textField;
private boolean updating = false;

public EditableTextCell(boolean editable)
{
    textField = new TextField();
    textField.setAlignment(Pos.CENTER_RIGHT);

    textField.setEditable(editable);

    textField.textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
    {

        if (!updating)
        {
            ((WritableValue<String>) getTableColumn().getCellObservableValue((E) getTableRow().getItem())).setValue(newValue);
            getTableView().scrollTo(getTableRow().getIndex());
            getTableView().scrollToColumn(getTableColumn());
        }
        // this is where I would like stylize the textfield based on the input

    });
}

@Override
protected void updateItem(String item, boolean empty)
{
    super.updateItem(item, empty);
    if (empty)
    {
        setGraphic(null);
    } else
    {
        setGraphic(textField);
        if (!Objects.equals(textField.getText(), item))
        {
            // prevent own updates from moving the cursor
            updating = true;
            textField.setText(item);
            updating = false;

        }
    }
}
}

LineItem.java

package tester;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class LineItem
{

private final StringProperty string1;
private final StringProperty string2;

public LineItem()
{
    this.string1 = new SimpleStringProperty();
    this.string2 = new SimpleStringProperty();
}

public final StringProperty getString1Property()
{
    return this.string1;
}

public final StringProperty getString2Property()
{
    return this.string2;
}
}

Tester.java

package tester;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Tester extends Application
{

@Override
public void start(Stage primaryStage)
{

    TableView<LineItem> table = new TableView<>();
    table.setRowFactory(p ->
    {
        final TableRow<LineItem> row = new TableRow<>();
        row.setOnMouseClicked(event ->
        {
            if (event.getClickCount() == 2 && (!row.isEmpty()))
            {
                LineItem rowData = row.getItem();
                System.out.println(rowData.getString1Property().get() + " "+rowData.getString2Property().get());
            }

        });
        return row;
    });
    Callback<TableColumn<LineItem, String>, TableCell<LineItem, String>> textFactoryEditable = (TableColumn<LineItem, String> p) -> new EditableTextCell(true);

    TableColumn<LineItem, String> column1 = new TableColumn<>("Test1");
    column1.setCellValueFactory(cellData -> cellData.getValue().getString1Property());
    column1.setEditable(true);
    column1.setCellFactory(textFactoryEditable);

    table.getColumns().add(column1);

    TableColumn<LineItem, String> column2 = new TableColumn<>("Test2");
    column2.setCellValueFactory(cellData -> cellData.getValue().getString2Property());
    column2.setEditable(true);
    column2.setCellFactory(textFactoryEditable);

    table.getColumns().add(column2);

    table.getItems().add(new LineItem());
    HBox root = new HBox();
    root.getChildren().addAll(table);

    Scene scene = new Scene(root, 500, 500);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
}

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

}
like image 320
trilogy Avatar asked Jan 07 '19 18:01

trilogy


2 Answers

I figured it out. This is the line to get access to the model class.

LineItem lineItem = (LineItem) getTableRow().getItem();

However, my issue was that I was also changing the class declaration on EditableTextCell to match the type:

public class EditableTextCell<E> extends TableCell<E, String>

to:

public class EditableTextCell<LineItem> extends TableCell<LineItem, String>

This stopped me from using the properties under getTableRow().getItem()

with this error:

cannot find symbol
symbol:   method getString1Property()
location: variable lineItem of type LineItem
where LineItem is a type-variable:
LineItem extends Object declared in class EditableTextCell`
like image 99
trilogy Avatar answered Oct 23 '22 01:10

trilogy


I reedited the answer by pasting the whole code. I left comments where I put some code.

CSS code:

.red{
    -fx-background-color:red;
}

.white{
    -fx-background-color:white;
}

Tester class:

package tester;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.util.ArrayList;

public class Tester extends Application
{

    private static ArrayList<TableColumn<LineItem, String>> tableColumnArrayList;

    @Override
    public void start(Stage primaryStage)
    {

        TableView<LineItem> table = new TableView<>();
        table.setRowFactory(p ->
        {
            final TableRow<LineItem> row = new TableRow<>();
            row.setOnMouseClicked(event ->
            {
                if (event.getClickCount() == 2 && (!row.isEmpty()))
                {
                    LineItem rowData = row.getItem();
                    System.out.println(rowData.getString1Property().get() + " "+rowData.getString2Property().get());
                }

            });
            return row;
        });

        ArrayList<TableColumn<LineItem, String>> tableColumnArrayList = new ArrayList<>();


        Callback<TableColumn<LineItem, String>, TableCell<LineItem, String>> textFactoryEditable = (TableColumn<LineItem, String> p) -> new EditableTextCell(true);

        TableColumn<LineItem, String> column1 = new TableColumn<>("Test1");
        column1.setCellValueFactory(cellData -> cellData.getValue().getString1Property());
        column1.setEditable(true);
        column1.setCellFactory(textFactoryEditable);

        //I add each column
        tableColumnArrayList.add(column1);

        table.getColumns().add(column1);

        TableColumn<LineItem, String> column2 = new TableColumn<>("Test2");
        column2.setCellValueFactory(cellData -> cellData.getValue().getString2Property());
        column2.setEditable(true);
        column2.setCellFactory(textFactoryEditable);


        //I add
        tableColumnArrayList.add(column2);

        table.getColumns().add(column2);

        table.getItems().add(new LineItem());

        //here I put the TableColumnArrayList to a static field
        Tester.tableColumnArrayList = tableColumnArrayList;

        HBox root = new HBox();
        root.getChildren().addAll(table);

        Scene scene = new Scene(root, 500, 500);

        //here I set the stylesheet
        scene.getStylesheets().add(getClass().getResource("stylesheet.css").toExternalForm());

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }


    /**
     * static getter
     * @return ArrayList object containing TableColumn objects
     */
    public static ArrayList<TableColumn<LineItem, String>> getTableColumnArrayList() {
        return tableColumnArrayList;
    }

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

}

EditableTextCell class:

package tester;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Objects;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;

public class EditableTextCell<E> extends TableCell<E, String>
{

    private final TextField textField;
    private boolean updating = false;

    public EditableTextCell(boolean editable)
    {
        textField = new TextField();
        textField.setAlignment(Pos.CENTER_RIGHT);

        textField.setEditable(editable);

        textField.textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
        {

            if (!updating)
            {
                ((WritableValue<String>) getTableColumn().getCellObservableValue((E) getTableRow().getItem())).setValue(newValue);
                getTableView().scrollTo(getTableRow().getIndex());
                getTableView().scrollToColumn(getTableColumn());
            }


            // this is where I would like stylize the textfield based on the input
            ArrayList<TableColumn<LineItem, String>> tableColumnArrayList = Tester.getTableColumnArrayList();

            for(int i = 0; i < tableColumnArrayList.size(); i++){


                if(i == 0){
                    this.textField.getStyleClass().clear(); //remove all old classes
                    this.textField.getStyleClass().add("white"); //add new class
                } else {

                    String bd1String = tableColumnArrayList.get(i-1).getCellObservableValue(0).getValue();//first row
                    String bd2String = tableColumnArrayList.get(i).getCellObservableValue(0).getValue();

                    BigDecimal bd1, bd2;
                    try {
                        bd1 = new BigDecimal(bd1String);
                        bd2 = new BigDecimal(bd2String);
                    } catch(NullPointerException e){ //start imput will be null if You don't set anything
                        bd1 = BigDecimal.ZERO;
                        bd2 = BigDecimal.ZERO;
                    }
                    System.out.println(bd1 + " + " + bd2);

                    this.textField.getStyleClass().clear();
                    this.textField.getStyleClass().add(
                            (bd1.compareTo(bd2) > 0) ? "red" : "white"
                    );
                    System.out.println(this.textField.getStyleClass());

                }


            }


        });
    }

    @Override
    protected void updateItem(String item, boolean empty)
    {
        super.updateItem(item, empty);
        if (empty)
        {
            setGraphic(null);
        } else
        {
            setGraphic(textField);
            if (!Objects.equals(textField.getText(), item))
            {
                // prevent own updates from moving the cursor
                updating = true;
                textField.setText(item);
                updating = false;

            }
        }
    }
}

I didn't change a thing in LineItem class.

I created a static field- ArrayList of TableColumn objects- and created a static getter for it. I added the stylesheet to the scene (you may notice I don't have the slash in the path- the stylesheet is not in resources, but in the same path, as classes).

In the place been marked with a comment in class EditableTextCell I got the ArrayList from Tester class, looped over it and set style classes for the cells stored in the TableColumn object.

I create a try/catch block in which I initialized BigDecimal objects because the method was pulling there nulls.

like image 2
Embid123 Avatar answered Oct 23 '22 02:10

Embid123