JavaFX8 tree tabel view customize separate cells - javafx

In a tree-table-view there are child items that have in turn other child items. I need to customize, say, the text of certain cells of those pseudo-root items.
Is there a way to assign a css class/style to those items?
Update:
Ok, got it working with the following cellFactory:
treeTblColumnName.setCellFactory(new Callback<TreeTableColumn<FileModel, String>, TreeTableCell<FileModel, String>>() {
#Override
public TreeTableCell<FileModel, String> call(TreeTableColumn<FileModel, String> param) {
TreeTableCell<FileModel, String> cell = new TreeTableCell<FileModel, String>() {
#Override
protected void updateItem(String t, boolean bln) {
super.updateItem(t, bln); //To change body of generated methods, choose Tools | Templates.
System.out.println(this.getTableColumn().);
Label lbl = new Label(t);
lbl.setStyle("-fx-font-weight: bold; -fx-font-size: 14pt; -fx-text-fill: white;");
setGraphic(lbl);
}
};
return cell;
}
});
Now, how can I distinguish between classes? FileModel is an interface which is implemented by several classes. In this cellFactory I need to know what type is the current cell->item in order to apply different styling on it.
Thanks.

You can use instanceof:
FileModel fileModel = getTreeTableRow().getItem();
if(fileModel instanceof ExeFileModel){
//set style here
} else if(fileModel instanceof TxtFileModel){
//use another style here
}

Related

Various Colours In The Same Table Cell, Possible?

