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);
}
Related
I am trying to create custom cells in a ListView , but every time I add a new item, the updateItem(TextFlow item, Boolean empty) is executed twice: one time it receives null and true, and the second time it does not (!null and false)
If I do not implement the setCellFactory method, then I can add the items to the table without problems.
ListView without custom cellFactory
However, when I do implement it, it simply creates 10 empty cells (where is the content?).
ListView with custom cellFactory
public class Controller implements Initializable {
#FXML
private ListView <TextFlow> console;
private ObservableList<TextFlow> data = FXCollections.observableArrayList();
public void initialize(URL location, ResourceBundle resources) {
console.setCellFactory(new Callback<ListView<TextFlow>, ListCell<TextFlow>>() {
#Override
public ListCell<TextFlow> call(ListView<TextFlow> param) {
return new ListCell<TextFlow>() {
#Override
protected void updateItem(TextFlow item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setItem(item);
setStyle("-fx-control-inner-background: blue;");
} else {
System.out.println("Item is null.");
}
}
};
}
});
for (int i = 0 ; i < 10; i++) {
Text txt = getStyledText("This is item number " + i + ".");
TextFlow textFlow = new TextFlow();
textFlow.getChildren().add(txt);
data.add(textFlow);
}
console.setItems(data);
}
private Text getStyledText (String inputText) {
Text text = new Text(inputText);
text.setFont(new Font("Courier New",12));
text.setFill(Paint.valueOf("#000000"));
return text;
}
}
updateItem can be called an arbitrary amount of times, different items may be passed and the cell can go from empty to non-empty and the other way round. ListView creates about as many cells as you see on screen and fills them with items. E.g. scrolling or modifications of the items list or resizing of the ListView can result in updates.
For this reason any cell needs to be able to deal with an arbitrary sequence of items (or null+empty) being passed to the updateItem method.
Furthermore you should avoid invoking setItem yourself, since super.updateItem does that already. Use setGraphic instead, if you want to display the item in the cell:
#Override
public ListCell<TextFlow> call(ListView<TextFlow> param) {
return new ListCell<TextFlow>() {
#Override
protected void updateItem(TextFlow item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setStyle("-fx-control-inner-background: blue;");
setGraphic(item);
} else {
setStyle(null);
setGraphic(null);
System.out.println("Item is null.");
}
}
};
}
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 have this problem with the first Hyperlink being alignment on top of TableCell.
I have tried almost everything and I could not get it to work.
colData.setCellFactory(e -> {
return new TableCell<TabelaShitjet, Hyperlink>(){
#Override
protected void updateItem(Hyperlink item, boolean empty) {
super.updateItem(item, empty);
if (!empty){
item.setOnAction(e -> {
TeDhenatBlerjes(Integer.parseInt(getTableView().getColumns().get(0).getCellData(getTableRow().getIndex())+""), item.getText());
});
setGraphic(item);
}
}
};
});
CONSTRUCTOR
public class TabelaShitjet {
private Hyperlink data;
public TabelaShitjet(String data){
this.data = new Hyperlink(data);
}
public Hyperlink getData() {
return data;
}
public void setData(Hyperlink data) {
this.data = data;
}
}
I've got no idea why exactly this happens, but if you remove the graphic when cell becomes empty by setting it to null, the problem seems to be fixed.
You should undo any modifications done to a cell when a item is added on a call of updateItem where the cell becomes empty anyway, since otherwise empty cells could be shown as if they were non-empty:
#Override
protected void updateItem(Hyperlink item, boolean empty) {
super.updateItem(item, empty);
if (!empty){
item.setOnAction(e -> {
TeDhenatBlerjes(Integer.parseInt(getTableView().getColumns().get(0).getCellData(getTableRow().getIndex())+""), item.getText());
});
}
// set graphic every time i.e. set it to null for empty cells
setGraphic(item);
}
In general the updateItem method should be implemented like this:
#Override
protected void updateItem(ItemType item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
// undo any updates that could have been made
// to make the cell look different from the empty cell
} else {
// update cell to display item
}
}
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());
}
}
};
}