user3164187
user3164187

Reputation: 1432

JavaFX TreeView edit item

I am trying to edit a TreeView item, below is my code,

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.*;

public class TreeViewSample extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    TreeView<Object> tree;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Tree View Sample");

        TreeItem<Object> rootItem = new TreeItem<Object>("Tree");
        rootItem.setExpanded(true);
        for (int i = 1; i < 6; i++) {
            TreeItem<Object> item = new TreeItem<Object>("Item" + i);
            rootItem.getChildren().add(item);
        }

        ContextMenu menu = new ContextMenu();
        MenuItem renameItem = new MenuItem("Rename");
        menu.getItems().add(renameItem);

        tree = new TreeView<Object>(rootItem);
        tree.setContextMenu(menu);
        tree.setEditable(true);

        tree.setCellFactory(new Callback<TreeView<Object>, TreeCell<Object>>() {
            @Override
            public TreeCell<Object> call(TreeView<Object> p) {
                return new TextFieldTreeCell<Object>();
            }
        });
        renameItem.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                tree.edit(tree.getSelectionModel().getSelectedItem());
            }
        });

        tree.setOnEditCommit(new EventHandler<TreeView.EditEvent<Object>>() {
            @Override
            public void handle(TreeView.EditEvent<Object> event) {
                System.out.println("Commit Value");
            }
        });
        StackPane root = new StackPane();
        root.getChildren().add(tree);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

While editing and commit the value,

If the TreeView and TreeItem generic types are String i have used return new TextFieldTreeCell<String>(new DefaultStringConverter());

When I use Object , i am not sure how to use String converter which leads to ,

Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException: 
Attempting to convert text input into Object, but provided StringConverter is null. Be sure to set a StringConverter in your cell factory.

How can i mitigate this exception.

Upvotes: 2

Views: 3615

Answers (1)

DVarga
DVarga

Reputation: 21799

Displaying Object instances in a TreeView does not really make sense as they do not store any information to display.

Practically, the following StringConverter helps you to remove the error:

tree.setCellFactory(new Callback<TreeView<Object>, TreeCell<Object>>() {
    @Override
    public TreeCell<Object> call(TreeView<Object> p) {
        return new TextFieldTreeCell<Object>(new StringConverter<Object>(){

            @Override
            public String toString(Object object) {
                return object.toString();
            }

            @Override
            public Object fromString(String string) {
                return new Object();
            }
        });
    }
});

What it does: It defines a StringConverter which will return a new Object instance from any string typed into the TextField on commit (as the Object does not store any information to identify, this is the best you can have) and will display the String got from the toString() method of the Object instance.

But: You should provide a data model for the TreeView to display, which is capable to store at least the textual data that could be displayed.

I have updated your code to create an example of a TreeView backed by a really simple data model:

It defines the data model for the tree (Item class) which class has one single property to the the name of the item. The generic parameter of the TreeView is set to this Item class, therefore the TreeView is capable to display Item instances.

As you can see I have defined a StringConverter also, which now makes much more sense: the fromString method is executed when the typed text is commited, in this case the method will return a new Item instance, that has its name set to the input String. The toString method is needed to specify, how to textually display an Item instance. In our case we will return the name of the particular instance.

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.*;

public class TreeViewSample2 extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    TreeView<Item> tree;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Tree View Sample");

        TreeItem<Item> rootItem = new TreeItem<Item>(new Item("Root"));


        rootItem.setExpanded(true);
        for (int i = 1; i < 6; i++) {
            TreeItem<Item> item = new TreeItem<Item>(new Item("Item"+i));
            rootItem.getChildren().add(item);
        }

        ContextMenu menu = new ContextMenu();
        MenuItem renameItem = new MenuItem("Rename");
        menu.getItems().add(renameItem);

        tree = new TreeView<Item>(rootItem);
        tree.setContextMenu(menu);
        tree.setEditable(true);

        tree.setCellFactory(new Callback<TreeView<Item>, TreeCell<Item>>() {
            @Override
            public TreeCell<Item> call(TreeView<Item> p) {
                return new TextFieldTreeCell<Item>(new StringConverter<Item>(){

                    @Override
                    public String toString(Item object) {
                        return object.getName();
                    }

                    @Override
                    public Item fromString(String string) {
                        return new Item(string);
                    }
                });
            }
        });
        renameItem.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                tree.edit(tree.getSelectionModel().getSelectedItem());
            }
        });

        tree.setOnEditCommit(new EventHandler<TreeView.EditEvent<Item>>() {
            @Override
            public void handle(TreeView.EditEvent<Item> event) {
                System.out.println("Commit Value");
            }
        });
        StackPane root = new StackPane();
        root.getChildren().add(tree);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

class Item {
    private SimpleStringProperty name = new SimpleStringProperty("");
    public SimpleStringProperty nameProperty() {
        return name;
    }

    public final String getName() { return nameProperty().get();}
    public final void setName(String newName) { nameProperty().set(newName);}

    public Item(String name) {
        setName(name);
    }
}

Upvotes: 3

Related Questions