I have a request to display a string in various colours in a table cell, that is one portion of a string in one colour and the rest in another colour (either the background or the text). I have found an article on changing the cell background colour, but not a portion of a cell. That is close to the requirement, but don't meet the requirement.
The only possible solution, I can think of, is to use the Text type which can be set with various colours after splitting a string into two parts. But, how to use the Text type data with the TableView setup as the following?
aColumn.setCellValueFactory(p -> new SimpleStringProperty(...) );
...
aTalbeView.setItems(FXcollections.observableArrayList(...));
I am still new to JavaFX. Is it doable? If so, how shall I approach a solution?
A mock up table is attached below.
The cellValueFactory is used to tell the cell what data to display. To tell the cell how to display its data, use a cellFactory. The two are more or less independent.
So you can do
aColumn.setCellValueFactory(p -> new SimpleStringProperty(...));
and then something like:
aColumn.setCellFactory(tc -> new TableCell<>() {
private final String[] palette = new String[] {
"#1B9E77", "#D95F02", "#7570B3", "#E7298A",
"#66A61E", "#E6AB02", "#A6761D", "#666666" };
private TextFlow flow = new TextFlow();
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
flow.getChildren().clear();
int i = 0 ;
for (String word : item.split("\\s")) {
Text text = new Text(word);
text.setFill(Color.web(palette[i++ % palette.length]);
flow.getChildren().add(text);
flow.getChildren().add(new Text(" "));
}
setGraphic(flow);
}
}
});
This assumes each cell has multiple words (separated by whitespace) and colors each word a different color. You can implement the different colors any way you like; this shows the basic idea.
The approach used in this answer
An additional range parameter is added to the backing model to indicate the highlight range for text in the cell.
The cellValueFactory uses a binding statement to allow the cell to respond to updates to either the text in the cell or the highlight range.
Labels within an HBox are used for the cell graphic rather than a
TextFlow as labels have more styling options (e.g. for text
background) than text nodes in TextFlow.
Using multiple labels within the cells does change some of the eliding behavior of the cell when not enough room is available in the column to include all text, this could be customized by setting properties on the HBOX or label to configure this behavior how you want.
CSS stylesheet for styling is included in the code but could be
extracted to a separate stylesheet if desired.
I didn't thoroughly test the solution, so there may be logic errors around some of the boundary conditions.
Screenshots
Highlighted a subset of text within a cell in a non-selected row:
Highlighted a subset of text within a cell in a selected row:
Example code
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HighlightedTextTableViewer extends Application {
private static final String CSS_DATA_URL = "data:text/css,";
private static final String HIGHLIGHTABLE_LABEL_CSS = CSS_DATA_URL + // language=CSS
"""
.highlightable {
-fx-font-family: monospace;
-fx-font-weight: bold;
}
.highlight {
-fx-background-color: cornflowerblue;
-fx-text-fill: white;
}
""";
private static final String HIGHLIGHTABLE_STYLE_CLASS = "highlightable";
private static final String HIGHLIGHTED_STYLE_CLASS = "highlight";
#Override
public void start(Stage stage) {
TableView<Field> table = createTable();
populateTable(table);
VBox layout = new VBox(
10,
table
);
layout.setPadding(new Insets(10));
layout.setPrefHeight(100);
stage.setScene(new Scene(layout));
stage.show();
}
private TableView<Field> createTable() {
TableView<Field> table = new TableView<>();
TableColumn<Field, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(
p -> p.getValue().nameProperty()
);
TableColumn<Field, Field> valueColumn = new TableColumn<>("Value");
valueColumn.setCellValueFactory(
p -> Bindings.createObjectBinding(
p::getValue,
p.getValue().valueProperty(), p.getValue().highlightRangeProperty()
)
);
valueColumn.setCellFactory(param -> new HighlightableTextCell());
//noinspection unchecked
table.getColumns().setAll(nameColumn, valueColumn);
return table;
}
public static class HighlightableTextCell extends TableCell<Field, Field> {
protected void updateItem(Field item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty || item.getValue() == null) {
setGraphic(null);
} else {
setGraphic(constructTextBox(item));
}
}
private Node constructTextBox(Field item) {
HBox textBox = new HBox();
textBox.getStylesheets().setAll(HIGHLIGHTABLE_LABEL_CSS);
textBox.getStyleClass().add(HIGHLIGHTABLE_STYLE_CLASS);
int from = item.getHighlightRange() != null ? item.getHighlightRange().from() : -1;
int valueLen = item.getValue() != null ? item.getValue().length() : -1;
int to = item.getHighlightRange() != null ? item.getHighlightRange().to() : -1;
if (item.highlightRangeProperty() == null
|| from >= to
|| from > valueLen
) { // no highlight specified or no highlight in range.
textBox.getChildren().add(
createStyledLabel(
item.getValue()
)
);
} else {
textBox.getChildren().add(
createStyledLabel(
item.getValue().substring(
0,
from
)
)
);
if (from >= valueLen) {
return textBox;
}
textBox.getChildren().add(
createStyledLabel(
item.getValue().substring(
from,
Math.min(valueLen, to)
), HIGHLIGHTED_STYLE_CLASS
)
);
if (to >= valueLen) {
return textBox;
}
textBox.getChildren().add(
createStyledLabel(
item.getValue().substring(
to
)
)
);
}
return textBox;
}
private Label createStyledLabel(String value, String... styleClasses) {
Label label = new Label(value);
label.getStyleClass().setAll(styleClasses);
return label;
}
}
private void populateTable(TableView<Field> table) {
table.getItems().addAll(
new Field("Dragon", "93 6d 6d da", null),
new Field("Rainbow", "0c fb ff 1c", new Range(3, 8))
);
}
}
class Field {
private final StringProperty name;
private final StringProperty value;
private final ObjectProperty<Range> highlightRange;
public Field(String name, String value, Range highlightRange) {
this.name = new SimpleStringProperty(name);
this.value = new SimpleStringProperty(value);
this.highlightRange = new SimpleObjectProperty<>(highlightRange);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getValue() {
return value.get();
}
public StringProperty valueProperty() {
return value;
}
public void setValue(String value) {
this.value.set(value);
}
public Range getHighlightRange() {
return highlightRange.get();
}
public ObjectProperty<Range> highlightRangeProperty() {
return highlightRange;
}
public void setHighlightRange(Range highlightRange) {
this.highlightRange.set(highlightRange);
}
}
record Range(int from, int to) {}
Alternative using TextField
An alternative to the HBox for displaying highlighted text would be to use a TextField (non-editable), which allows a selection to be set (via APIs on the text field), however, I did not attempt a solution with a TextField approach. A TextField may allow a user to drag the mouse to select text (perhaps could be disabled if desired by making the field mouse transparent).
Related Questions (uses TextFlow)
Highlight text in TableView with TextFlow
JavaFX TableView with highlighted text
JavaFX: setting background color for Text controls

javafx CheckBoxTreeItem update parents programmatically

I have to retrieve some data from my database to dynamically create a TreeView and select some CheckBoxTreeItems from this TreeView. This TreeView represents permissions to a menu structure.
My doubt is when I create the TreeView and select specific items from the Tree according to the user's permissions programmatically, the parents items don't have any status change (selected or indeterminate). But when I select any item directly from the interface, the parents get updated.
For example, here I have my screen when I select the items programmatically:
You can see that I have two menu items selected, but the parents aren't.
On this image, I have selected the same menu items using the screen, and the parents were updated with indeterminate status or selected if I select all children inside the submenu.
I have gone through the documentation, google and here on Stack Overflow, but only found examples to update the children.
Is there a way to update the parents programmatically or to call the event executed from the screen when an item is selected?
EDIT:
All items from the Tree have the independent property set to false.
I came with a workaround for this problem.
I had to first create all the TreeView structure, and change the selected property after using this code snippet:
Platform.runLater(new Runnable() {
#Override
public void run() {
selectItems();
}
});
Here is the code to verify the TreeItems:
private void selectItems(){
TreeItem root = tree.getRoot();
if (root != null) {
selectChildren(root);
}
}
private void selectChildren(TreeItem<TesteVO> root){
for(TreeItem<TesteVO> child: root.getChildren()){
// HERE I CHECK IF THE USER HAS PERMISSION FOR THE MENU ITEM
// IF SO, I CHANGE THE SELECTED PROPERTY TO TRUE
if (child.getValue().id == 4) {
((CheckBoxTreeItem) child).setSelected(true);
}
// IF THERE ARE CHILD NODES, KEEP DIGGING RECURSIVELY
if(!child.getChildren().isEmpty()) {
selectChildren(child);
}
}
}
If there is a simpler way, please let me know!
This is not the case. Parent items do get automatically get set to the indeterminate state when you select a child item. I'm not sure if this is something that got corrected from the time that this question was posted, probably not.
My guess is that there's a programming bug in how the node was selected or how the TableView was constructed and initialized.
Here's some code that shows what I'm doing, and it works! In my case, I'm using a CheckBoxTreeItem<File> for the TreeItem.
How the treeview was created
treeView = new TreeView(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
CheckBoxTreeItem<File> node = (CheckBoxTreeItem<File>)t1;
if (node.getValue() != currentFile) {
setFileDetail(node);
showChildren(node);
}
}
});
treeView.setCellFactory(new CallBackWrapper());
treeView.setShowRoot(false);
Below show the CallBackWrapper class.
private class CallBackWrapper implements Callback<TreeView<File>, TreeCell<File>> {
Callback<TreeView<File>, TreeCell<File>> theCallback;
private CallBackWrapper() {
theCallback = CheckBoxTreeCell.<File>forTreeView(getSelectedProperty, converter);
}
#Override
public TreeCell<File> call(TreeView<File> fileTreeView) {
return theCallback.call(fileTreeView);
}
final Callback<TreeItem<File>, ObservableValue<Boolean>> getSelectedProperty = (TreeItem<File> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>) item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<File>> converter = new StringConverter<TreeItem<File>>() {
#Override
public String toString(TreeItem<File> object) {
File item = object.getValue();
return fileSystemView.getSystemDisplayName(item);
}
#Override
public TreeItem<File> fromString(String string) {
return new TreeItem<File>(new File(string));
}
};
}
And lastly here some code that the selection was made in:
boolean selectNode(CheckBoxTreeItem<File> parentNode, String name) {
Object[] children = parentNode.getChildren().toArray();
for (Object child : children) {
CheckBoxTreeItem<File> childItem = (CheckBoxTreeItem<File>) child;
if (name.equals(childItem.getValue().getName())) {
childItem.setSelected(true);
//treeView.getSelectionModel().select(child); <-- this does not work!
return true;
}
}
return false;
}

