TreeView in a table cell Java FX 8

2019-09-02 06:07发布

问题:

I have a TableView in my UI, it displays an user list from the database. One of the column of this table is editable, when editing is enabled the cell of this particular column becomes a treeview, listing various options which can be chosen.

Just to clarify, I am trying to implement a Datepicker or a colorpicker like functionality on a table cell, but with my own list of items as a tree.

The table is defined like this

private TableView<User> userTable;

The particular column which displays a tree view is defined like this

private TableColumn<User, TreeView> col4;

I set a setcellFactory method like this to display the tree

col4.setCellFactory(new Callback<TableColumn<User,TreeView>, TableCell<User,TreeView>>() {

            @Override
            public TableCell<User, TreeView> call(
                    TableColumn<User, TreeView> param)
            {
                returns a comboboxtablecell which is filled with a tree
            }
    });

In the table, in the corresponding column, when I click the cell, the cell shows a combo box and the combobox on opening up shows the tree value.

However when the cell is in non-editable state, I am not sure how I should set the setcellValuefactory with a string value

col4.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<User,TreeView>, ObservableValue<TreeView>>() {

        @Override
        public ObservableValue<TreeView> call(
                CellDataFeatures< User,TreeView> param)
        {
                     }
  }

I want to display a value which is inside the treeview as a string when the cell is in a non editable state. I am at a loss as to how to return an observablevalue of type treeview as per the method signature and still display a string.

回答1:

You need to distinguish between the data that your TableView (and it's TableColumns) is presenting, and the cells that are used to present those data. The cellValueFactory is an object that determines how to get the value (i.e. data) for a particular column from the value for the entire row. The cellFactory is an object that determines how to get the cell that presents the data to the user.

Any time you write something like TableColumn<User, TreeView<...>> it is (almost always) a mistake. The first type in the type parameters is the type of the object in each row of the table - this is fine here - and the second is the type of the data that is displayed. TreeView is not a data type: it's the type of a UI element, i.e. the type of something that is used to display the data.

So you want something along these lines. (You haven't been too specific in explaining your data model - which is fine - but this might not be quite right; it will give you the idea though.)

TableView<User> userTable ;
TableColumn<User, Options> optionsCol ;

The User class will have an ObjectProperty<Options>:

public class User {
    private final ObjectProperty<Options> options = new SimpleObjectProperty<>(this, "options", new Options());
    public final Options getOptions() {
        return options.get();
    }
    public final void setOptions(Options options) {
        this.options.set(options);
    }
    public ObjectProperty<Options> optionsProperty() {
        return options ;
    }

    // other properties, etc...
}

And obviously this depends on an Options class encapsulating the data displayed in that column:

public class Options {
    // properties etc
}

Now you just use the default cell value factory:

optionsCol.setCellValueFactory(new PropertyValueFactory("options"));

(or in JavaFX 8, I prefer

    optionsCol.setCellValueFactory((TableColumn.cellDataFeatures<User, Options> data) -> 
        data.getValue()    // this is the User object
        .optionsProperty() // this is an ObjectProperty<Options>, which is an ObservableValue<Options>
    );

which is somewhat more efficient as it avoids the reflection that the PropertyValueFactory uses, and is not much more code, especially if you omit the type on the parameter to the lambda expression).

(You don't have to set it up like this. If the Options are not an intrinsic part of the User, then you could, for example, have a Map<User, ObjectProperty<Options>> defined and use that to return the ObjectProperty<Options> associated with each user. The way I showed is just the most common and probably easiest way.)

I don't really understand where your ComboBox fits in, but this answer should give you enough to work that in as you need it. The cellFactory now just has to return a TableCell that uses a TreeView to display the Options object:

optionsCol.setCellFactory( col -> new TableCell<User, Options>() {
    private TreeView<...> treeView ;
    {
        treeView = new TreeView<>(...);
        // configure tree view, etc
    }
    @Override
    public void updateItem(Options options, boolean empty) {
        super.updateItem(options, empty) ;
        if (empty) {
            setGraphic(null);
        } else {
            // configure treeView with data from options, etc
            setGraphic(treeView);
        }
    }
});

You mentioned the cell was editable, so you need to wire up all the editing stuff for the cell too (and you'll probably want to use a named [inner] class as it's going to get a bit verbose, instead of the anonymous inner class I have here). But this should give you the basic structure.