Pallavi
Pallavi

Reputation: 31

Is it possible to set cell factory two times for Listview in Javafx

I have set one cell factory in List view to change the colour of selected Cell.And another one is for drag and drop action.Both are not working at the same time. Cell factory:1

listView.setCellFactory( new Callback<ListView<String>, ListCell<String>>()
        {
            @Override
            public ListCell<String> call( ListView<String> param )
            {
                ListCell<String> listCell = new ListCell<String>()
                {
                    @Override
                    protected void updateItem( String item, boolean empty )
                    {
                        super.updateItem( item, empty );
                        setText( item );
                    }
                };
Cell factory:2
listView.setCellFactory(list -> {

                    ListCell<String> cell = new ListCell<String>() {
                        @Override
                        protected void updateItem(String item, boolean empty) {
                            super.updateItem(item, empty);
                            setText(empty ? null : item);

                        }
                    };

Upvotes: 3

Views: 1495

Answers (2)

fabian
fabian

Reputation: 82461

Is it possible to set cell factory two times for ListView in JavaFX?

It's possible to replace the cell factory, but you cannot make ListView use more than one factory to create a cell.

However you could use a cell factory that handles initialisation/replacement of the item by invoking code from other objects (strategy pattern). This allows you to use combine different behaviors without the need to reimplement the cell.

public class MultiBehaviorListCell<S> extends ListCell<S> {

    public static interface Behavior<T> {

        default void initialize(MultiBehaviorListCell<T> cell) {
        }

        void updateItem(MultiBehaviorListCell<T> cell, T item, boolean empty);
    }

    public static <T> Callback<ListView<T>, ListCell<T>> factory(final Behavior<T>... behaviors) {
        Objects.requireNonNull(behaviors);
        return factory(Arrays.asList(behaviors));
    }

    private final Iterable<Behavior<S>> behaviors;

    private MultiBehaviorListCell(Iterable<Behavior<S>> behaviors) {
        this.behaviors = behaviors;

        // let behaviors handle initialisation
        for (Behavior b : behaviors) {
            try {
                b.initialize(this);
            } catch (RuntimeException ex) {
            }
        }
    }

    @Override
    protected void updateItem(S item, boolean empty) {
        super.updateItem(item, empty);

        // let behaviors handle item replacement
        for (Behavior<S> b : behaviors) {
            try {
                b.updateItem(this, item, empty);
            } catch (RuntimeException ex) {
            }
        }
    }

    public static <T> Callback<ListView<T>, ListCell<T>> factory(final Iterable<Behavior<T>> behaviors) {
        Objects.requireNonNull(behaviors);
        return l -> new MultiBehaviorListCell<>(behaviors);
    }

}

Example use

@Override
public void start(Stage primaryStage) {
    ListView<Integer> listView = new ListView<>();
    listView.getItems().addAll(1, 2, 3, 4);

    EventHandler<MouseEvent> mouseClicked = evt -> {
        MultiBehaviorListCell<Integer> source = (MultiBehaviorListCell<Integer>) evt.getSource();
        System.out.println(source.getItem());
    };

    listView.setCellFactory(MultiBehaviorListCell.factory(
            // setting text
            (cell, number, empty) -> cell.setText(empty || number == null ? "" : number.toString()),

            // changing background / text color
            new MultiBehaviorListCell.Behavior<Integer>() {

                @Override
                public void updateItem(MultiBehaviorListCell<Integer> cell, Integer item, boolean empty) {
                }

                @Override
                public void initialize(MultiBehaviorListCell<Integer> cell) {
                    cell.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY)));
                    cell.setTextFill(Color.BLACK);
                }

            },

            // registering handler for non-empty cells
            (cell, number, empty) -> cell.setOnMouseClicked(empty ? null : mouseClicked)
    ));

    Scene scene = new Scene(listView);

    primaryStage.setScene(scene);
    primaryStage.show();
}

Upvotes: 1

James_D
James_D

Reputation: 209340

setCellFactory is a set method, and it behaves like any other set method: i.e. it sets the value of the ListView's (one and only) cellFactory property. Just like if you had code like

someObject.setIntValue(5);
someObject.setIntValue(42);

you would expect someObject's intValue property to be 42, if you call setCellFactory twice, the cell factory will be the second value you pass to it.

The simplest approach to what you say you are trying to do is to simply combine all the functionality in a single cell factory:

listView.setCellFactory(list -> {

    ListCell<String> cell = new ListCell<String>() {
        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            setText(empty ? "" : item);

            // modify color here depending on item...
        }
    };

    cell.setOnDragDetected(e -> { /* ... */ });
    // other drag handlers...

    return cell ;
});

If you really want to separate things out into different factory implementations, you can use a "decorator" type approach:

public class PlainCellFactory implements Callback<ListView<String, ListCell<String>> {
    @Override
    public ListCell<String> call(ListView<String> list) {
        return new ListCell<String>() {
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty) ;
                setText(empty ? "" : item);
            }
        };
    }
}

add color:

public class ColorListCellFactory implements Callback<ListView<String>, ListCell<String>> {

    private final Callback<ListView<String>, ListCell<String>> originalFactory ;

    public ColorListCellFactory(Callback<ListView<String>, ListCell<String>> originalFactory) {
        this.originalFactory = originalFactory ;
    }

    @Override
    public ListCell<String> call(ListView<String> list) {
        ListCell<String> cell = originalFactory.call(list);
        cell.itemProperty().addListener((obs, oldItem, newItem) -> {
            // change color depending on newItem (which might be null)...
        });
        return cell ;
    }
}

and drag and drop:

public class DragAndDropListCellFactory implements Callback<ListView<String>, ListCell<String>> {

    private final Callback<ListView<String>, ListCell<String>> originalFactory ;

    public DragAndDropListCellFactory(Callback<ListView<String>, ListCell<String>> originalFactory) {
        this.originalFactory = originalFactory ;
    }

    @Override
    public ListCell<String> call(ListView<String> list) {
        ListCell<String> cell = originalFactory.call(list);
        cell.setOnDragDetected(e -> { /* ... */ });
        // other drag handlers...
        return cell ;
    }
}

And now you can do

PlainCellFactory plainFactory = new PlainCellFactory();
ColorListCellFactory colorFactory = new ColorListCellFactory(plainFactory);
DragAndDropListCellFactory dndFactory = new DragAndDropListCellFactory(colorFactory);
listView.setCellFactory(dndFactory);

Upvotes: 3

Related Questions