I have got a problem with my cellFactory.
I want that if a Date is set to my object inside a FX TableView, that the updateItem method checks if it is a valid date. If not the cell should be colored red.
_availableCol.setCellFactory(column -> {
return new TableCell<SimpleReservationUnit, LocalDate>() {
#Override
protected void updateItem(LocalDate date, boolean empty) {
super.updateItem(date, empty);
if (empty || date == null) {
setText(null);
}else {
if (date.compareTo(_newDeparture.getValue()) < 0) {
setStyle("-fx-background-color: red");
}else{
setStyle("");
}
}
}
};
});
The coloring works, but the LocalDate is never set to the cell. As far as I understand this should happen in the super() call.
A CellValueFactory is implemeted for this column:
_availableCol.setCellValueFactory(new PropertyValueFactory<>("Available"));
Any ideas what i am doing wrong?
You need to set the text of the node. You do clear it when you detect it is empty, but you don't set it when it is not. You also forget to clear the style if the cell is empty.
if (empty || date == null) {
setText(null);
setStyle(""); // can this be null?
} else {
LocalDate departureDate = _newDeparture.getValue();
String text = departureDate.toString(); // or format it for user locale
setText(text);
String style = date.isBefore(departureDate) ? "-fx-background-color: red" : "";
setStyle(style);
}
Related
I found a good example on the Internet how to do this, in fact it does not work. Below is the code with my comments, please help to figure out what's what and why it doesn't work.
#FXML
private void initialize() {
timeContractColumn.setCellFactory(column -> {
return new TableCell<MainData, LocalDate>() { // MainData - model,
// where all variables and collections for storing data are stored
// timeContractColumn - stores the entered date, therefore, LocalDate
#Override
protected void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) { //If the cell is empty
setText(null);
setStyle("");
} else { //If the cell is not empty
setText(item.toString()); //We place the data in the cell
System.out.println(item);
// We get here all the information about this line.
MainData auxPerson = getTableView().getItems().get(getIndex());
System.out.println(auxPerson.toString());
// Change the style if ...
if (auxPerson.getTimeContract().equals("2019-04-09")) {
setTextFill(Color.RED);
setStyle("-fx-background-color: yellow");
} else {
//Here we see whether the row of this cell is highlighted or not
if(getTableView().getSelectionModel().getSelectedItems().contains(auxPerson))
setTextFill(Color.WHITE);
else
setTextFill(Color.BLACK);
}
}
}
};
});
}
Fabian was right, I did not take into account that I was comparing different objects, thanks for all the advice.
if (auxPerson.getTimeContract().toString().equals("2019-04-11"))
I want to add/remove a style class to a table row based on a boolean in the row item.
Adding and removing the class works as expected with the following code. But when I click on the column header to reorder the table, the style sticks to the row id instead of the row item. Meaning if before ordering the first row was styled, after ordering the style is still on the first row instead of the row at the new position.
setRowFactory(table -> {
TableRow<PowerPlantPM> row = new TableRow<>() {
#Override
protected void updateItem(PowerPlantPM pp, boolean empty) {
super.updateItem(pp, empty);
if (!empty && pp != null) {
pp.savedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
getStyleClass().remove("unsaved");
} else {
getStyleClass().add("unsaved");
}
});
// the following binding works (including ordering), but is not what I want because of the ":selected" pseudo class
// styleProperty().bind(Bindings.when(pp.savedProperty()).then("").otherwise("-fx-background-color: #f2dede"));
}
}
};
return row;
});
I hope it is clear what I want to achieve. How do I get the style to stick to the row item when reordering?
A TableRow is reused as much as possible, in your updateItem you need to query the corresponding property, not add a listener to it. The listener will only fire if the property changes, but the TableRow may asked to redraw on a different position, or a different item.
protected void updateItem(PowerPlantPM pp, boolean empty) {
super.updateItem(pp, empty);
if (!empty && pp != null) {
if (!pp.isSaved()) {
getStyleClass().add("unsaved");
} else {
getStyleClass().remove("unsaved");
}
.....
}
}
Create your ObservableList with the properties it should watch with
ObservableListFX<PowerPlantPM> powerplants =
Collections.observableArrayList(pp -> new Observable[] { pp.savedProperty() });
This list will report changes on the items for the properties you returned in the Observable[].
You never unregister the listener from the old items. Also the listener is not called for the initial value of the property. Even if it was, your code could result in the same style class being added multiple times to a node. Furthermore cells may become empty. You need to remove the style class in that case too.
To avoid adding the same style class multiple times use a pseudoclass:
final PseudoClass unsaved = PseudoClass.getPseudoClass("unsaved");
setRowFactory(table -> {
TableRow<PowerPlantPM> row = new TableRow<>() {
private final ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> {
pseudoClassStateChanged(unsaved, !newValue);
};
#Override
protected void updateItem(PowerPlantPM pp, boolean empty) {
PowerPlantPM oldItem = getItem();
if (oldItem != null) {
// remove old listener
oldItem.savedProperty().removeListener(listener);
}
super.updateItem(pp, empty);
if (empty || pp == null) {
// remove pseudoclass from empty cell
pseudoClassStateChanged(unsaved, false);
} else {
// add new listener & handle initial value
pp.savedProperty().addListener(listener);
pseudoClassStateChanged(unsaved, pp.isSaved());
}
}
};
return row;
});
(Of course you need to adjust your CSS selectors to use :unsaved instead of .unsaved.)
There TreeView, each element of which implements Viewable interface:
public interface Viewable {
enum ViewStyle {
NEW("-fx-background-color: b8faa7;"),
NEW_PARENT("-fx-background-color: b8ebbb;"),
LOCKED("-fx-background-color: adadad; "),
HAS_NO_DATA("-fx-background-color: eb8d8d;");
String style;
ViewStyle(String style){
this.style = style;
}
public String getStyle() {
return style;
}
}
ViewStyle getViewStyle();
void setViewStyle(ViewStyle style);
StringProperty styleProperty();
String getTreeItemTitle();
void setTreeItemTitle(String title);
StringProperty titleProperty();
}
Each element has its own .styleProperty(), and get value from ViewStyle.getStyle()
This property bind for each TreeCell.styleProperty():
treeView.setCellFactory(new Callback<TreeView<Viewable>, TreeCell<Viewable>>() {
#Override
public TreeCell<Viewable> call(TreeView<Viewable> param) {
return new TreeCell<Viewable>() {
#Override
protected void updateItem(Viewable item, boolean empty) {
textProperty().unbind();
styleProperty().unbind();
if (empty || item == null) {
setGraphic(null);
textProperty().set(null);
styleProperty().set(null);
return;
}
if (item != null) {
styleProperty().bind(item.styleProperty());
textProperty().bind(item.titleProperty());
}
super.updateItem(item, empty);
}
};
}
});
The problem is that tree cells are displayed ugly in the selection. That is the color of the selected cell does not change. Changing only the color of the letters (in accordance with the default theme), but it is not very convenient. Therefore, probably it is necessary to attach .css files. At the same time, I don't understand how to change the style (default and when selected) of the cell depending on the current ViewStyle.
You could simply change the css property to one that is only used for unselected cells (-fx-control-inner-background):
enum ViewStyle {
NEW("-fx-control-inner-background: b8faa7;"),
NEW_PARENT("-fx-control-inner-background: b8ebbb;"),
LOCKED("-fx-control-inner-background: adadad; "),
HAS_NO_DATA("-fx-control-inner-background: eb8d8d;");
Also note that you did something you shouldn't do in a overwritten version of the updateItem method: Not always call super.updateItem. This can lead to the filled/empty pseudoclasses not being assigned correctly and the item property of the TreeCell not containing the item from the latest updateItem call. You should do something like this instead:
#Override
protected void updateItem(Viewable item, boolean empty) {
textProperty().unbind();
styleProperty().unbind();
if (empty || item == null) {
setText(null);
setStyle(null);
} else {
styleProperty().bind(item.styleProperty());
textProperty().bind(item.titleProperty());
}
super.updateItem(item, empty);
}
This question is related to this. Now I want to colour the row where field value equals to some value.
#FXML
private TableView<FaDeal> tv_mm_view;
#FXML
private TableColumn<FaDeal, String> tc_inst;
tc_inst.setCellValueFactory(cellData -> new SimpleStringProperty(""+cellData.getValue().getInstrumentId()));
tc_inst.setCellFactory(column -> new TableCell<FaDeal, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
// Style row where balance < 0 with a different color.
TableRow currentRow = getTableRow();
if (item.equals("1070")) {
currentRow.setStyle("-fx-background-color: tomato;");
} else currentRow.setStyle("");
}
}
});
The problem is I don't want to show tc_inst in my table. For this reason I set visible checkbox in SceneBuilder to false. In this case colouring part doesn't work at all. How can hide tc_inst so that colouring works?
Use a row factory, instead of a cell factory, if you want to change the color of the whole row:
tv_mm_view.setRowFactory(tv -> new TableRow<FaDeal>() {
#Override
public void updateItem(FaDeal item, boolean empty) {
super.updateItem(item, empty) ;
if (item == null) {
setStyle("");
} else if (item.getInstrumentId().equals("1070")) {
setStyle("-fx-background-color: tomato;");
} else {
setStyle("");
}
}
});
Note that if the value of instrumentId changes while the row is displayed, then the color will not change automatically with the above code, unless you do some additional work. The simplest way to make that happen would be to construct your items list with an extractor which returned the instrumentIdProperty() (assuming you are using the JavaFX property pattern in FaDeal).
I have a ComboBox which I'm populating with Sheet object values.
I set a Cell Factory in order to display the sheet's name in the drop down list itself. It works properly (seems so).
The problem is that after an item ("Cell") is selected, the value that is shown in the box is not the value that was shown in the list.
This is the relevant code part:
excelFile = new ExcelFile(file);
//ObservableList<String> sheets = FXCollections.observableArrayList(excelFile.getSheetsNames());
ObservableList<Sheet> sheets = FXCollections.observableArrayList(excelFile.getSheets());
sheetsBox.setItems(sheets);
sheetsBox.setDisable(false);
sheetsBox.setCellFactory(new Callback<ListView<Sheet>, ListCell<Sheet>>() {
#Override
public ListCell<Sheet> call(ListView<Sheet> param) {
return new ListCell<Sheet>() {
#Override
protected void updateItem(Sheet item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.getSheetName());
}
}
};
}
});
This is the problem (visually):
Thank you
The cell used to display the selected item is the buttonCell. So you just need to set the same cell for the button cell. You can factor the cell creation into a method to avoid replicating code:
sheetsBox.setCellFactory(lv -> createSheetCell());
sheetsBox.setButtonCell(createSheetCell());
// ...
private ListCell<Sheet> createSheetCell() {
return new ListCell<Sheet>() {
#Override
protected void updateItem(Sheet item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.getSheetName());
}
}
};
}