JavaFX : color individual TreeItems using css

I want to be able to color individual Tree Item of treeView based on some condition.
This answer seems good but I am unable to implement it.
https://stackoverflow.com/a/10931896/6653207
I am unable to understand how to use setCellFactory method to format individual TreeItems.
I have a class
public class Bag {
public String caption,assignment="";
Boolean eval;
public Set<Vertex> Nodes = new HashSet<Vertex>();
public Vector<Bag> ChildBags = new Vector<Bag>();
#Override
public String toString()
{
return assignment+ " " +caption;
}
}
Here's my css file:
.true{
-fx-text-fill:#33cc00 ;
}
.assignment{
-fx-text-fill: #0033cc
}
So I want to color to green the caption ( the toString() method returns ) of all those nodes whose eval property is true.
and assignment string which toString() method returns for all the nodes should be blue.
How can I do so?
Thanks.
By overriding the updateItem method of the TreeCell, you can adjust the TreeCell's properties based on the value of the TreeItem the cell contains.
In the following example a pseudoclass is assigned to all cells that contain a value with the prefix "child" and all empty cells get a black background.
TreeView<String> treeView = ...
PseudoClass childPseudoClass = PseudoClass.getPseudoClass("child");
treeView.setCellFactory(tv -> new TreeCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
// update for empty cell / cell containing null
pseudoClassStateChanged(childPseudoClass, false);
setText("");
setStyle("-fx-background-color: black;");
} else {
// update for filled cell
pseudoClassStateChanged(childPseudoClass, item.startsWith("child"));
setText(item);
setStyle(null);
}
}
});
CSS Stylesheet
.tree-cell:child {
-fx-background-color: red;
}
The updateItem method is called by the TreeView every time the value changes, e.g. if a new TreeItem is associated with the cell or the value property of a TreeItem is modified.
You could also use the factory to add listeners to the TreeCell, before it's returned, in case you prefer this and e.g. want to change the cell based on the treeItem property.
EDIT: To apply different colors to the text, you need to use different Nodes for the text parts.
treeView.setCellFactory(tv -> new TreeCell<Bag>() {
private final Text assignment;
private final Text caption;
private final Node graphic;
{
assignment = new Text();
caption = new Text();
assignment.getStyleClass().add("assignment");
graphic = new HBox(4, assignment, caption);
setGraphic(graphic);
}
#Override
protected void updateItem(Bag item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
setGraphic(graphic);
assignment.setText(item.assignment);
caption.setText(item.caption);
caption.getStyleClass().remove("true");
if (item.eval) {
caption.getStyleClass().add("true");
}
}
}
});
To color the text you need to use the -fx-fill property instead of the -fx-text-fill property.

