I want following behaviours:
I partially implemented first and third processes, but how can I manually commit current edit? (second behaviour) Without explicitly commiting, changes get lost when the focus is shifted.
Below is my source.
private KeyCombination shiftEnter = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.SHIFT_DOWN);
@FXML
public void typeHandle(KeyEvent e) {
if (shiftEnter.match(e)) {
TreeItem<Content> newItem = new TreeItem<>(null);
List<TreeItem<Content>> siblings = treeView.getSelectionModel().getSelectedItem().getParent().getChildren();
siblings.add(siblings.indexOf(treeView.getSelectionModel().getSelectedItem()) + 1, newItem);
treeView.getSelectionModel().select(newItem);
}
}
Full source code of the controller if you wonder:
package jsh.hiercards;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTreeView;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
public JFXButton save;
@FXML
public JFXButton load;
@FXML
public Label pending;
@FXML
public Label completed;
@FXML
public Label incompleted;
@FXML
public JFXButton start;
@FXML
public JFXButton viewerMode;
@FXML
public JFXTreeView<Content> treeView;
private KeyCombination shiftEnter = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.SHIFT_DOWN);
@Override
public void initialize(URL location, ResourceBundle resources) {
treeView.setCellFactory(tv -> new TextFieldTreeCell<>(new StringConverter<Content>() {
@Override
public Content fromString(String text) {
System.out.println(tv.getTreeItem(tv.getSelectionModel().getSelectedIndex()).getValue());
TreeItem<Content> parentItem = tv.getTreeItem(tv.getSelectionModel().getSelectedIndex()).getParent();
Concept parent = parentItem == null ? null : (Concept) parentItem.getValue();
String[] tokens = text.split(":", 2);
if (tokens.length < 2) {
return new Concept(tokens[0].trim(), parent);
} else return new Property(tokens[0].trim(), parent, tokens[1].trim());
}
@Override
public String toString(Content content) {
if (content instanceof Property) {
return ((Property) content).name + " : " + ((Property) content).description;
}
return content == null ? "" : content.name;
}
}));
TreeItem<Content> root = new TreeItem<>(new Concept("TESTROOT", null));
root.getChildren().add(new TreeItem<>(new Concept("TESTCONCEPT", (Concept) root.getValue())));
root.getChildren().add(new TreeItem<>(new Property("TESTPROPERTY", (Concept) root.getValue(), "DESCRIPTION")));
treeView.setRoot(root);
}
@FXML
public void startEdit(TreeView.EditEvent e) {
System.out.println("sTART");
}
@FXML
public void commitEdit(TreeView.EditEvent e) {
System.out.println("COLMMIT");
}
@FXML
public void cancelEdit(TreeView.EditEvent e) {
System.out.println("edit cancel");
}
@FXML
public void typeHandle(KeyEvent e) {
if (shiftEnter.match(e)) {
TreeItem<Content> newItem = new TreeItem<>(null);
List<TreeItem<Content>> siblings = treeView.getSelectionModel().getSelectedItem().getParent().getChildren();
siblings.add(siblings.indexOf(treeView.getSelectionModel().getSelectedItem()) + 1, newItem);
treeView.getSelectionModel().select(newItem);
}
}
@FXML
public void save() {
}
@FXML
public void load() {
}
@FXML
public void viewerMode() {
}
@FXML
public void start() {
}
}
Unfortunately, the editing api on the TreeView (actually, on all virtualized controls) is incomplete in not providing any way to commit an edit - the single method to control editing state
tree.edit(item);
The only collaborator that can commit is the cell - but we must not talk to the cell in application code, catch-22 :) Well not quite, we just need to look at the requirement list a bit differently, changing the order a bit:
For 1 we need a custom cell, 2 can be done in a custom editCommit handler. Something like:
KeyCombination shiftEnter = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.SHIFT_DOWN);
// custom cell
tree.setCellFactory(t -> {
TextFieldTreeCell<MenuItem> cell = new TextFieldTreeCell<>(conv) {
// we don't have access to super's field, keep an alias
private TextField fieldAlias;
@Override
public void startEdit() {
super.startEdit();
// install a custom key handler
if (isEditing() && fieldAlias == null) {
fieldAlias = (TextField) lookup(".text-field");
fieldAlias.setOnKeyReleased(e -> {
if (shiftEnter.match(e)) {
shiftCommit(fieldAlias.getText());
}
});
}
}
// signal "special" commit before calling commitEdit
private void shiftCommit(String text) {
MenuItem item = getConverter().fromString(text);
getTreeView().getProperties().put(SHIFT_COMMIT, item);
commitEdit(item);
getTreeView().getProperties().remove(SHIFT_COMMIT);
}
};
return cell;
});
// custom editCommit handler
tree.setOnEditCommit(e -> {
// normal edit, nothing to do
// note: this is a bug in tree editing - the cell changes the value of the treeItem
// even if there's a custom handler installed!
if (tree.getProperties().get(SHIFT_COMMIT) == null) return;
// find the location of the edited item
TreeItem<MenuItem> edited = e.getTreeItem();
TreeItem<MenuItem> parent = edited.getParent();
int index = -1;
if (parent != null) {
index = parent.getChildren().indexOf(edited);
}
if (index > 0) {
// if found, insert a new item as next and select it
TreeItem<MenuItem> added = new TreeItem<>(new MenuItem("added"));
parent.getChildren().add(index + 1, added);
tree.getSelectionModel().select(added);
// start editing the new item
// must be delayed until all internal state changes are processed
Platform.runLater(() -> {
tree.edit(added);
});
}
});