最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - How to create a simple custom Skin with virtual flow? - Stack Overflow

programmeradmin2浏览0评论

I need to create a custom component with VirtualFlow. When I create it manually then I don't have any problems. Now I want to try to create it with a custom Skin. This is my code:

public class NewMain extends Application {

    public static class MyControl<T> extends Control {

        private ObservableList<T> items;

        public MyControl(ObservableList<T> items) {
            this.items = items;
            setSkin(new MySkin<T>(this));
        }

        public ObservableList<T> getItems() {
            return items;
        }
    }

    public static class MySkin<T> extends VirtualContainerBase<MyControl<T>, VirtualCell<T>> {

        public MySkin(MyControl<T> c) {
            super(c);
            getChildren().add(getVirtualFlow());
            getVirtualFlow().setCellFactory(vf -> new VirtualCell() {
                @Override
                public void updateIndex(int index) {
                    super.updateIndex(index);
                    if (index >= 0 && index < getSkinnable().getItems().size()) {
                        updateItem(getSkinnable().getItems().get(index), false);
                    } else {
                        updateItem(null, true);
                    }
                }
            });
            getVirtualFlow().setCellCount(getSkinnable().getItems().size());
        }

        @Override
        protected int getItemCount() {
            return getSkinnable().getItems().size();
        }

        @Override
        protected void updateItemCount() {

        }
    }

    public static class VirtualCell<T> extends IndexedCell<T> {

        private static int counter = 0;

        public VirtualCell() {
            System.out.println("Created:" + counter);
            counter++;
        }

