Provide various TreeCells conditionally in CellFactory

I have a tree where the cells are supposed to display different information according to the actual implementation of the TreeItem Value .

My domain model looks like this:

Domain model

It seemed natural to me to separate the behavior of "how to display a task in a cell" or "how to display a group in a cell" in two different classes.

public abstract class ComponentTreeCell<T extends Component> extends TreeCell<T>
{
    @Override
    protected void updateItem(T item, boolean empty)
    {
        //Some common logic...
    }
}

public class GroupTreeCell extends ComponentTreeCell<Group>
{
    @Override
    protected void updateItem(Group item, boolean empty)
    {
        super.updateItem(item, empty);

        //Some group-specific-logic
    }    
}

public class TaskTreeCell extends ComponentTreeCell<Task>
{
    @Override
    protected void updateItem(Task item, boolean empty)
    {
        super.updateItem(item, empty);

        //Some task-specific-logic
    }    
}

      

The following controller class contains a TreeView where I have set the CellFactory.

public class Controller implements Initializable
{
    @FXML
    private TreeView<Component> treeview;

    @Override
    public void initialize(URL url, ResourceBundle bundle)
    {
        treeview.setCellFactory(new Callback<TreeView<Component>, TreeCell<Component>>()
        {
            @Override
            public TreeCell<Component> call(TreeView<Component> arg0)
            {
                if(/* stuck */ instanceof Group)
                {
                    return new GroupTreeCell();
                }
                else if(/* stuck */ instanceof Task)
                {
                    return new TaskTreeCell();
                }
                else
                {
                    return new DefaultTreeCell();
                }
            }
        });
    }
}

      

But here I am stuck at the moment to decide which cell I should return. Indeed, I only have a parameter associated with the TreeView and not associated with a TreeItem !

It seems to me like a weakness of JavaFX. Why does JavaFX provide the user with a full TreeView when you only need to restore one TreeCell ??

Is there a way to do it this way or do I need to implement 2 different behaviors in the same custom TreeCell implementation?

public class ComponentTreeCell extends TreeCell<Component>
{
        @Override
        protected void updateItem(Component item, boolean empty)
        {
            //Some common logic...

            if(item instanceof Group)
            {
                //Group-specific logic...
            }
            else if(item instanceof Task)
            {
                //Task-specific logic...
            }
            else
            {
                //Default logic...
            }
       }
}

      

+3


source to share


1 answer


Why does JavaFX provide the user with a full TreeView when you only need to restore one TreeCell ??

Since there is no 1-1 relationship between TreeItem

and TreeCell

s: TreeView

will only create a small number TreeCell

(even if the tree has a very large number of elements), are TreeCell

reused to display different TreeItem

s, for example if some nodes are expanded / reset or if the user scrolls.

. , , : CSS .. . , , .. TreeItem

, ; String

s. , TreeView

, . TreeCell

TreeItem

.

Because of this TreeCell

, which one you provide from the factory should handle whatever TreeItem

can be provided to it TreeView

. For example, when the user changes the items that are displayed (by expanding / collapsing or scrolling), the Group

instance TreeCell

that was previously displayed can be used for the display Task

. This is the purpose of the method updateItem(...)

; it gets called on reuse TreeCell

.

This means that your installation simply won't work. You basically need the implementation TreeCell<Component>

in your last code example. You can of course split the configuration into separate classes if you like, for example:

public class ComponentTreeCell extends TreeCell<Component>
{
    @Override
    protected void updateItem(Component item, boolean empty)
    {
        super.updateItem(item, empty);
        CellConfiguratorFactory.getConfigurator(item).configure(this, item, empty);
    }

}

      

Factory:



public class CellConfiguratorFactory {

    private static CellConfigurator<Task> taskCellConfigurator ;
    private static CellConfigurator<Group> groupCellConfigurator ;
    private static CellConfigurator<Component> defaultCellConfigurator ;

    private static CellConfigurator getConfigurator(Component item) {
        if (item instanceof Task) {
            if (taskCellConfigurator == null) {
                taskCellConfigurator = new TaskCellConfigurator();
            }
            return taskCellConfigurator ;
        } else if (item instanceof Group) {
            if (groupCellConfigurator == null) {
                groupCellConfigurator = new GroupCellConfigurator();
            }
            return groupCellConfigurator ;
        } else {
            if (defaultCellConfigurator == null) {
                defaultCellConfigurator = new DefaultCellConfigurator();
            }
            return defaultCellConfigurator ;
        }
    }
}

      

(Note that you can afford to assume that the factory is only used from one thread, since everything will happen on the FX application thread.)

And then

public interface CellConfigurator<T extends Component> {
    public void configureCell(ComponentTreeCell cell, T item, boolean empty);
}

      

with for example

public class TaskCellConfigurator implements CellConfigurator<Task> {
    public void configureCell(TreeCell<Component> cell, Task item, boolean empty) {
        // task-specific implementation...
    }
}

      

However, I'm not sure if the additional structure is worth the effort here.

+2


source







All Articles