JavaFx treetableview setStyle for rows with NO children - javafx

In TreeTableView I need to find and setStyle rows with NO children.
In below code example, problematic code is in method: markRows.
public class Controller {
public TreeTableView<MyTreeObject> fuses_ttv;
private ArrayList<MyTreeObject> data = new ArrayList<>();
private void createTreeTableView(){}
private void markRows(){
fuses_ttv.setRowFactory(row -> new TreeTableRow<MyTreeObject>(){
#Override
protected void updateItem(MyTreeObject item, boolean empty) {
super.updateItem(item, empty);
if (item==null){
setStyle(null);
} else if (item.getType().equals("FRC")){
setStyle("-fx-background-color: lightslategray;");
} else if(item.getType().equals("wire")){
setStyle("-fx-background-color: lightyellow;");
} //***** else if (ROW HAS NOW CHILDREN) - HOW TO DO IT????? ******
}
});
}
}
Like in picture below - rows with SLOT "A1" and "A2" have no children.
How to identify such rows?
Thanks in advance for any help.

In JavaFX 19 and later you can do:
fuses_ttv.setRowFactory(row -> new TreeTableRow<MyTreeObject>(){
{
treeItemProperty().flatMap(TreeItem::leafProperty)
.orElse(false)
.addListener((obs, wasLeaf, isLeaf) -> {
if (isLeaf) {
// set style for leaf (no children)
} else {
// set style for non-leaf (has children)
}
});
}
#Override
protected void updateItem(MyTreeObject item, boolean empty) {
super.updateItem(item, empty);
if (item==null){
setStyle(null);
} else if (item.getType().equals("FRC")){
setStyle("-fx-background-color: lightslategray;");
} else if(item.getType().equals("wire")){
setStyle("-fx-background-color: lightyellow;");
} //***** else if (ROW HAS NOW CHILDREN) - HOW TO DO IT????? ******
}
});
I would actually recommend setting custom PseudoClasses, and an external style sheet, instead of using inline styles.
Here is a complete working example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TreeTableStyleExample extends Application {
private int itemCount ;
#Override
public void start(Stage stage) throws IOException {
TreeTableView<Integer> table = new TreeTableView<>();
TreeTableColumn<Integer, Number> column = new TreeTableColumn<>("Item");
table.getColumns().add(column);
column.setCellValueFactory(data -> new SimpleIntegerProperty(data.getValue().getValue()));
column.setCellFactory(ttv -> new TreeTableCell<>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
} else {
setText("Item "+item);
}
}
});
PseudoClass leaf = PseudoClass.getPseudoClass("leaf");
PseudoClass odd = PseudoClass.getPseudoClass("odd-value");
PseudoClass even = PseudoClass.getPseudoClass("even-value");
table.setRowFactory( ttv -> new TreeTableRow<>() {
{
treeItemProperty().flatMap(TreeItem::leafProperty).orElse(false)
.addListener((obs, wasLeaf, isNowLeaf) -> pseudoClassStateChanged(leaf, isNowLeaf));
}
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
pseudoClassStateChanged(odd, false);
pseudoClassStateChanged(even, false);
} else {
pseudoClassStateChanged(odd, item % 2 == 1);
pseudoClassStateChanged(even, item % 2 == 0);
}
}
});
table.setRoot(buildTable(20));
Button add = new Button("Add item");
add.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
add.setOnAction(e -> {
TreeItem<Integer> treeItem = new TreeItem<>(++itemCount);
treeItem.setExpanded(true);
table.getSelectionModel().getSelectedItem().getChildren().add(treeItem);
});
Button remove = new Button("Remove");
remove.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems())
.or(Bindings.equal(table.getSelectionModel().selectedItemProperty(), table.getRoot())));
remove.setOnAction(e -> {
TreeItem<Integer> selection = table.getSelectionModel().getSelectedItem();
selection.getParent().getChildren().remove(selection);
});
HBox controls = new HBox(5, add, remove);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(table);
root.setBottom(controls);
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private TreeItem<Integer> buildTable(int numItems) {
Random rng = new Random();
TreeItem<Integer> root = new TreeItem<>(1);
root.setExpanded(true);
List<TreeItem> items = new ArrayList<>();
items.add(root);
for (itemCount = 2; itemCount <= numItems ; itemCount++) {
TreeItem<Integer> item = new TreeItem<>(itemCount);
item.setExpanded(true);
items.get(rng.nextInt(items.size())).getChildren().add(item);
items.add(item);
}
return root ;
}
public static void main(String[] args) {
launch();
}
}
with style.css:
.tree-table-row-cell:odd-value {
-fx-background: lightslategray ;
}
.tree-table-row-cell:even-value {
-fx-background: lightyellow;
}
.tree-table-row-cell:leaf {
-fx-background: lightgreen ;
}
Sample output:

