JavaFX: How to find index of TreeItem in a TreeView? - java

I have a TreeView and a Button in my program. Whenever this Button is clicked, it adds a new element right after the element selected last in the TreeView.
For example, if I were to select the "Test Action" element and click the "Add" button, it should add another TreeItem right after "Test Action", but before "Test Condition".
I've written code so that I can keep track of the element selected last:
#FXML
TreeView<String> view;
TreeItem<String> current = root;
view.selectionModelProperty().addListener(new ChangeListener<MultipleSelectionModel<TreeItem<String>>>() {
#Override
public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<String>>> observable,
MultipleSelectionModel<TreeItem<String>> oldValue,
MultipleSelectionModel<TreeItem<String>> newValue) {
current = newValue.getSelectedItem();
}
});
However, through the use of the TreeItem, "current", there is no method I can use to find its index in the TreeView.
This is so that I can do:
root.getChildren().add(index, new TreeItem<String>(new OpenBank().getAction(), Icons.ACTION.getIcon()));
So is there a way to find a child's index in a TreeView?

The class TreeItem has a method getParent, which returns the parent of the specified TreeItem. This parent, which is also a TreeItem has a method getChildren to get the child TreeItems; the order of TreeItems in the returned ObservableList is the actual order that you can see on the screen, therefore you can insert a new element in a specific index with add after you retrieved the index of the element in the list with indexOf().
You can simply handle the current selection in the event listener of your Button:
Button b = new Button("Add");
b.setOnAction(event -> {
// Get the selected item
TreeItem<String> selectedItem = treeView.getSelectionModel().getSelectedItem();
// If there is no selection or the root is selected do nothing
if (selectedItem == null || selectedItem == rootNode)
return;
// Otherwise get the index of the Node from the children of its parent
// and append the new item right after it
int index = selectedItem.getParent().getChildren().indexOf(selectedItem);
selectedItem.getParent().getChildren().add(index+1, new TreeItem<>("New Item"));
});
If you already tracking the current selection:
The modification is just to use current (that's how you named your variable) rather than getting the selection in the handler:
Button b = new Button("Add");
b.setOnAction(event -> {
// If there is no selection or the root is selected do nothing
if (current == null || current == rootNode)
return;
// Otherwise get the index of the Node from the children of its parent
// and append the new item right after it
int index = current.getParent().getChildren().indexOf(current);
current.getParent().getChildren().add(index+1, new TreeItem<>("New Item"));
});

TreeItem::getRow
Returns the index position of the given TreeItem, assuming that it is
currently accessible through the tree hierarchy (most notably, that
all parent tree items are expanded). If a parent tree item is
collapsed, the result is that this method will return -1 to indicate
that the given tree item is not accessible in the tree.

Related

Load in a specific name in combobox as first row

i have a model with an id and a name.
I put the model object into a combobox. From the model i take the name atribute and make that the visual part, the only problem is. When you start up the program the combobox it is empty.
You have to click on it and select the second row to see the item. I would like to see the item straight away. is this possible?
public class ItemCell extends ListCell<Model> {
#Override
public void updateItem(Model person, boolean empty) {
super.updateItem(person, empty);
setText(person == null ? "" : person.getFirstName());
}
}
in my view class i have:
ComboBox<Model> comboBox = new ComboBox<>();
comboBox.setCellFactory(lv -> new ItemCell());
comboBox.setButtonCell(new ItemCell());
comboBox.valueProperty().addListener((o, oldValue, newValue) -> {
personModelFromCombobox = otherObject.getPerson();
});
as you see it does everything correct except you have to click on it and select the second row to see the item.
this happens because you do not select an item in the comboBox
comboBox.getSelectionModel().select(index);
where index is the integer position of the item to select in the selection model, or a value from and of the same type as the arrayList.

How to move items between two ListViews with change listener JavaFX

I have two ListViews, allStudentsList has items already populated within it, currentStudentList has none. My aim for when the user selects an item in allStudentList is for that item be moved into currentStudentList. I have done this by placing a listener on the selection model of the allStudentList.
I get an IndexOutOfBoundsException and I am not sure why this is occurring. From testing, it seems that this problem is isolated to the last 4 lines of this method yet I am not sure why.
allStudentsList.getSelectionModel().selectedItemProperty()
.addListener((observableValue, oldValue, newValue) -> {
if (allStudentsList.getSelectionModel().getSelectedItem() != null) {
ArrayList<String> tempCurrent = new ArrayList<>();
for (String s : currentStudentList.getItems()) {
tempCurrent.add(s);
}
ArrayList<String> tempAll = new ArrayList<>();
for (String s : allStudentsList.getItems()) {
tempAll.add(s);
}
tempAll.remove(newValue);
tempCurrent.add(newValue);
// clears current studentlist and adds the new list
if (currentStudentList.getItems().size() != 0) {
currentStudentList.getItems().clear();
}
currentStudentList.getItems().addAll(tempCurrent);
// clears the allStudentList and adds the new list
if (allStudentsList.getItems().size() != 0) {
allStudentsList.getItems().clear();
}
allStudentsList.getItems().addAll(tempAll);
}
});
As a quick fix you can wrap the code parts that modify the item lists into a Platform.runLater(...) block:
Platform.runLater(() -> {
// clears current studentlist and adds the new list
if (currentStudentList.getItems().size() != 0)
currentStudentList.getItems().clear();
currentStudentList.getItems().addAll(tempCurrent);
});
Platform.runLater(() -> {
// clears the allStudentList and adds the new list
if (allStudentsList.getItems().size() != 0)
allStudentsList.getItems().clear();
allStudentsList.getItems().addAll(tempAll);
});
The problem is that you cannot change the selection while a selection change is being processed. As you remove all elements with allStudentsList.getItems().clear();, the selection will change (the selected index will be -1), the mentioned condition will met. This is what the usage of the Platform.runLater(...) block will prevent by "postponing" the modification.
But your whole handler could be exchanged with
allStudentsList.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
if (newValue != null) {
Platform.runLater(() -> {
allStudentsList.getSelectionModel().select(-1);
currentStudentList.getItems().add(newValue);
allStudentsList.getItems().remove(newValue);
});
}
});
It sets the selected index to -1: nothing is selected in the ListView to avoid changing to a different item when removing the current one (this was done implicitly in your version by clearing the list), then it adds the currently selected element to the s"elected list", then it removes the current element from the "all item list". All of these actions are wrapped in the mentioned Platform.runLater(...) block.

