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 |1 Answer
Reset to default 3Firstly, 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);
}
}
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 aFlowPane
. You may have to implement this entirely from scratch to get what you want (though you can study the source code ofVirtualFlow
for help). – Slaw Commented Mar 17 at 6:42