Related

How to set text and color of multiple ListView items when clicked

I know there are many related questions about this but maybe I'm missing something because I can't get the behavior I'm expecting, to work.
#FXML
private ListView<String> guiList;
void performAction(Actions action) {
try {
Task<String> task = new Task<>() {
#Override
public String call() {
String mySelection = Context.getInstance().getSelected();
ArrayList<String> selectedList = Context.getInstance().getItemsClicked();
if (selectedList == null) {
selectedList = new ArrayList<>();
}
selectedList.add(mySelection);
Context.getInstance().setItemsClicked(selectedList);
guiList.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param) {
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(item != null && item.matches(mySelection)) {
setText(mySelection + " [" + action + "]");
setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize()));
setStyle("-fx-text-fill: green;");
} else {
setText(item);
}
}
};
return cell;
}
});
return "";
}
};
} catch (Exception e) {
}
}
When I click in an item of guiList, the text is changed, gets bold and shows in green color but I don't understand why I need the else statement. If I don't use it, all the other items of the list disappear.
I ask this because I want to change ALL of the items I click and in the current behavior, the changes are only made in the last one clicked.
Here is on approach. Use an object that has a Boolean variable to keeps up with if the item has been selected.
KeyCode 1
lvMain.getSelectionModel().selectedItemProperty().addListener(((ov, t, t1) - > {
if (t1 != null) {
t1.setSelected(true);
}
}));
Key Code 2
lvMain.setCellFactory(lv - > new ListCell < MyItem > () {
#Override
public void updateItem(MyItem item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item.getText());
if (item.isSelected()) {
setTextFill(Color.RED);
}
}
}
});
Main
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class App extends Application {
#Override
public void start(Stage primaryStage) {
ListView<MyItem> lvMain = new ListView();//Create ListView
lvMain.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);//Change ListView selection mode to multiple
ObservableList<MyItem> items = FXCollections.observableArrayList(new MyItem("Julia"), new MyItem("Ian"), new MyItem("Sue"), new MyItem("Matthew"), new MyItem("Hannah"));//ObseravableList that will be used to set the ListView
lvMain.setItems(items);//Set the ListView's items
lvMain.setCellFactory(lv -> new ListCell<MyItem>()
{
#Override
public void updateItem(MyItem item, boolean empty)
{
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
}
else {
setText(item.getText());
if(item.isSelected())
{
setTextFill(Color.RED);
}
}
}
});
lvMain.getSelectionModel().selectedItemProperty().addListener(((ov, t, t1) -> {
if(t1 != null)
{
t1.setSelected(true);
}
}));
VBox vbox = new VBox();
vbox.getChildren().addAll(lvMain);
StackPane root = new StackPane();
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
MyItem
/**
*
* #author Sed
*/
public class MyItem {
private String text;
private boolean selected;
public MyItem(String text) {
this.text = text;
this.selected = false;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean isSelected) {
this.selected = isSelected;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Output
I think a better solution would be to use the ListView's built in multiple selection or have your cells have a ToggleButton. When the ToggleButton is on, change the color of the text. When it is off, change the color back to it's original state.

JavaFx TableCellEditor

I have JavaFx TableView and I permit user to enter some data in the table and retrieve what a user entered in the table for this reason I create also ArrayList of TextFields and use the following code but the size of the ArrayList should be 3 in my case but I found the size 7, what's wrong?
Edit the full code here
import java.util.ArrayList;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TestTableView extends Application
{
private final TableView <ProductOpeningBalance> tableProductOpeningBalance = new TableView();
private final Scene scene = new Scene(tableProductOpeningBalance, 400, 200);
private final TableColumn productQuantity = new TableColumn("Product Quantity");
private final ObservableList <ProductOpeningBalance> data = FXCollections.observableArrayList(
new ProductOpeningBalance("0"),
new ProductOpeningBalance("0"));
private final ArrayList <TextField> txtProductQuantity = new ArrayList <> ();
#Override
public void start(Stage stage)
{
productQuantity.impl_setReorderable(false);
productQuantity.setEditable(true);
productQuantity.setCellValueFactory(new PropertyValueFactory("ProductQuantity"));
productQuantity.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
super.startEdit();
createTextField();
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
txtProductQuantity.get(txtProductQuantity.size() - 1).selectAll();
}
}
#Override
public void cancelEdit()
{
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(empty)
{
setText(null);
setGraphic(null);
}
else
{
if(isEditing())
{
if(txtProductQuantity.get(txtProductQuantity.size() - 1) != null)
{
txtProductQuantity.get(txtProductQuantity.size() - 1).setText(getString());
}
setText(null);
setGraphic(txtProductQuantity.get(txtProductQuantity.size() - 1));
}
else
{
setText(getString());
setGraphic(null);
}
}
}
private void createTextField()
{
txtProductQuantity.add(new TextField(getString()));
txtProductQuantity.get(txtProductQuantity.size() - 1).
setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
txtProductQuantity.get(txtProductQuantity.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
txtProductQuantity.get(txtProductQuantity.size() - 1).focusedProperty().
addListener((ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) ->
{
if(!arg2)
{
commitEdit(txtProductQuantity.get(txtProductQuantity.size() - 1).getText());
}
});
}
private String getString()
{
return getItem() == null ? "" : getItem().toString();
}
});
tableProductOpeningBalance.setEditable(true);
tableProductOpeningBalance.getColumns().addAll(productQuantity);
tableProductOpeningBalance.setItems(data);
stage.setScene(scene);
stage.show();
}
public class ProductOpeningBalance
{
private final SimpleStringProperty ProductQuantity;
public ProductOpeningBalance(String productQuantity)
{
this.ProductQuantity = new SimpleStringProperty(productQuantity);
}
public void setProductQuantity(String productQuantity)
{
ProductQuantity.set(productQuantity);
}
public String getProductQuantity()
{
return ProductQuantity.get();
}
}
}
Solution finally:
this code help me to find what I need after spent a lot of time in searching and trying a lot of methods
purchaseItemPrice.setCellFactory(column -> new TableCell()
{
#Override
public void startEdit()
{
if(!isEmpty())
{
if(txtPurchaseItemPrice.size() < data.size() && getGraphic() == null)
{
super.startEdit();
txtPurchaseItemPrice.add(new TextField());
txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1).setAlignment(Pos.BASELINE_RIGHT);
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(!empty)
{
if(isEditing())
{
setGraphic(txtPurchaseItemPrice.get(txtPurchaseItemPrice.size() - 1));
}
}
}
});

JavaFX: Make comboBox cell to change disable state dynamically

Except for the first item, all items of a comboBox are initially disabled (I used setCellFactory to accomplish this).
If I click on the option 0, I want for it to unlock option 1 and so on.
I tried to use some boolean variables inside a comboBox Listener but it seems like the setCellFactory is called only once. Is this correct?
If so, how could I achieve what I want?
SSCCE below adapted from here
Main.java
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
public class Main extends Application {
boolean isZeroLocked = false;
boolean isOneLocked = true;
boolean isTwoLocked = true;
boolean isThreeLocked = true;
#Override
public void start(Stage stage) throws Exception {
ComboBox<Integer> box = new ComboBox<Integer>();
ObservableList<Integer> values = FXCollections.observableArrayList(0,1,2,3);
box.setItems(values);
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
System.out.println(newValue + " was clicked. The next option will be unlocked.");
if(newValue.intValue() == 0)
isOneLocked = false;
if(newValue.intValue() == 1)
isTwoLocked = false;
if(newValue.intValue() == 2)
isThreeLocked = false;
});
box.setCellFactory(lv -> new ListCell<Integer>() {
#Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
if(item.intValue() == 0)
setDisable(isZeroLocked);
if(item.intValue() == 1)
setDisable(isOneLocked);
if(item.intValue() == 2)
setDisable(isTwoLocked);
if(item.intValue() == 3)
setDisable(isThreeLocked);
}
}
});
Scene scene = new Scene(new Group(box));
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
application.css
.combo-box-popup .list-cell:disabled {
-fx-opacity: 0.4 ;
}
I created an Object called CustomNumber to keep up with the disabled property.
Key code:
This code sets the ComboBox's text and enables its cell.
Callback<ListView<CustomNumber>, ListCell<CustomNumber>> factory = lv -> new ListCell<CustomNumber>() {
#Override
protected void updateItem(CustomNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
System.out.println(item.getNum());
setText(Integer.toString(item.getNum()));
setDisable(item.isDisable());
}
}
};
This code gets the cell below the clicked cell and updates its disable property
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
if(newValue.intValue() + 1 < box.getItems().size())
{
CustomNumber tempCustomNumber = (CustomNumber)box.getItems().get(newValue.intValue() + 1);
tempCustomNumber.setDisable(false);
System.out.println(tempCustomNumber.getNum() + " " + tempCustomNumber.isDisable() + " was unlocked.");
box.getItems().set(newValue.intValue() + 1, tempCustomNumber);
}
});
Full Code:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<CustomNumber> box = new ComboBox();
List<CustomNumber> customNumbers = new ArrayList();
customNumbers.add(new CustomNumber(0, false));
customNumbers.add(new CustomNumber(1, true));
customNumbers.add(new CustomNumber(2, true));
customNumbers.add(new CustomNumber(3, true));
ObservableList<CustomNumber> values = FXCollections.observableArrayList(customNumbers);
box.setItems(values);
Callback<ListView<CustomNumber>, ListCell<CustomNumber>> factory = lv -> new ListCell<CustomNumber>() {
#Override
protected void updateItem(CustomNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
System.out.println(item.getNum());
setText(Integer.toString(item.getNum()));
setDisable(item.isDisable());
}
}
};
box.setCellFactory(factory);
box.setButtonCell(factory.call(null));
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
if(newValue.intValue() + 1 < box.getItems().size())
{
CustomNumber tempCustomNumber = (CustomNumber)box.getItems().get(newValue.intValue() + 1);
tempCustomNumber.setDisable(false);
System.out.println(tempCustomNumber.getNum() + " " + tempCustomNumber.isDisable() + " was unlocked.");
box.getItems().set(newValue.intValue() + 1, tempCustomNumber);
}
});
Scene scene = new Scene(new Group(box));
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
CustomNumber
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxapplication9;
/**
*
* #author sedrick
*/
public class CustomNumber {
private int num;
private boolean disable;
public CustomNumber(int num, boolean disable) {
this.num = num;
this.disable = disable;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isDisable() {
return disable;
}
public void setDisable(boolean isDisable) {
this.disable = isDisable;
}
}

Automatically updating/styling TreeView's TreeCells when BooleanProperty is true in seperate TableView

What's the easiest way to have a TreeView's cells auto-refresh with a new style when a condition is met in a separate TableView?
I'm currently setting the TreeCells' styles in the updateItem() method in the TreeView cell factory, but this only fires off if the user adds or removes something in the TreeView. I want to be able to change the style of a given TreeCell if I check off all 3 checkboxes in a separate dialog box.
I'm currently able to monitor the number of checked checkboxes with a BooleanProperty and an IntegerProperty, but I have no idea how I'm supposed to "auto-update" or call a TreeView refresh when a TreeItem's Object's BooleanProperty changes.
Any help is greatly appreciated.
You can set the style in the TreeCell whenever a boolean property on the value underlying the TreeCell is updated (via a binding).
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
Full Sample
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeViewSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ObservableList<Message> messages = FXCollections.observableArrayList();
TreeItem<Message> rootItem = new TreeItem<> (new Message("Inbox"));
rootItem.setExpanded(true);
for (int i = 1; i < 6; i++) {
Message message = new Message("Message" + i);
messages.add(message);
TreeItem<Message> item = new TreeItem<> (message);
rootItem.getChildren().add(item);
}
TreeView<Message> tree = new TreeView<> (rootItem);
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
}
});
TableView<Message> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Message, String> textCol = new TableColumn<>("Text");
textCol.setCellValueFactory(new PropertyValueFactory<>("text"));
tableView.getColumns().add(textCol);
TableColumn<Message, Boolean> readCol = new TableColumn<>("Read");
readCol.setCellValueFactory(new PropertyValueFactory<>("read"));
readCol.setCellFactory(CheckBoxTableCell.forTableColumn(readCol));
readCol.setEditable(true);
tableView.getColumns().add(readCol);
tableView.setItems(messages);
VBox root = new VBox(10, tree, tableView);
root.setPadding(new Insets(10));
stage.setScene(new Scene(root, 300, 250));
stage.show();
}
public class Message {
private StringProperty text = new SimpleStringProperty();
private BooleanProperty read = new SimpleBooleanProperty(false);
public Message(String msgText) {
text.set(msgText);
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
public boolean isRead() {
return read.get();
}
public BooleanProperty readProperty() {
return read;
}
public void setRead(boolean read) {
this.read.set(read);
}
}
}
I'm trying to the bind the graphicProperty to the same BooleanProperty and change the image based on the value.
Example using a binding of an Image within an ImageView associated with the cell.
Image unreadImage = new Image("http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png");
Image readImage = new Image("http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png");
. . .
return new TreeCell<Message>() {
ImageView imageView = new ImageView();
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
imageView.imageProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
setGraphic(null);
styleProperty().set(null);
} else {
setText(item.getText());
setGraphic(imageView);
imageView.imageProperty().bind(
Bindings.when(
item.readProperty()
).then(readImage)
.otherwise(unreadImage)
);
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
An alternate (and possibly preferable) way to handle this from above is to instead get the style class or psuedoclass of the cell and update that based upon the boolean property. Then define the style in a separate CSS stylesheet. The output of the sample below is the same as the graphic based sample above.
mail.css
.readable:read {
-fx-background-color: red;
-fx-graphic: url(
"http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png"
);
}
.readable:unread {
-fx-graphic: url(
"http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png"
);
}
Pseudo-class based code snippet:
PseudoClass READ_PSEUDO_CLASS = PseudoClass.getPseudoClass("read");
PseudoClass UNREAD_PSEUDO_CLASS = PseudoClass.getPseudoClass("unread");
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
private ChangeListener<Boolean> readChangeListener = (observable, oldValue, newValue) -> {
pseudoClassStateChanged(READ_PSEUDO_CLASS, newValue);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !newValue);
};
Message priorItem = null;
{
getStyleClass().add("readable");
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
if (priorItem != null) {
priorItem.readProperty().removeListener(readChangeListener);
}
priorItem = item;
if (empty || item == null || item.getText() == null) {
setText(null);
pseudoClassStateChanged(READ_PSEUDO_CLASS, false);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, false);
} else {
item.readProperty().addListener(readChangeListener);
setText(item.getText());
pseudoClassStateChanged(READ_PSEUDO_CLASS, item.isRead());
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !item.isRead());
}
}
};
}
});

