JavaFX Custom CellFactory Wrapper Throwing Null Po

2020-05-02 13:08发布

问题:

I have created a cell factory wrapper to enable custom configuration of a JavaFX table cell. See code below.

package idmas.controller.modules.tableview;

import idmas.model.Cml;
import idmas.model.CmlDAO;
import idmas.model.Damageloop;
import idmas.model.modules.general.dateMethods;
import java.util.Date;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;

/**
 * Generic cell factory that can be used to override specific properties of cells, such as formatting (text size, color etc.) and even values.
 * @author Joshua Adams
 * @param <S> = Data type of table view to be modified. E.g. Damageloop, Equipment, CML, Inspection etc.
 * @param <T> = Data type of specific cell being overridden. E.g. Date, Integer, Double etc.
 */
public class CellFactoryCustom<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>>  {

    //Represents the name of the column for the specific tableview, i.e. Last Internal Inspection Date etc.
    String colname;

    //Constructors for class wide objects
    CmlDAO cmldao = new CmlDAO();

    /**
     * Class constructor requires a column name to be passed so the specific formatting for that column's cells can be applied.
     * @param colname = Name of column to apply formatting to.
     */
    public CellFactoryCustom (String colname) {
        this.colname = colname;
    }

    /**
     * Main method to override the properties of the cells for a table column.
     * @param arg
     * @return 
     */
    @Override
    public TableCell<S, T> call(TableColumn<S, T> arg) {
        TableCell<S, T> cell = new TableCell<S, T>() {
            @Override
            protected void updateItem(T item, boolean empty) {
                //super allows reffering to methods in superclass
                super.updateItem(item, empty);
                //recommended syntax for overridding the updateItem method - Refer to update item javadoc.
                if (empty || item == null) {
                    setText(null);
                    setGraphic(null);
                    getStyleClass().removeAll("highlightCellBlack");
                    getStyleClass().add(getTableRow().getStyleClass().toString());
                } else {
                    setConditionalFormatting(this,item);
                }
            }
        };
        return cell;
    }

    /**
     * Specific formatting rules which are applied to a columns cells based on its column name.
     * @param cell
     * @param item 
     */
    public void setConditionalFormatting (TableCell<S,T> cell, T item) {
        //Constructors for reference classes
        dateMethods datemethods = new dateMethods();
        Date currentdateplus60 = new Date();
        Date date;
        Damageloop damageloop;
        Cml cml;
        ComboBox combobox;
        //Current styles need to be removed to ensure all specific styles for each cell are applied correctly
        cell.getStyleClass().removeAll("highlightCellBlack");
        //Switch statement selected over if statement to improve performance of code and readibility
        switch (colname) {
            case "colNextExternalInspectionDate":
                damageloop = (Damageloop) cell.getTableRow().getItem();
                cell.setText(datemethods.getDateToString((Date) item,"dd/MM/yyyy"));
                currentdateplus60 = datemethods.getDatePlusDays(currentdateplus60, 60);
                date = (Date) item;
                if(date.before(currentdateplus60) && damageloop.getErrorcode() != 2 & damageloop.getErrorcode() != 3) {
                    cell.getStyleClass().add("highlightCellBlack");
                } else {
                    cell.getStyleClass().add(cell.getTableRow().getStyleClass().toString());
                }
                break;
            case "colNextInternalInspectionDate":
                damageloop = (Damageloop) cell.getTableRow().getItem();
                cell.setText(datemethods.getDateToString((Date) item,"dd/MM/yyyy"));
                currentdateplus60 = datemethods.getDatePlusDays(currentdateplus60, 60);
                date = (Date) item;
                if(date.before(currentdateplus60) && damageloop.getErrorcode() != 1 && damageloop.getErrorcode() != 3) {
                    cell.getStyleClass().add("highlightCellBlack");
                } else {
                    cell.getStyleClass().add(cell.getTableRow().getStyleClass().toString());
                }
                break;
            case "colCmlStatus":
                cml = (Cml) cell.getTableRow().getItem();
                String[] fieldsArray = new String[]{"C = Continue to Monitor", "S = Scoped", "X = Redundant"};
                String[] disabledFieldsArray = new String[]{"S = Scoped"};
                String SQLString = "UPDATE IDMAS.CML SET CMLSTATUS = '<NEW_STATUS>' WHERE EQUIPMENT_ID = '" + cml.getEquipmentId() + "' AND CML_NO = " + cml.getCmlNo();
                String replacestring = "<NEW_STATUS>";
                String defaultvalue = item.toString();
                ComboBoxCustom comboboxcustom = new ComboBoxCustom (fieldsArray, disabledFieldsArray, SQLString, replacestring, defaultvalue);
                comboboxcustom.setPrefWidth(10);
                cell.setGraphic(comboboxcustom);
                break;
            case "colCmlStatusRemediation":
                ObservableList<String> options = FXCollections.observableArrayList(
                        "A = Approved for Remediation",
                        "C = Continue to Monitor",
                        "F = Fit for Service",
                        "R = Recommend Remediation"
                );     
                cml = (Cml) cell.getTableRow().getItem();
                combobox = new ComboBox(options);
                combobox.setValue(item.toString());
                combobox.setPrefWidth(10);
                combobox.valueProperty().addListener(new ChangeListener<String>() {
                    @Override 
                    public void changed(ObservableValue ov, String oldvalue, String newvalue) {
                        cml.setCmlstatusremediation(newvalue.charAt(0));
                        cmldao.updateCml(cml);
                        //Platform runlater required to update the combox with the new value
                        Platform.runLater(() -> {
                            combobox.setValue(String.valueOf(newvalue.charAt(0)));
                        });
                    }    
                });
                cell.setGraphic(combobox);
                break;
            case "colTemporaryrepairinstalled":
                cml = (Cml) cell.getTableRow().getItem();
                CheckBox checkbox = new CheckBox();
                checkbox.setSelected(Boolean.valueOf(item.toString()));
                checkbox.selectedProperty().addListener(new ChangeListener<Boolean>() {
                    @Override
                    public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                        cml.setTemporaryrepairinstalled(newValue);
                        cmldao.updateCml(cml);
                        checkbox.setSelected(newValue);
                    }
                });
                cell.setGraphic(checkbox);
                break;
            default:
                break;
        }   
    }
}

