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");
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 created a RadioButtonCell with this article but now i want to bind the selectedPropeties of my RadioButton with the properties contained in the ObservableList linked to this TableView. The observableList contains object type of "Risk", and the Model is containing:
final BooleanProperty isDefaultRiskProperty;
My own TableCell implementation is:
package utils;
import Model.databaseModels.Risk;
import controllers.risks.ModifyRisksAvailableController;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.ToggleGroup;
public class RadioButtonCell extends TableCell<Risk, Boolean> {
ToggleGroup toggleGroup;
ModifyRisksAvailableController modifyRisksAvailableController;
public RadioButtonCell(ToggleGroup group){
toggleGroup = group;
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
}
}
#Override
protected void updateItem(Boolean item, boolean empty){
super.updateItem(item, empty);
System.out.println(item);
if(!empty && item != null){
RadioButton radioButton = new RadioButton();
radioButton.setToggleGroup(this.toggleGroup);
radioButton.setSelected(item);
setGraphic(radioButton);
}else{
setGraphic(null);
}
}
}
My TableView contains 3 columns:
#FXML
TableColumn<Risk,Boolean> ColumnCheckBox;
#FXML
TableColumn<Risk,Number> ColumnRiskValue;
#FXML
TableColumn<Risk, Boolean> ColumnIsDefaultRisk;
And I initialize the TableView like this:
//Colonne -> Checbkox / sélection pour suppression
ColumnCheckBox.setCellValueFactory(cellData -> cellData.getValue().checkProperty());
ColumnCheckBox.setCellFactory(column -> new CheckBoxTableCell<>());
ColumnCheckBox.setEditable(true);
ColumnCheckBox.setVisible(false);
ColumnCheckBox.setPrefWidth(24.0);
//Colonne -> Checkbox / risque par défaut
ColumnIsDefaultRisk.setCellValueFactory(cellData -> cellData.getValue().isDefaultRiskProperty());
ColumnIsDefaultRisk.setCellFactory(column -> new RadioButtonCell(toggleGroup,this));
ColumnIsDefaultRisk.setEditable(true);
//Colonne -> TextField / % de risque
ColumnRiskValue.setCellValueFactory(cellData -> cellData.getValue().riskValueProperty());
ColumnRiskValue.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
ColumnRiskValue.setEditable(true);
The property i want to bind with the radioButton is ".isDefaultRiskProperty()" of the "ColumnIsDefaultRisk" column. I giving my datas to the column with setCellValueFactory but i can't get the SimpleBooleanProperty in my CellFactory.
The param "item" that i get in the updateItem's method is a Boolean, (it converting BooleanProperty to Boolean), but i want a ObservableValue.
Thanks you very much.
The problem is that table cells aren’t guaranteed to exist all the time; they are for painting and editing only. So, a ToggleGroup isn’t really of any use here.
But you can do the work of a ToggleGroup yourself fairly easily:
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
RadioButton radioButton = new RadioButton();
radioButton.setToggleGroup(this.toggleGroup);
radioButton.setSelected(item);
setGraphic(radioButton);
radioButton.selectedProperty().addListener(
(o, old, selected) -> {
if (selected) {
Risk cellRisk = getTableRow().getItem();
for (Risk risk : getTableView().getItems()) {
risk.setDefaultRisk(risk == cellRisk);
}
}
});
} else {
setGraphic(null);
}
}
If you have a lot of table items (like, thousands), the for-loop could become a performance issue. So, alternatively, you can implement radio-button-like behavior by defining a Risk field or property independent of the RadioButtonCell class which keeps track of the previous selection:
ObjectProperty<Risk> previousDefaultRisk = new SimpleObjectProperty<>();
columnIsDefaultRisk.setCellFactory(
c -> new RadioButtonCell(previousDefaultRisk));
And your RadioButtonCell class would change to look like this:
public class RadioButtonCell extends TableCell<Risk, Boolean> {
private final ObjectProperty<Risk> previousDefaultRisk;
public RadioButtonCell(ObjectProperty<Risk> previousDefaultRisk) {
this.previousDefaultRisk = previousDefaultRisk;
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
RadioButton radioButton = new RadioButton();
radioButton.setSelected(item);
setGraphic(radioButton);
radioButton.selectedProperty().addListener(
(o, old, selected) -> {
if (selected) {
if (previousDefaultRisk.get() != null) {
previousDefaultRisk.get().setDefaultRisk(false);
}
Risk risk = getTableRow().getItem();
risk.setIsDefaultRisk(true);
previousDefaultRisk.set(risk);
}
});
} else {
setGraphic(null);
}
}
}
Note: It is Java convention that non-static field names should always start with a lowercase letter. Following these conventions will make your code easier for others to read, including Stack Overflow readers.
I have a recyclerview. After setting adapter, user should be reorder their list and save it. And afterwards it should be populate like saved one. But onclicking list items, it color has to change and one of item will open a popup. I've write my codes, but when i add both onclick and ontouch, neither of them worked. If i comment one of them, they work fine. How can i solve this?
holder.itemView.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder);
}
return false;
});
holder.clData.setOnLongClickListener(view -> {
row_index = position;
notifyDataSetChanged();
return true;
});
if (row_index == position) {
holder.clData.setBackgroundColor(context.getResources().getColor(R.color.blue_back));
holder.txtAmount.setTextColor(Color.WHITE);
} else {
holder.clData.setBackgroundColor(context.getResources().getColor(R.color.darkestGrey));
holder.txtAmount.setTextColor(context.getResources().getColor(R.color.blue_current));
}
/// https://github.com/sjthn/RecyclerViewDemo/blob/advanced-usecases/app/src/main/java/com/example/srijith/recyclerviewdemo/SwipeAndDragHelper.java
////// My Adapter
AdapterDatas adapter = new AdapterDatas (context, lstDatas);
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeAndDragHelper);
adapter.setTouchHelper(touchHelper);
rvDatas.setAdapter(adapter);
touchHelper.attachToRecyclerView(rvDatas);
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());
}
});
I'm trying to add some options to the context menu in a JavaFX WebView when the user right clicks a link, however I can't figure out how to do it.
I found I could add my own context menu using:
final ContextMenu contextMenu = new ContextMenu();
view.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println(mouseEvent.getEventType());
if(mouseEvent.isSecondaryButtonDown()) {
System.out.println("Secondary");
contextMenu.show(view, mouseEvent.getSceneX(), mouseEvent.getSceneY());
} else if (mouseEvent.isPrimaryButtonDown()) {
System.out.println("Primary");
} else if (mouseEvent.isMiddleButtonDown()) {
System.out.println("Middle");
}
}
});
javafx.scene.control.MenuItem menuItem1 = new javafx.scene.control.MenuItem("View Source");
menuItem1.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent actionEvent) {
System.out.println("Link: " + rightClickURL);
System.out.println("You clicked view source");
}
});
contextMenu.getItems().add(menuItem1);
Unfortunately, when I do that both menus appear:
If I use view.setContextMenuEnabled(false); then the default menu disappears. Unfortunately doing that also prevents me from detecting which link was right clicked. Here is the code I'm using for that:
engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
EventListener listener = new EventListener() {
#Override
public void handleEvent(org.w3c.dom.events.Event evt) {
String domEventType = evt.getType();
if (domEventType.equals(EVENT_TYPE_CLICK)) {
String href = ((Element)evt.getTarget()).getAttribute("href");
} else if (domEventType.equals(EVENT_TYPE_RIGHT_CLICK)) {
String href = ((Element)evt.getTarget()).getAttribute("href");
rightClickURL = href;
System.out.println(href);
}
}
};
Document doc = engine.getDocument();
NodeList nodeList = doc.getElementsByTagName("a");
for (int i = 0; i < nodeList.getLength(); i++) {
((EventTarget) nodeList.item(i)).addEventListener(EVENT_TYPE_CLICK, listener, false);
((EventTarget) nodeList.item(i)).addEventListener(EVENT_TYPE_RIGHT_CLICK, listener, false);
}
}
}
});
So my question is this: how can I access the default ContextMenu so I can customize it? I've scoured the docs but cannot find any method that allows you to access the default ContextMenu. It seems like there must be a way to do this but I'm stumped as to how.
If customizing the default ContextMenu is not possible, does anyone know how I can show a custom context menu when right clicking a link and also capture which link was clicked? I've been trying to achieve this for days with absolutely no luck...
Currently that's not possible. There is an open issue on JavaFX for that:
https://javafx-jira.kenai.com/browse/RT-20306
If you just want to add a different context menu, then you could do that
webView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouse) {
if (mouse.getButton() == MouseButton.SECONDARY) {
menu = new ContextMenu();
//add some menu items here
menu.show(this, mouse.getScreenX(), mouse.getScreenY());
} else {
if (menu != null) {
menu.hide();
}
}
}
});