Edit item in JavaFX TreeView

I have this custom TreeView code:
treeView.setCellFactory(new Callback<TreeView<Tree>, TreeCell<Tree>>()
{
#Override
public TreeCell<Tree> call(TreeView<Tree> treeView)
{
final TreeCell<Tree> cell = new TreeCell<Tree>()
{
#Override
protected void updateItem(Tree item, boolean empty)
{
super.updateItem(item, empty);
if (!empty)
{
setText(item != null ? item.toString() : "");
setGraphic(createImageView(item));
setContextMenu(createContextMenuTreeItem(item));
}
else
{
setText(null);
setGraphic(null);
setContextMenu(null);
}
}
};
return cell;
}
});
I would like to be able to rename the nodes of the tree using Content Menu. Can you help me to implement this?
You could simply start the editing of a tree node in MenuItem's EventHandler. Here is an example code that allow the basic editing by double clicking(I didn't try to alter/prevent that) and also from the context menu:
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.*;
import javafx.util.converter.DefaultStringConverter;
public class TreeViewSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tree View Sample");
TreeItem<String> rootItem = new TreeItem<String> ("Tree");
rootItem.setExpanded(true);
for (int i = 1; i < 6; i++) {
TreeItem<String> item = new TreeItem<String> ("Item" + i);
rootItem.getChildren().add(item);
}
TreeView<String> tree = new TreeView<String> (rootItem);
tree.setEditable(true);
tree.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
#Override
public TreeCell<String> call(TreeView<String> p) {
return new RenameMenuTreeCell();
}
});
StackPane root = new StackPane();
root.getChildren().add(tree);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
private static class RenameMenuTreeCell extends TextFieldTreeCell<String> {
private ContextMenu menu = new ContextMenu();
public RenameMenuTreeCell() {
super(new DefaultStringConverter());
MenuItem renameItem = new MenuItem("Rename");
menu.getItems().add(renameItem);
renameItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
startEdit();
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEditing()) {
setContextMenu(menu);
}
}
}
}
This is basically Oracle's example 13-1 with custom tree cells.

Resources