MouseEvent Listeners/EventTypes for TreeItems

Basically, I have a TreeView
TreeView<String> treeView = new TreeView<String>();
The root
TreeItem<String> root = new TreeItem<String>();
treeView.setRoot(root);
And lastly an item inside the root
TreeItem<String> item1 = new TreeItem<String>();
root.getChildren().add(item1);
(With or without more siblings or adding more children to item1)
And what I want to do is add a listener or EventHandler specifically for item1 (and any siblings) so when a user double clicks on it or changes its value, something happens. I also want to know if there are any of these that show the oldValue and newValue of the TreeItem when it is edited.
The only leads I really have are that it probably has to do with using:
root.getChildren().addListeners()...
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener...
or
item1.addEventHandler(...
Any help is very appreciated!
I also want to know if there are any of these that show the oldValue and newValue of the TreeItem when it is edited.
Are you just looking for
item1.valueProperty().addListener((obs, oldValue, newValue) -> {
// do whatever you need with oldValue and newValue
});
?

Can JavaFX's ListChangeListener.Change.getRemoved() return non-contiguous items?

Problem
When items are removed from an ObservableList, a change event is fired where getFrom() gives the location of the removal and getRemoved() gives a list of items that were removed. The documentation says:
The getRemoved() method returns a list of elements that have been replaced or removed from the list.
It is not stated as such, but I took it to be implied that the list of items is a contiguous sub-list from the original list. I've written a lot of code with that assumption, but am now encountering difficulties with TreeTableView's selection model, which doesn't behave that way.
Example
Take for instance a simple tree table with three "Node" rows. If I select those three rows...
...and then click and select just the middle row...
...the change event fired on treeTableView.getSelectionModel().getSelectedItems() looks like this:
{ [TreeItem [ value: Node 1 ], TreeItem [ value: Node 3 ]] removed at 0, }
In a single change event it reports that "Node 1" and "Node 3" were removed from index 0 of the selectedItems list.
I would have expected the Change object to have two separate removal events separated by next() calls. The first call to next() would tell me that "Node 1" was removed at index 0, and the second call to next() would tell me that "Node 3" was removed at index 1. But no, I get a single event with both rows listed at once.
Question
Can getRemoved() really return non-contiguous items? Is this a misunderstanding on my part of how list change events work, or is it a bug in TreeTableView?
Normally I'm hesitant to blame standard libraries, but this wouldn't be the first bug I've found in JavaFX, so it's not unthinkable.
Update
If I add a call to setShowRoot(false), the behavior changes. I get what I would expect, the removal split into two pieces:
{ [TreeItem [ value: Node 1 ]] removed at 0, [TreeItem [ value: Node 3 ]] removed at 1, }
Also, here is my MCVE:
import java.util.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;
public class TreeTableSelectionEvents extends Application {
public void start(Stage stage) {
// Root node.
TreeItem<String> root = new TreeItem<>("Root");
root.setExpanded(true);
root.getChildren().setAll(Arrays.asList(
new TreeItem<>("Node 1"),
new TreeItem<>("Node 2"),
new TreeItem<>("Node 3")
));
// Single column.
TreeTableColumn<String, String> column = new TreeTableColumn<>("Column");
column.setPrefWidth(150);
column.setCellValueFactory((TreeTableColumn.CellDataFeatures<String, String> p) -> {
return new ReadOnlyStringWrapper(p.getValue().getValue());
});
// Tree table.
TreeTableView<String> table = new TreeTableView<>(root);
table.getColumns().add(column);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// table.setShowRoot(false);
table.getSelectionModel().getSelectedItems().addListener(
(ListChangeListener.Change<? extends TreeItem<String>> change) -> {
System.out.printf("item change = %s, list is now %s%n", change, change.getList());
}
);
table.getSelectionModel().getSelectedIndices().addListener(
(ListChangeListener.Change<? extends Integer> change) -> {
System.out.printf("index change = %s, list is now %s%n", change, change.getList());
}
);
// Stage.
stage.setScene(new Scene(table));
stage.show();
}
}
You're right, the event should contain two separate 'remove' changes. As of 1.8.0_74, TreeTableView's selection model seems hopelessly broken. It's not even consistent with TreeView's selection model, which is also screwy (but less so). There are so many failure modes, existing bugs and regression errors that it's tough to tell if Oracle is aware of problem. I'd suggest filing another bug. Below code provides a decent sandbox for playing with feature.
public class Test extends Application {
public void start(Stage pStage) {
pStage.setTitle("Test");
final TabPane tabPane = new TabPane();
tabPane.getTabs().addAll(
Stream.of(true, false).map(
pIsTreeTable -> {
final Tab result = new Tab(pIsTreeTable ? "TreeTableView" : "TreeView");
// create tree model
final TreeItem<String> root = new TreeItem<>("Root Node");
root.setExpanded(true);
final Collection<TreeItem<String>> children = IntStream.rangeClosed(
1, 5
).mapToObj(pIdx -> new TreeItem<>("Child Node " + pIdx)).collect(
Collectors.toList()
);
// create TreeView or TreeTableView
final Control tree;
final MultipleSelectionModel<TreeItem<String>> selectionModel;
if (pIsTreeTable) {
final TreeTableView<String> treeTableView = new TreeTableView<>(
root
);
final TreeTableColumn<String,String> column = new TreeTableColumn<>(
"Column"
);
column.setCellValueFactory(
pTreeItem -> new ReadOnlyStringWrapper(
pTreeItem.getValue().getValue()
)
);
treeTableView.getColumns().add(column);
tree = treeTableView;
selectionModel = treeTableView.getSelectionModel();
} else {
final TreeView<String> treeView = new TreeView<>(root);
tree = treeView;
selectionModel = treeView.getSelectionModel();
}
selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
// add buttons
final ToggleButton childrenBtn = new ToggleButton("Children");
childrenBtn.selectedProperty().addListener(
(pObservable, pOldVal, pNewVal) -> {
if (pNewVal) {
root.getChildren().addAll(children);
} else {
root.getChildren().clear();
}
}
);
childrenBtn.setSelected(true);
final ToggleButton showRootBtn = new ToggleButton("Show Root");
showRootBtn.setSelected(true);
(
pIsTreeTable ?
((TreeTableView<?>) tree).showRootProperty() :
((TreeView<?>) tree).showRootProperty()
).bind(showRootBtn.selectedProperty());
// 'getSelectedItems()' tab
final Tab selectedItemsTab = new Tab("getSelectedItems()");
final TextArea selectedItemsTextArea = new TextArea();
selectionModel.getSelectedItems().addListener(
(ListChangeListener<TreeItem<String>>) pChange -> {
while (pChange.next()) {
if (pChange.getRemovedSize() > 0) {
selectedItemsTextArea.appendText(
"Removed " + pChange.getRemoved() + '\n'
);
}
if (pChange.getAddedSize() > 0) {
selectedItemsTextArea.appendText(
"Added " + pChange.getAddedSubList() + '\n'
);
}
}
selectedItemsTextArea.appendText(
"Selection: " + pChange.getList() + "\n\n"
);
}
);
selectedItemsTab.setContent(selectedItemsTextArea);
// 'getSelectedItem()' tab
final Tab selectedItemTab = new Tab("getSelectedItem()");
final TextArea selectedItemTextArea = new TextArea();
selectionModel.selectedItemProperty().addListener(
(pObservable, pOldVal, pNewVal) -> {
selectedItemTextArea.appendText("Selected " + pNewVal + '\n');
}
);
selectedItemTab.setContent(selectedItemTextArea);
// display selection data in text area
final TabPane selectionTabPane = new TabPane();
selectionTabPane.getTabs().addAll(selectedItemsTab, selectedItemTab);
final SplitPane splitPane = new SplitPane(
tree, new HBox(showRootBtn, childrenBtn), selectionTabPane
);
splitPane.setOrientation(Orientation.VERTICAL);
result.setContent(splitPane);
return result;
}
).collect(Collectors.toList())
);
pStage.setScene(new Scene(tabPane, 300, 450));
pStage.show();
}
public static void main(String[] pArgs) {launch(pArgs);}
}
Related(?) issues:
Depress 'Ctrl' button
Select "Child Node 2"
Select "Child Node 3"
Select "Child Node 1"
ListChangeListener.Change event implies that "Child Node 2" was selected.
Select "Root Node"
De-select "Show Root" button
"Child Node 1" is selected but no selection event is broadcast.
Select "Child Node 2"
De-select "Children" button
'selection' list includes a null.
Select "Child Node 1"
Collapse "Root Node"
ListChangeListener.Change event does includes no removals.
De-select "Children" button
Select "Root Node"
De-select "Show Root" button
Event is broadcast for neither TreeView nor TreeTableView. From there, if "Show Root" button is pressed, TreeView broadcasts event but TreeTableView doesn't.

How to create a loop which will check if Jtree contains a value from Set <string> and reselects them automatically after a button press?

How to create a loop which will check if the jtree contains any elements from the set <string> and reselect them automatically after a press of button.
I store my info like this:
testNos = (currentTestLabel.getText() + classLabel.getText());
mySet1.add(testNos);
I've been struggling with this for days, and I wasn't able to find any info on this or how to achieve that. Any help would be great.
You can create a method that will return you all the matching nodes. You can do it by iterating over all the nodes in the tree and check if there names matches the one in the set.
public java.util.List<TreePath> find(DefaultMutableTreeNode root, Set<String> s) {
java.util.List<TreePath> paths = new ArrayList<>();
#SuppressWarnings("unchecked")
Enumeration<DefaultMutableTreeNode> e = root.depthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode node = e.nextElement();
if (s.contains(node.toString())) {
paths.add(new TreePath(node.getPath()));
}
}
return paths;
}
You can then call this method passing the tree node and Set of strings. Please note that you will need to cast the root to DefaultMutableTreeNode because getRoot returns Object.
java.util.List<TreePath> treePaths= find((DefaultMutableTreeNode)tree.getModel().getRoot(), someSet);
Then iterate over treePaths and invoke removeSelectionPath to deselect the nodes
for (TreePath treePath : treePaths) {
tree.getSelectionModel().removeSelectionPath(treePath);
}

Categories

Resources