JavaFX - how to set CellFactory for specific TreeItem - javafx

I have a TreeView, with many TreeItem. what i want to do is that i enable modification for the selected TreeItem and disable it for others.
To get all the TreeView to be modifiable i use :
syTree.setEditable(true);
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
}
syTree.setOnEditCommit(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
syTree.getRoot().getChildren().set(syTree.getRow(t.getTreeItem()), new TreeItem<String>(t.getNewValue()));
System.out.println("setOnEditCommit");
//}
}
});
syTree.setOnEditCancel(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
System.out.println("setOnEditCancel");
}
});
This line just change all the TreeItems to TextField when trying to modify :
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
How to do it for a specific TreeItem ?
Any help please ?

I think you are saying you want some cells to be editable, and some not to be editable, depending on some condition on the TreeItem they are displaying. If so, it's possible, you just need to do a little more work with your cell factory:
Callback<TreeView<String>, TreeCell<String>> defaultCellFactory = TextFieldTreeCell.forTreeView();
syTree.setCellFactory((TreeView<String> tv) -> {
TreeCell<String> cell = defaultCellFactory.call(tv);
cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
if (newTreeItem == null) {
cell.setEditable(false);
} else if ( /* newTreeItem should be editable */) {
cell.setEditable(true);
} else {
cell.setEditable(false);
}
});
return cell ;
});

Related

JavaFX TableView custom row style on double click

I have a TableView in my JavaFX application.
I would like to style differently row when it is double-clicked on it, and differently when it is single-clicked.
Here is what I achieve:
final PseudoClass doubleClickPseudoClass = PseudoClass.getPseudoClass("new");
setRowFactory(tableView -> {
final TableRow<Bean> row = new TableRow<Bean>();
row.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && (! row.isEmpty())) {
row.pseudoClassStateChanged(doubleClickPseudoClass, true);
});
return row;
});
However, when the user doubles click on every new row, I want all previously double-clicked rows to be styled without applying "new" class:
row.pseudoClassStateChanged(doubleClickPseudoClass, false);
How can I do that?
Now I have cumulative styled all rows as they are double-clicked.
You shouldn't use TableRows to store the state themselves since new items may be assigned to a TableRow instance. Instead use a property to store the item double-clicked item and use a listener for styling the rows:
final ObjectProperty<Bean> doubleClickedObject = new SimpleObjectProperty<>();
setRowFactory(tableView -> new TableRow<Bean>() {
private void updateStyle() {
pseudoClassStateChanged(doubleClickPseudoClass, !isEmpty() && doubleClickedObject.get() == getItem());
}
private final InvalidationListener listener;
{
listener = o -> updateStyle();
doubleClickedObject.addListener(new WeakInvalidationListener(listener));
setOnMouseClicked(event -> {
if (!isEmpty() && event.getClickCount() == 2) {
doubleClickedObject.set(getItem());
}
});
}
#Override
protected void updateItem(Bean item, boolean empty) {
super.updateItem(item, empty);
updateStyle();
}
});

How to add multiple listener at treeview in javafx? (hover, focused)

I have made treeview. I want to make treeview like this.
when I enter mouse to item, that item should change image.
when I clcick mouse to item, that item should chnage image.
I know how the way getSelectionMode()... but I don't know hover event.
Please help me.
Not sure if I understand you correct.
But to change your image as soon as you click an image use a selectedItemProperty listener:
treeView.getSelectionModel().selectedItemProperty().addListener( new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue,
Object newValue) {
TreeItem<String> selectedItem = (TreeItem<String>) newValue;
// do something
}
});
If you want it as soon as you hover over the item you can use a hoverProperty on the row:
treeView.setRowFactory(tableView -> {
final TableRow<Person> row = new TableRow<>();
row.hoverProperty().addListener((observable) -> {
final YourItem yourItem = row.getItem();
if (yourItem.isHover() ) {
// do something
} else {
// do something
}
});
return row;
});
(this code is from the answer here)
I missread, its about a TreeView. You can try an onMouseEntered or similiar within a cellfactory:
treeView.setCellFactory(tv -> {
final Tooltip tooltip = new Tooltip();
TreeCell<Path> cell = new TreeCell<Path>() {
#Override
public void updateItem(Path item, boolean empty) {
super.updateItem(item, empty);
}
};
cell.setOnMouseClicked(e -> {
// do something
});
cell.setOnMouseEntered(e -> {
// do something
});
return cell ;
});