JavaFX Changing cell to icon

I have an issue with changing cell to an icon with the following code:
TableColumn typeCol = new TableColumn("Account Type");
typeCol.setCellFactory(new Callback<TableColumn<Account, String>, TableCell<Account, String>>() {
#Override
public TableCell<Account, String> call(TableColumn<Account, String> param) {
TableCell<Account,String> cell = new TableCell<Account, String>(){
#Override
public void updateItem(Account item, boolean empty){
if (item != null){
VBox vb = new VBox();
vb.setAlignment(Pos.CENTER);
ImageView imgVw = new ImageView(item.getTypeIcon());
imgVw.setFitHeight(10);
imgVw.setFitWidth(10);
vb.getChildren().addAll(imgVw);
setGraphic(vb);
}
}
};
return cell;
}
});
typeCol.setMinWidth(100);
typeCol.setCellValueFactory(
new PropertyValueFactory<Account, String>("type"));
The issue here is that for some reason I get en error of 'method does not override or implement a method form a supertype'. Any idaes?
TableCell<S, T> extends Cell<T>, not Cell<S>. Therefore the correct signature for the updateItem method of TableCell<Account, String> is
public void updateItem(String item, boolean empty)
Assuming your cellValueFactory
new PropertyValueFactory<Account, String>("type")
returns ObservableValues containing the URLs of images, you can use
ImageView imgVw = new ImageView(item);
instead of
ImageView imgVw = new ImageView(item.getTypeIcon());
Since the value passed to the updateItem method is the one that is contained in the ObservableValue returned by the cellValueFactory.
Sample code for placing an image in a table cell:
import javafx.scene.control.*;
import javafx.scene.image.*;
public class ImageTableCell<S> extends TableCell<S, Image> {
private final ImageView imageView = new ImageView();
public ImageTableCell() {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageView.setImage(null);
setText(null);
setGraphic(null);
}
imageView.setImage(item);
setGraphic(imageView);
}
}
This will work fine if your table doesn't represent millions of items. If you have many items and can't hold all of the potential images in memory, then you would need a TableCell instead of TableCell where the string is just the URL of the image rather than the actual image data itself. Then you would keep an LRU cache of image data which you would update in the updateItem, fetching the image data from the cache if it was there, otherwise loading it from the url. Such an approach could get a little tricky as you would probably want to be careful not to do too much dynamic loading of images as the user scrolls. In general, if you just have a few hundred or thousand thumbnail images, then the straight-forward approach defined in the code above would suffice rather than the alternate cache based approach.

JavaFX8 Style not immediately updating

I am trying to change the color of the table rows when I set a boolean.
So I have this code:
boolean searchmode = false;
....
columns.forEach(c -> c.setCellFactory(column -> {
return new TableCell<ShowableInWarenkorb, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
if (searchmode) {
getStyleClass().add("searchmode");
} else{
getStyleClass().remove("searchmode");
}
}
};
}));
This CSS:
.searchmode {
-fx-background-color: rgba(153,153,153,0.3);
})
And then I switch searchmode eventually in my code before I am updating the table contents.
But the color does not change immediatley, sometimes I have to click a little bit around before it changes, how can I trigger it manually?
From your code, it looks like you want to apply this to all cells in the table. You can do this without a cell factory at all (though you may need one for other purposes).
Do
PseudoClass searchmodePseudoClass = PseudoClass.getPseudoClass("searchmode");
and then when you change the value of searchmode, do
table.pseudoClassStateChanged(searchmode);
In your css, do
.table-view:searchmode .table-cell {
-fx-background-color: rgba(153,153,153,0.3);
}
If you want to "automate" the update to the pseudoclass state, use a boolean property and add a listener:
private final BooleanProperty searchmode = new SimpleBooleanProperty(false);
public final boolean isSearchmode() {
return searchmodeProperty().get();
}
public final void setSearchmode(boolean searchmode) {
searchmodeProperty().set(searchmode);
}
public BooleanProperty searchmodeProperty() {
return searchmode ;
}
Then if you add the listener
searchmode.addListener((obs, wasSearchmode, isNowSearchmode) ->
table.pseudoClassStateChanged(searchmodePseudoClass, isNowSearchmode));
everything will be wired automatically so the table changes whenever you call setSearchmode(...).

Resources