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?