在 JavaFX TreeView 中手动提交编辑
Manually commit the edit in JavaFX TreeView
我想要以下行为:
- 当我在编辑 TreeItem 时按 SHIFT+Enter,新的 TreeItem 被附加到下一个。
- 提交当前编辑。
- 将编辑焦点移至新创建的 TreeItem。 (变成编辑状态就好了,但只关注那个项目也可以。)
我部分实现了第一个和第三个过程,但是如何手动提交当前编辑? (第二个行为)如果没有明确提交,当焦点转移时更改会丢失。
以下是我的来源。
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);
}
}
如果您想了解控制器的完整源代码:
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() {
}
}
不幸的是,TreeView 上的编辑 api(实际上,在所有虚拟化控件上)是不完整的,因为它没有提供任何方式来提交编辑 - 控制编辑状态的单一方法
tree.edit(item);
- 如果项目为空则取消编辑
- 如果项目不为空则开始编辑(如果在编辑时调用则隐式取消任何正在进行的编辑)
唯一 可以 提交的协作者是单元格 - 但我们不能在应用程序代码中与单元格对话,catch-22 :) 不完全是,我们只需要以不同的方式查看需求列表,稍微更改一下顺序:
- 单元格中的 shift-enter 提交当前编辑,表示“特殊”提交
- 树上的处理程序注意到“特殊”并触发进一步的操作
- 添加新项目
- select新项目
- (编辑新项目)
对于 1 我们需要一个自定义单元格,2 可以在自定义 editCommit 处理程序中完成。类似于:
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);
});
}
});
我想要以下行为:
- 当我在编辑 TreeItem 时按 SHIFT+Enter,新的 TreeItem 被附加到下一个。
- 提交当前编辑。
- 将编辑焦点移至新创建的 TreeItem。 (变成编辑状态就好了,但只关注那个项目也可以。)
我部分实现了第一个和第三个过程,但是如何手动提交当前编辑? (第二个行为)如果没有明确提交,当焦点转移时更改会丢失。
以下是我的来源。
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);
}
}
如果您想了解控制器的完整源代码:
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() {
}
}
不幸的是,TreeView 上的编辑 api(实际上,在所有虚拟化控件上)是不完整的,因为它没有提供任何方式来提交编辑 - 控制编辑状态的单一方法
tree.edit(item);
- 如果项目为空则取消编辑
- 如果项目不为空则开始编辑(如果在编辑时调用则隐式取消任何正在进行的编辑)
唯一 可以 提交的协作者是单元格 - 但我们不能在应用程序代码中与单元格对话,catch-22 :) 不完全是,我们只需要以不同的方式查看需求列表,稍微更改一下顺序:
- 单元格中的 shift-enter 提交当前编辑,表示“特殊”提交
- 树上的处理程序注意到“特殊”并触发进一步的操作
- 添加新项目
- select新项目
- (编辑新项目)
对于 1 我们需要一个自定义单元格,2 可以在自定义 editCommit 处理程序中完成。类似于:
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);
});
}
});