在 JavaFX TreeView 中手动提交编辑

Manually commit the edit in JavaFX TreeView

我想要以下行为:

  1. 当我在编辑 TreeItem 时按 SHIFT+Enter,新的 TreeItem 被附加到下一个。
  2. 提交当前编辑。
  3. 将编辑焦点移至新创建的 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 :) 不完全是,我们只需要以不同的方式查看需求列表,稍微更改一下顺序:

  1. 单元格中的 shift-enter 提交当前编辑,表示“特殊”提交
  2. 树上的处理程序注意到“特殊”并触发进一步的操作
    1. 添加新项目
    2. select新项目
    3. (编辑新项目)

对于 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);
        });
    }
});