And this cellfactory wrapper will work on generally navigating through all of my tableviews. However, as soon as I perform another function in my system such as adding new records, editing records etc. The following error is thrown.

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at idmas.controller.modules.tableview.CellFactoryCustom.setConditionalFormatting(CellFactoryCustom.java:110)
    at idmas.controller.modules.tableview.CellFactoryCustom$1.updateItem(CellFactoryCustom.java:60)
    at javafx.scene.control.TableCell.updateItem(TableCell.java:663)
    at javafx.scene.control.TableCell.indexChanged(TableCell.java:468)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.requestCellUpdate(TableRowSkinBase.java:659)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.handleControlPropertyChanged(TableRowSkinBase.java:234)
    at com.sun.javafx.scene.control.skin.TableRowSkin.handleControlPropertyChanged(TableRowSkin.java:70)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyIntegerPropertyBase.fireValueChangedEvent(ReadOnlyIntegerPropertyBase.java:72)
    at javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:102)
    at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:113)
    at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:147)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:115)
    at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1957)
    at com.sun.javafx.scene.control.skin.VirtualFlow.addTrailingCells(VirtualFlow.java:1344)
    at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1197)
    at javafx.scene.Parent.layout(Parent.java:1087)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Scene.doLayoutPass(Scene.java:552)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)

I cant seem to figure out why this wrapper thinks that my CML class has null values in it. Anyone have any ideas as to why this Null Pointer exception is being thrown?

回答1:

There is no guarantee that the TableCell is already associated with a TableRow or that the TableRow is already filled when the updateItem method is called the first time.

In your case probably getTableRow() returns null (which would mean however that cml = (Cml) cell.getTableRow().getItem(); is the line the exception is thrown, not String SQLString = "UPDATE IDMAS.CML SET CMLSTATUS = '<NEW_STATUS>' WHERE EQUIPMENT_ID = '" + cml.getEquipmentId() + "' AND CML_NO = " + cml.getCmlNo();); At least that happened when I tried to reproduce the error.

However if

String SQLString = "UPDATE IDMAS.CML SET CMLSTATUS = '<NEW_STATUS>' WHERE EQUIPMENT_ID = '" + cml.getEquipmentId() + "' AND CML_NO = " + cml.getCmlNo();

is indeed the line causing the error, then the only way for that to happen that results in a stacktrace like this is if cml is null.

A way to fix this would be getting the item from the table based on the index:

cml = (Cml) cell.getTableView().getItems().get(getIndex());


标签: java javafx