        @Override
        protected void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(item.toString());
            }
        }

        @Override
        protected Skin<?> createDefaultSkin() {
            return new CellSkinBase<>(this);
        }
    }

    @Override
    public void start(Stage primaryStage) {
        List<String> list = new ArrayList<>();
        for (var i = 0; i < 100; i++) {
            list.add("Item " + i);
        }
        ObservableList<String> items = FXCollections.observableArrayList(list);
        var control = new MyControl<String>(items);
        VBox.setVgrow(control, Priority.ALWAYS);
        var root = new VBox(control);
        Scene scene = new Scene(root, 600, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

But the items are not displayed. Could anyone say how to fix it?

I need to create a custom component with VirtualFlow. When I create it manually then I don't have any problems. Now I want to try to create it with a custom Skin. This is my code:

public class NewMain extends Application {

    public static class MyControl<T> extends Control {

        private ObservableList<T> items;

        public MyControl(ObservableList<T> items) {
            this.items = items;
            setSkin(new MySkin<T>(this));
        }

        public ObservableList<T> getItems() {
            return items;
        }
    }

    public static class MySkin<T> extends VirtualContainerBase<MyControl<T>, VirtualCell<T>> {

        public MySkin(MyControl<T> c) {
            super(c);
            getChildren().add(getVirtualFlow());
            getVirtualFlow().setCellFactory(vf -> new VirtualCell() {
                @Override
                public void updateIndex(int index) {
                    super.updateIndex(index);
                    if (index >= 0 && index < getSkinnable().getItems().size()) {
                        updateItem(getSkinnable().getItems().get(index), false);
                    } else {
                        updateItem(null, true);
                    }
                }
            });
            getVirtualFlow().setCellCount(getSkinnable().getItems().size());
        }

        @Override
        protected int getItemCount() {
            return getSkinnable().getItems().size();
        }

        @Override
        protected void updateItemCount() {

        }
    }

    public static class VirtualCell<T> extends IndexedCell<T> {

        private static int counter = 0;

        public VirtualCell() {
            System.out.println("Created:" + counter);
            counter++;
        }

        @Override
        protected void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(item.toString());
            }
        }

        @Override
        protected Skin<?> createDefaultSkin() {
            return new CellSkinBase<>(this);
        }
    }

    @Override
    public void start(Stage primaryStage) {
        List<String> list = new ArrayList<>();
        for (var i = 0; i < 100; i++) {
            list.add("Item " + i);
        }
        ObservableList<String> items = FXCollections.observableArrayList(list);
        var control = new MyControl<String>(items);
        VBox.setVgrow(control, Priority.ALWAYS);
        var root = new VBox(control);
        Scene scene = new Scene(root, 600, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

But the items are not displayed. Could anyone say how to fix it?

Share Improve this question edited Mar 17 at 12:04 SilverCube asked Mar 15 at 19:09 SilverCubeSilverCube 6443 silver badges12 bronze badges 1
  • 2 If your ultimate goal is to implement a control as described in one of your recent questions, then I don't believe VirtualFlow will work for you. That class is designed to layout cells in a single column or row (depending on orientation). It's not designed to layout cells similar to a FlowPane. You may have to implement this entirely from scratch to get what you want (though you can study the source code of VirtualFlow for help). – Slaw Commented Mar 17 at 6:42
Add a comment  | 

1 Answer 1

Reset to default 3

Firstly, creating a Skin that extends VirtualContainerBase, is not a very simple task. You need to have a thorough understanding of flow and basics of VirtualFlow. And I believe it will be very hard to explain here the exact flow or approach of using a custom VirtualContainerBase.

So, working on surface level and trying to give you something to work with, below are the minimal changes you need to do in your code, to get the cell rendering in your control.

Step:1

In the Skin, there should be a point to call the rebuildCells() of VirtualFlow. In your code, you are not calling this method anywhere. And as rebuildCells() is a protected method, you need to create your custom VirtualFlow first.

I am including this call in layoutChildren() method of Skin, but in reality you need to call this based on the implementation of your Skin. Please check the current implementation of how ListView, TableView,. etc controls do this.

@Override
protected void layoutChildren(double x, double y, double w, double h) {
    super.layoutChildren(x, y, w, h);
    flow.rebuildCells();
    flow.resizeRelocate(x, y, w, h);
}

Once you add the above code, the control is rendered as below.

From the above gif, you can notice that the cells are rendered but not the content. This is because, we have not yet included the mechanism that calls the updateItem() method of the cell.

Step:2

You need to ensure that you call the updateItem() of the cell when required. For this demo, I am including a listener for indexProperty of the cell and calling the updateItem method. But in reality it should done carefully!! Again saying.. Please check the current implementation of how ListView, TableView,. etc controls do this.

getVirtualFlow().setCellFactory(vf -> {
    VirtualCell cell = new VirtualCell();
    cell.indexProperty().addListener(((obs, old, val) -> {
        int index = val.intValue();
        if (index > -1) {
           cell.updateItem(getSkinnable().getItems().get(index), false);
        } else {
           cell.updateItem(null, true);
        }
    }));
    return cell;
});

Once you include the above code, your cells are rendered with the content as below.

Below is the full code of the demo.

On a side note, Please check if you really do need to create a Skin from scratch using VirtualContainerBase? Check if there is a way to get the required implementation using existing Controls. All I say is, there will be tons of stuff you need to handle manually, and be prepared for that :)

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.Skin;
import javafx.scene.control.skin.CellSkinBase;
import javafx.scene.control.skin.VirtualContainerBase;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.List;

public class CustomVirtualFlowDemo extends Application {

    class MyControl<T> extends Control {

        private ObservableList<T> items;

        public MyControl(ObservableList<T> items) {
            this.items = items;
            setSkin(new MySkin<T>(this));
        }

        public ObservableList<T> getItems() {
            return items;
        }
    }

    class MyVirtualFlow<T> extends VirtualFlow<IndexedCell<T>> {

        @Override
        public void rebuildCells() {
            super.rebuildCells();
        }
    }

    class MySkin<T> extends VirtualContainerBase<MyControl<T>, VirtualCell<T>> {
        MyVirtualFlow flow;

        public MySkin(MyControl<T> c) {
            super(c);
            getChildren().add(getVirtualFlow());
            getVirtualFlow().setCellFactory(vf -> {
                VirtualCell cell = new VirtualCell();
                cell.indexProperty().addListener(((obs, old, val) -> {
                    int index = val.intValue();
                    if (index > -1) {
                        cell.updateItem(getSkinnable().getItems().get(index), false);
                    } else {
                        cell.updateItem(null, true);
                    }
                }));
                return cell;
            });
            getVirtualFlow().setCellCount(getSkinnable().getItems().size());
        }

        @Override
        protected VirtualFlow<VirtualCell<T>> createVirtualFlow() {
            flow = new MyVirtualFlow();
            return flow;
        }

        @Override
        protected void layoutChildren(double x, double y, double w, double h) {
            super.layoutChildren(x, y, w, h);
            flow.rebuildCells();
            flow.resizeRelocate(x, y, w, h);
        }

        @Override
        protected int getItemCount() {
            return getSkinnable().getItems().size();
        }

        @Override
        protected void updateItemCount() {

        }
    }

    class VirtualCell<T> extends IndexedCell<T> {

        private static int counter = 0;

        public VirtualCell() {
            System.out.println("Created:" + counter);
            counter++;
        }

        @Override
        protected void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
            } else {
                setText(item.toString());
            }
        }

        @Override
        protected Skin<?> createDefaultSkin() {
            return new CellSkinBase<>(this);
        }
    }

    @Override
    public void start(Stage primaryStage) {
        List<String> list = new ArrayList<>();
        for (var i = 0; i < 100; i++) {
            list.add("Item " + i);
        }
        ObservableList<String> items = FXCollections.observableArrayList(list);
        var control = new MyControl<>(items);
        VBox.setVgrow(control, Priority.ALWAYS);
        var root = new VBox(control);
        Scene scene = new Scene(root, 600, 200);
        primaryStage.setScene(scene);
        primaryStage.setTitle("CustomSkin Demo");
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
发布评论

评论列表(0)

  1. 暂无评论