I'm trying to call event.getSource().getGraphic() in a branch expanded event of a TreeItem so that I can set a different icon, but I keep getting NullPointerException, and I can't figure out why. I can set the icon successfully when setting up the cell factory, but when I listen for the branch expanded event to do the same, it doesn't work. Here's how I'm setting up the tree (from the initialize event in my controller):
tree.setCellFactory(param -> new TreeCell<File>() {
#Override
public void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText("");
setGraphic(null);
} else {
setText(item.getName());
Image icon = new Image(getClass().getResourceAsStream("folder.png"));
setGraphic(new ImageView(icon));
}
}
});
This works fine and dandy.
Here's my event listener where the ImageView is null for some reason (also being added in the initialize event in my TreeView controller):
File home = new File(System.getProperty("user.home"));
TreeItem<File> root = new TreeItem<>(home);
tree.setRoot(root);
root.addEventHandler(TreeItem.branchExpandedEvent(), event -> {
TreeItem source = event.getSource();
ImageView img = (ImageView)source.getGraphic(); // this is null!
Image icon = Image(getClass().getResourceAsStream("folder-open.png"));
img.setImage(icon);
});
Does anyone have any idea what I'm doing wrong? I'm using Java 1.8.
You're accessing the graphic property of the TreeItem, not the graphic property of the TreeCell that is set to a value != null. You need to handle this in the TreeCell instead. Furthermore you probably should use the disclosureNode property to replace the arrow. Also it's better to reuse the Images:
final Image closedImage = new Image(getClass().getResourceAsStream("folder.png"));
final Image openImage = new Image(getClass().getResourceAsStream("folder-open.png"));
tree.setCellFactory(param -> new TreeCell<File>() {
{
final ImageView imageView = new ImageView();
imageView.setFitWidth(20);
imageView.setFitHeight(20);
final ChangeListener<Boolean> expansionListener = new WeakChangeListener<>((o, oldValue, newValue) -> {
imageView.setImage(newValue ? openImage : closedImage);
});
// add change listener to expanded property of item
treeItemProperty().addListener((o, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.expandedProperty().removeListener(expansionListener);
}
if (newValue != null) {
newValue.expandedProperty().addListener(expansionListener);
expansionListener.changed(null, null, newValue.isExpanded()); // trigger for initial value
}
});
setDisclosureNode(imageView);
}
#Override
public void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
setText((empty || item == null) ? "" : item.getName());
}
});
Related
i am trying to put elements on a listview and treeview with javafx, but both controls wont refresh theyre content. i am using an obvservable list to control the items and every time i delete one item, the listview or treeview removes it from the datasource. but the view is not updating. i am still seeing all the items. the only difference is, the removed item can not be selected any more. for example link 2 shows the collaped item list. image 1 shows the items before they are collaped. the items are collapsed but the old entry is still visible. does anybody know a solution for this problem. thank you all for helping me
link 1: treeview is not collapsed
link 2: treeview is collapsed but not updating old view
this is the custom cell factory i use to display a listview:
public ListCell<T> call(final ListView<T> param) {
ListCell<T> cell = new ListCell<T>(){
#Override
protected void updateItem(final T persistentObject, final boolean empty) {
super.updateItem(persistentObject, empty);
if(persistentObject instanceof POProcessStep){
POProcessStep poProcessStep = (POProcessStep) persistentObject;
if (persistentObject != null) {
super.setText(poProcessStep.getId() + " - " + poProcessStep.getTitle());
}
}else if(persistentObject instanceof POProcess){
POProcess poProcess = (POProcess) persistentObject;
if (persistentObject != null) {
super.setText(poProcess.getId() + " - " + poProcess.getTitle());
}
}else if(persistentObject instanceof POCategory){
POCategory poCategory = (POCategory) persistentObject;
if(persistentObject != null){
super.setText(poCategory.getId() + " - " + poCategory.getTitle());
}
}else if(persistentObject instanceof String){
if(persistentObject != null){
super.setText(String.valueOf(persistentObject));
}
}
super.setGraphic(null);
}
};
return cell;
}
Your cell factory's updateItem(...) needs to handle the case where the cell is empty. This will be exactly the scenario when an item is removed (or becomes empty because a node in the TreeView was collapsed) and the cell that previously showed an item is reused as an empty cell:
public ListCell<T> call(final ListView<T> param) {
ListCell<T> cell = new ListCell<T>(){
#Override
protected void updateItem(final T persistentObject, final boolean empty) {
super.updateItem(persistentObject, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
// ... rest of your code.
}
}
}
return cell ;
}
I'm using a ListView in my project and wanted to add a context menu to each list item so that each can be removed individually. When using the following code this appears to work just fine:
postList.setCellFactory(lv -> {
ListCell<Result> cell = new ListCell<>();
ContextMenu contextMenu = new ContextMenu();
StringBinding stringBinding = new StringBinding() {
{
super.bind(cell.itemProperty().asString());
}
#Override
protected String computeValue() {
if (cell.itemProperty().getValue() == null) {
return "";
}
return cell.itemProperty().getValue().getTitle();
}
};
cell.textProperty().bind(stringBinding);
MenuItem deleteItem = new MenuItem();
deleteItem.textProperty().bind(Bindings.format("Delete item"));
deleteItem.setOnAction(event -> postList.getItems().remove(cell.getItem()));
contextMenu.getItems().addAll(openPermalink, openSubreddit, openURL, deleteItem);
cell.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
if (isNowEmpty) {
cell.setContextMenu(null);
} else {
cell.setContextMenu(contextMenu);
}
});
return cell;
});
However, after clearing the post list - although the items appear to be removed - when another is added all of the removed items re-appear and the item to be added is not displayed.
Any items what could be causing this? It only happens when I set the cell factory and is fine otherwise.
I recorded a small gif to help better explain the issue:
Thank you!
Edit: It appears that the issue is mainly to do with this segment
StringBinding stringBinding = new StringBinding() {
{
super.bind(cell.itemProperty().asString());
}
#Override
protected String computeValue() {
if (cell.itemProperty().getValue() == null) {
return "";
}
return cell.itemProperty().getValue().getTitle();
}
};
As is seems that even though the items are there they have an empty display title
If you use ListCell.updateItem() workflow instead of the StringBinding it should work:
ListCell< Result > cell = new ListCell< Result >() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getValue());
} else {
setText("");
}
}
};
Your binding workflow seems to create an unnecessary dependency which blocks deletion.
P.S.: why do you use binding for static text in deleteItem? Just assign the value directly:
MenuItem deleteItem = new MenuItem();
deleteItem.setText("Delete item");
I have a TableView and a custom MyTableCell extends CheckBoxTreeTableCell<MyRow, Boolean>, In this cell is #Overridden the updateItem method:
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if(!empty){
MyRow currentRow = geTableRow().getItem();
Boolean available = currentRow.isAvailable();
if (!available) {
setGraphic(null);
}else{
setGraphic(super.getGraphic())
}
} else {
setText(null);
setGraphic(null);
}
}
I have a ComboBox<String> where I have some items, and when I change the value of that combobox I want to set the visibility of the checkboxes depending on selected value. So I have a listener:
comboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.equals("A") || newValue.equals("S")) {
data.stream().filter(row -> row.getName().startsWith(newValue)).forEach(row -> row.setAvailable(false));
}
});
The data is an ObservableList<MyRow>
This is just an example and a simplified version of my code
When I change the value in comboBox the table's the chekboxes don't disappear until I scroll or click on that cell. There is a "sollution" to call table.refresh(); but I don't want to refresh the whole table, when I want to refresh just one cell. So I tried to adding some listeners to trigger the updateItem, but I failed at every attempt. Do you have any idea how can I trigger the update mechanism for one cell, not for the whole table?
Bind the cell's graphic, instead of just setting it:
private Binding<Node> graphicBinding ;
#Override
protected void updateItem(Boolean item, boolean empty) {
graphicProperty().unbind();
super.updateItem(item, empty) ;
MyRow currentRow = getTableRow().getItem();
if (empty) {
graphicBinding = null ;
setGraphic(null);
} else {
graphicBinding = Bindings
.when(currentRow.availableProperty())
.then(super.getGraphic())
.otherwise((Node)null);
graphicProperty.bind(graphicBinding);
}
}
I'm trying to add an image to a tableView and went through several questions and answers and it still doesn't work.
The other fields in the tableView like name are loaded correctly.
Intelij tells me that updateItem is never used, which is probably why it doesn't work, but I have no idea how to debug this...
Here's what I got so far
#FXML private TableColumn<PlayerManager, Image> tableColumnType;
#FXML private void initialize(){
tableColumnType.setCellFactory(param -> {
//Set up the ImageView
final ImageView imageview = new ImageView();
imageview.setFitHeight(10);
imageview.setFitWidth(10);
///imageview.setImage(imageComputer); //uncommenting this places the image on all cells, even empty ones
//Set up the Table
TableCell<PlayerManager, Image> cell = new TableCell<PlayerManager, Image>() {
public void updateItem(PlayerManager item, boolean empty) {
if (item != null) { // choice of image is based on values from item, but it doesn't matter now
imageview.setImage(imageComputer);
}
}
};
// Attach the imageview to the cell
cell.setGraphic(imageview);
return cell;
});
}
The questions I went through are:
How to add an Image into a JavaFx TableView column
Display image in table
Inserting images into TableView rows - JavaFX
The signature of the updateItem method is wrong: it should be
public void updateItem(Image item, boolean empty) { /* ... */ }
If the compiler rejects the #Override annotation, then you know you are not defining the correct method. So you should use #Override and if you get a compile error, it is a signal that something is not right.
So you should be able to do
#FXML private TableColumn<PlayerManager, Image> tableColumnType;
#FXML private void initialize(){
tableColumnType.setCellFactory(param -> {
//Set up the ImageView
final ImageView imageview = new ImageView();
imageview.setFitHeight(10);
imageview.setFitWidth(10);
///imageview.setImage(imageComputer); //uncommenting this places the image on all cells, even empty ones
//Set up the Table
TableCell<PlayerManager, Image> cell = new TableCell<PlayerManager, Image>() {
#Override
public void updateItem(Image item, boolean empty) {
if (item != null) { // choice of image is based on values from item, but it doesn't matter now
imageview.setImage(imageComputer);
}
}
};
// Attach the imageview to the cell
cell.setGraphic(imageview);
return cell;
});
}
If your table cell needs to access the actual PlayerManager object, then you need to make the table column a TableColumn<PlayerManager, PlayerManager> and update the cellValueFactory (which you haven't shown) accordingly.
Finally, note that your updateItem(...) method needs to deal with all cases, including empty cells for which the item is null.
So you may need something like
#FXML private TableColumn<PlayerManager, PlayerManager> tableColumnType;
#FXML private void initialize(){
tableColumnType.setCellValueFactory(cellData -> new SimpleObjectProperty<PlayerManager>(cellData.getValue());
tableColumnType.setCellFactory(param -> {
//Set up the ImageView
final ImageView imageview = new ImageView();
imageview.setFitHeight(10);
imageview.setFitWidth(10);
///imageview.setImage(imageComputer); //uncommenting this places the image on all cells, even empty ones
//Set up the Table
TableCell<PlayerManager, PlayerManager> cell = new TableCell<PlayerManager, PlayerManager>() {
#Override
public void updateItem(PlayerManager item, boolean empty) {
if (item != null) { // choice of image is based on values from item, but it doesn't matter now
imageview.setImage(imageComputer);
} else {
imageView.setImage(null);
}
}
};
// Attach the imageview to the cell
cell.setGraphic(imageview);
return cell;
});
}
The signature of the updateItem() method is wrong.
Try to use:
#Override
protected void updateItem(Image item, boolean empty){
//your code
}
Edit:
I think you can solve your problem by also setting a CellValueFactory for your TableColumn:
tableColumnType.setCellValueFactory(
new Callback<CellDataFeatures<PlayerManager, Image>, ObservableValue<Image>(){
#Override
public ObservableValue<Image> call(
CellDataFeatures<PlayerManager, Image> param) {
return param.getValue().exampleMethod; /* Method of your PlayerManager which returns an Image as ObservableValue. To do so you could wrap it in an `ObjectProperty<Image>`*/
}
}
);
I am trying to add a ToolTip ui control to TableColumn of a TableView.
I am getting following exception. Please help.
SEVERE: Failed to load skin 'com.sun.javafx.scene.control.skin.TooltipSkin' for control Label[id=null, styleClass=tooltip]'123'
java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
Following is the code.
TableColumn<HomeDraftRequestModel, Long> revenueColId = (TableColumn) getReqForMyActionTableView()
.getColumns().get(8);
revenueColId.setCellFactory(new Callback<TableColumn<HomeDraftRequestModel, Long>, TableCell<HomeDraftRequestModel, Long>>() {
#Override
public TableCell<HomeDraftRequestModel, Long> call(
TableColumn<HomeDraftRequestModel, Long> param) {
Label label = new Label();
return new TableCell<HomeDraftRequestModel, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
label.setText(item + "");
Tooltip toopTip = new Tooltip(item + "");
Tooltip.install(label, toopTip);
label.setUnderline(true);
label.setCursor(Cursor.HAND);
label.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent arg0) {
label.setTooltip(toopTip);
}
});
setGraphic(label);
}
}
};
}
});
All the useful functionality in a Label is also defined directly in a TableCell (they are both subclasses of Labeled). So you can get rid of the label, and just call the methods directly on the TableCell. You also don't need the mouse listener: the tooltip knows when to display itself. Just call setTooltip(...) to enable it.
The following should work:
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setTooltip(null);
} else {
setText(item + "");
Tooltip toolTip = new Tooltip(item + "");
setUnderline(true);
setCursor(Cursor.HAND);
setTooltip(toolTip);
}
}
I'm not entirely sure why you were getting the exception you were getting; but it should help to simplify the code and use something more "standard".