JavaFX connected comboboxes

I could really use some help.
I am creating application that has two connected comboboxes in a way that if i select productCode in first in second one should be selected productName.
Both combobox textfields are filtrable for search purposes.
I have set setCellFactories like this (for purpose of dropdown list rendering).
cbSifra.setCellFactory((comboBox) -> new ListCell<Product>() {
#Override
protected void updateItem(Product product, boolean empty) {
super.updateItem(product, empty);
if (product == null || empty) {
setText(null);
} else {
setText(product.getProductCode());
}
}
});
cbNaziv.setCellFactory((comboBox) -> new ListCell<Product>() {
#Override
protected void updateItem(Product product, boolean empty) {
super.updateItem(product, empty);
if (product == null || empty) {
setText(null);
} else {
setText(product.getProductName());
}
}
});
Both comboboxes implement converters to show data into combobox when selected.
cbNaziv.setConverter(new StringConverter<Product>() {
#Override
public String toString(Product product) {
if (product == null) {
return null;
} else {
return product.productNameProperty().get();
}
}
#Override
public Product fromString(String productString)
{
return cbNaziv.getItems().stream().filter(item->productString.equals(item.getProductName())).findFirst().orElse(null);
}
});
cbSifra.setConverter(new StringConverter<Product>() {
#Override
public String toString(Product product) {
if (product == null) {
return null;
} else {
return product.productCodeProperty().get();
}
}
#Override
public Product fromString(String productString)
{
return cbSifra.getItems().stream().filter(item ->productString.equals(item.getProductCode())).findAny().orElse(null);
}
});
Filtering of dropdown list is done using Listener on textProperty() like this:
cbNaziv.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
cbNaziv.show();
final TextField editor = cbNaziv.getEditor();
final Product selected = cbNaziv.getSelectionModel().getSelectedItem();
/*
This needs run on the GUI thread to avoid the error described
here: https://bugs.openjdk.java.net/browse/JDK-8081700.
*/
Platform.runLater(() -> {
/*
If the no item in the list is selected or the selected item
isn't equal to the current input, we refilter the list.
*/
if (selected == null || !selected.equals(editor.getText())) {
filteredProductList.setPredicate(item -> {
// We return true for any items that contains the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (item.getProductName().toUpperCase().contains(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
cbSifra.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
cbSifra.show(); // Is used to open dropdown list as i start typing
final TextField editor = cbSifra.getEditor();
final Product selected = cbSifra.getSelectionModel().getSelectedItem();
/*
This needs run on the GUI thread to avoid the error described
here: https://bugs.openjdk.java.net/browse/JDK-8081700.
*/
Platform.runLater(() -> {
/*
If the no item in the list is selected or the selected item
isn't equal to the current input, we refilter the list.
*/
if (selected == null || !selected.equals(editor.getText())) {
filteredProductList.setPredicate(item -> {
// We return true for any items that contains the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (item.getProductCode().toUpperCase().contains(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
I have valueProperty Listeners to check if value is selected and to fill some textFields to their values or set them to null.
cbSifra.valueProperty().addListener(new ChangeListener<Product>() {
#Override
public void changed(ObservableValue<? extends Product> observable, Product oldValue, Product newValue) {
if (cbSifra.getValue() == null || cbSifra.getValue().getProductName().isEmpty())
{
cbNaziv.getSelectionModel().clearSelection();
tfMpCijena.setText(null);
tfPopust.setText(null);
} else {
cbNaziv.setValue(cbSifra.getValue());
cbSifra.setValue(cbNaziv.getValue());
cbNaziv.hide();
tfMpCijena.setText(cbSifra.getValue().getProductRetailPrice().toString());
tfPopust.setText("0");
}
}
});
cbNaziv.valueProperty().addListener(new ChangeListener<Product>() {
#Override
public void changed(ObservableValue<? extends Product> observable, Product oldValue, Product newValue) {
if (cbNaziv.getValue() == null || cbNaziv.getValue().getProductName().isEmpty())
{
cbSifra.getSelectionModel().clearSelection();
tfMpCijena.setText(null);
tfPopust.setText(null);
} else {
cbSifra.setValue(cbNaziv.getValue());
cbSifra.hide();
tfMpCijena.setText(cbNaziv.getValue().getProductRetailPrice().toString());
tfPopust.setText("0");
}
}
});
The problems are :
when i start typing something into any combobox it filters ok and
when i select item from dropdown list it fills second combobox but
first combobox editor gets focus again and displays dropdown
again
when i delete entry from combobox it deletes ok but the other
combobox value remains (it isnt deleted)
If you can help me i would really appreciate it.
Thanks in advance.
Assuming that both ComboBoxes are of type Product, you can use bidirectional binding to ensure that the values for both ComboBoxes always point to the same product.
cbNaziv.valueProperty().bindBidirectional(cbSifra.valueProperty());
Using this should allow you to remove your change listeners and will hopefully fix some of the issues you were having.

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(...).

expand TreeView (parent)nodes which contains selected item

I have a TreeView existing out of User objects. The TreeView represents the hierarchy of the Users:
Master1
Super1
Super2
User1
User2
Super3
User3
Super4
Master2
...
Every TreeItem is Collapsed when the TreeView is initialized. However, it can be that when the FXML is loaded, a TreeItem object is passed through from another FXML file. Eg: User3 has been passed through:
selectedUserTreeItem = (TreeItem<User>) currentNavigation.getPassthroughObject(); //this is the User3 TreeItem
I try to use a recursive function to expand all the parent nodes from the selecterUserTreeItem
if (selectedUserTreeItem != null) {
expandTreeView(selectedUserTreeItem);
}
tvUsers.setRoot(rootItem);
This is what I have so far:
private void expandTreeView(TreeItem<User> selectedItem) {
if (selectedItem != null) {
System.out.println(selectedItem);
if (selectedItem.isLeaf() == false) {
selectedItem.setExpanded(true);
}
TreeItem<User> parent = selectedItem.getParent();
expandTreeView(parent);
} else {
System.out.println("null");
}
}
I think it has to do something with the fact that the function is a void function and it should be returning a TreeItem object I suppose but for some reason I don't succeed in doing it.
Could someone point me in the right direction?
Ok, I notice that in your expandTreeView() method you expand the node and then you recurse to the previous node to expand that. In my own code I expanded from the root to the leaf, so lets try that:
private static <T> void expandTreeView(TreeItem<T> selectedItem) {
if (selectedItem != null) {
expandTreeView(selectedItem.getParent());
if (!selectedItem.isLeaf()) {
selectedItem.setExpanded(true);
}
}
}
No I don't think its because your method is returning void.
Try setting your TreeView root first before expanding:
>>> tvUsers.setRoot(rootItem);
if (selectedUserTreeItem != null) {
expandTreeView(selectedUserTreeItem);
}
If that doesn't work then try wrapping your initial expandTreeView() to run later:
Platform.runlater( new Runnable()
{
public void run() { expandTreeView( selectedUserTreeItem ); }
};
Ok, I had the same problem and found that I had to delay the expansion a bit like this:
Platform.runlater( new Task<Void>()
{
#Override
protected Void call() throws Exception
{
Thread.sleep( 250 );
return null;
}
#Override
protected void succeeded()
{
expandTreeView( selectedUserTreeItem );
}
} );
You may have to try and vary the delay to get it to work in your case.

Resources