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

java - How to set items to JavaFX VirtualFlow? - Stack Overflow

programmeradmin8浏览0评论

I know two VirtualFlow implementations—one native to JavaFX and another from the Flowless library. I know how to use Flowless VirtuaFlow, but now, I would like to try using the JavaFX VirtualFlow. However, I don't understand how to pass an ObservableList<T> items to this VirtualFlow. Here is my code:

public class NewMain extends Application {

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

    }

    private final VirtualFlow<VirtualCell<String>>  virtualFlow = new VirtualFlow<>();

    @Override
    public void start(Stage primaryStage) {
        this.virtualFlow.setCellFactory(v -> new VirtualCell<>());
        List<String> list = new ArrayList<>();
        for (var i = 0; i < 100; i++) {
            list.add("Item " + i);
        }
        ObservableList<String> items = FXCollections.observableArrayList(list);
        //this.virtualFlow.set items  ????

        var root = new VBox(this.virtualFlow);
        Scene scene = new Scene(root, 600, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

Could anyone say how to do it?

I know two VirtualFlow implementations—one native to JavaFX and another from the Flowless library. I know how to use Flowless VirtuaFlow, but now, I would like to try using the JavaFX VirtualFlow. However, I don't understand how to pass an ObservableList<T> items to this VirtualFlow. Here is my code:

public class NewMain extends Application {

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

    }

    private final VirtualFlow<VirtualCell<String>>  virtualFlow = new VirtualFlow<>();

    @Override
    public void start(Stage primaryStage) {
        this.virtualFlow.setCellFactory(v -> new VirtualCell<>());
        List<String> list = new ArrayList<>();
        for (var i = 0; i < 100; i++) {
            list.add("Item " + i);
        }
        ObservableList<String> items = FXCollections.observableArrayList(list);
        //this.virtualFlow.set items  ????

        var root = new VBox(this.virtualFlow);
        Scene scene = new Scene(root, 600, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

Could anyone say how to do it?

Share Improve this question edited Mar 14 at 16:53 SilverCube asked Mar 14 at 16:45 SilverCubeSilverCube 6443 silver badges12 bronze badges 0
Add a comment  | 

3 Answers 3

Reset to default 6

VirtualFlow doesn't directly accept ObservableList.

You should extend ListCell<> to handle string directly.

public class VirtualCell extends IndexedCell<String> {
    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || item == null) {
            setText(null);
        } else {
            setText(item);
        }
    }

    // it gave me a missing skin error, this fixed it.
    @Override
    protected Skin<?> createDefaultSkin() {
        return new CellSkinBase<>(this);
    }
}

the VirtCell class extends IndexedCell and overrides the updateItem method to properly display the item.

Then you need to set cell factory:

@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);

    virtualFlow.setCellFactory(vf -> new VirtualCell());
    virtualFlow.setCellCount(items.size());

    virtualFlow.setCellFactory(vf -> new VirtualCell() {
        @Override
        public void updateIndex(int index) {
            super.updateIndex(index);
            if (index >= 0 && index < items.size()) {
                updateItem(items.get(index), false);
            } else {
                updateItem(null, true);
            }
        }
    });

    var root = new VBox(this.virtualFlow);
    Scene scene = new Scene(root, 600, 200);
    primaryStage.setScene(scene);
    primaryStage.show();
}

The intended design usage for VirtualFlow is in a Skin implementation, with the flow itself managed via a Skin that extends VirtualContainerBase.

ControlsFX provides an example of usage with a virtualized GridViewSkin that extends VirtualContainerBase. That class registers listeners on the item list maintained for the GridView, which is the control being skinned.

It is the GridView which maintains a modifiable list of items. The GridViewSkin listens for changes to the modifiable list to notify the virtual flow of change events through the virtual flow API. The virtual flow itself does not do that work.

Built-in controls like ListView, and its default ListViewSkin, work the same way as the GridView example provided above (they are just more complicated because they provide additional functionality).

One of the intended purposes of the skin implementation is to separate various look and feel aspects of the control API from the functional programming API required to use the control. A control containing a list of items includes a reference to the observable list of items, but the skin and virtual container classes do not. This is a separation of concerns implemented by having the observable item list referencein the control code rather than the skin code and the skin (and the virtualized container and flow it uses in its implementation) just observes the list using listeners and reacts to changes on it.

You could decide to follow the same pattern with skinned controls or not as you see fit. In particular, the skinning pattern may not apply if you aren't interested in keeping the separation of concerns and instead are implementing a tightly coupled control with a single interface for the control functions and look and feel (e.g. you don't have a Skin implementation). However, the virtual container API wasn't designed to be used that way, so you may have some overhead and additional difficulties that you will need to address if you do use it that way.

TL;DR: It's not the responsibility of the VirtualFlow to handle the items. It only handles the cells and their indexes. It's the responsibility of the cell to grab the correct item based on its index, and to call its own updateItem method when appropriate (typically only if the cell determines the item actually changed, as updateItem may be expensive).


The standard VirtualFlow is not "meant" to be used directly as a layout. Rather, it's "meant" to be a building block for creating virtualized controls. In JavaFX, controls are supposed to follow an MVC architecture. You have:

  • The Control. This would be the "model".

  • The Skin. This would be the "view".

  • The "behavior". This would be the "controller". However, there's no public API for this part of the design.

This allows you to use different skin/behavior implementations for the same control. In other words, this allows third-party customization.

A VirtualFlow is meant to be part of the Skin. As such, its primary concern is displaying the cells (i.e., the view). This shows up in the design by not defining a way to specify the list of items that are displayed in the cells. The items list belongs in the Control (i.e., the model).

For example, look at the ListView and ListCell classes. The model of the these two controls should work regardless of the skin implementation. This makes it the list view's responsibility to hold the list of items, and the cell's responsibility to grab the appropriate item from its list view based on its index. And the cell calls its own updateItem method when needed (note that method is protected). All the VirtualFlow does, besides the relatively complex task of displaying a "virtualized" list of cells, is update each cell's index as needed, because the indexes of the cells depend on which cells are being displayed.

The accepted answer gives a minimal example of a cell that updates its item when its index changes. Note this is likely not enough, as the list of items can typically be modified independently of the view changing. This includes updating or replacing an item without structurally modifying the list. For more complete examples, check out ListCell.java and TableCell.java.

To use VirtualFlow, you just tell it how to create the cells and how many cells there are via its cellFactory and cellCount properties, respectively. Then you have to call the rebuildCells(), reconfigureCells() and recreateCells() methods when appropriate. Note those methods are protected, so you'll need to subclass VirtualFlow. And if you're creating a custom virtualized control, then consider extending VirtualContainerBase for your skin class. For a real-world example of all this, see ListViewSkin.java.

Note you are not required to create an entire control and skin implementation just to use VirtualFlow. But I still recommend against using it directly as a layout. In this case, consider hiding it behind a subclass of Region. Though a VirtualFlow must work with IndexedCell which is a control, so there will still be some of the MVC architecture involved.

发布评论

评论列表(0)

  1. 暂无评论