Mastering Custom JavaFX Components & VirtualFlow Techniques
Mastering Custom JavaFX Components & VirtualFlow Techniques

Creating a Custom JavaFX Skin with VirtualFlow: A Step-by-Step Guide

Learn to efficiently create custom JavaFX components with VirtualFlow, manage large datasets, implement skins and cell factories.6 min


Creating custom JavaFX components allows developers to build truly personalized applications. While JavaFX provides standard controls like ListView and TableView, sometimes you need special behavior or design that these controls don’t offer. Using VirtualFlow, we can efficiently manage large data sets within custom JavaFX controls, similar to how ListView internally handles scrolling large lists of items.

In this step-by-step guide, you’ll learn exactly how to create a custom JavaFX control using VirtualFlow, including handling cell factories, skins, and troubleshooting common issues.

Setting up the MyControl Class

Our custom control, named MyControl, extends the JavaFX Control class. Extending Control lets us leverage JavaFX’s built-in skinning mechanisms, making customization easier.

First step: Let’s create the MyControl class and include an ObservableList that will hold our items.

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Control;

public class MyControl<T> extends Control {
    private ObservableList<T> items;

    public MyControl() {
        this(FXCollections.observableArrayList());
    }

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

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

As shown, our control comes prepared with an ObservableList for easy data binding and built-in JavaFX responsiveness.

Implementing the MySkin Class with VirtualFlow

Next, let’s create the skin for our control. The skin class extends VirtualContainerBase to leverage the powerful virtual scrolling capabilities provided by VirtualFlow.

Why VirtualFlow? In short, it’s like JavaFX’s way of efficiently handling large datasets by reusing view cells instead of creating new nodes for each item. Imagine it as recycling—a reusable cell approach that saves memory greatly.

Here’s how you set up the MySkin class:

import javafx.scene.control.skin.VirtualContainerBase;
import com.sun.javafx.scene.control.VirtualFlow;

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

    private final VirtualFlow<VirtualCell<T>> virtualFlow;

    public MySkin(MyControl<T> control) {
        super(control);
        virtualFlow = new VirtualFlow<>();
        virtualFlow.setCellFactory(flow -> new VirtualCell<>());

        updateItemCount();
        getChildren().add(virtualFlow);
    }

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

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

    @Override
    public void updateItemCount() {
        virtualFlow.setCellCount(getItemCount());
    }
}

A quick breakdown:

  • VirtualFlow Creation: This manages the cells shown on screen.
  • Cell Factory: A factory method, similar to those used in ListViews, that controls cell creation.
  • updateItemCount: Essential for correctly setting up and refreshing cells based on your items.

Developing the VirtualCell Class

Next up is the VirtualCell, which determines how your items appear on-screen. It extends IndexedCell, a specialized cell class that keeps track of item positions.

Here’s the VirtualCell implementation:

import javafx.scene.control.IndexedCell;

public class VirtualCell<T> extends IndexedCell<T> {
    @Override
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {
            setText(null);
            setGraphic(null);
        } else {
            setText(item.toString());
        }
    }

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

With this configuration, each cell efficiently updates and displays your ObservableList item’s content in the UI.

Running Your JavaFX Application

Now, let’s test your new control within a JavaFX application:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class MainApp extends Application {
    @Override
    public void start(Stage primaryStage) {
        // Sample data
        ObservableList<String> items = FXCollections.observableArrayList();
        for (int i = 1; i <= 1000; i++) {
            items.add("Item #" + i);
        }

        MyControl<String> myControl = new MyControl<>(items);

        VBox vbox = new VBox(myControl);
        Scene scene = new Scene(vbox, 300, 600);

        primaryStage.setScene(scene);
        primaryStage.setTitle("Custom JavaFX Control with VirtualFlow");
        primaryStage.show();
    }

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

You should now see an efficient scrolling list displaying 1000 items efficiently, thanks to VirtualFlow’s recycling capabilities.

Troubleshooting the Non-Displayed Items Issue

If items fail to display correctly, common issues are:

  • Cell Count isn’t set properly: Call updateItemCount() after list updates.
  • Items aren’t linked correctly: Verify your ObservableList is correctly updating with the UI.
  • Layout Issues: Ensure your VirtualFlow is resized and positioned within your Skin’s layoutChildren.

A common issue is neglecting the call to updateItemCount. For example, whenever you modify your items list, make sure to refresh the cell count:

control.getItems().add("New Item");
skin.updateItemCount();

Check your console output or logs, or use tools like Scenic View to diagnose problems visually.

Quick Recap & Recommendations

You’ve successfully built a highly customizable, memory-efficient JavaFX component, powered by VirtualFlow. Remember, the key steps were:

  1. Creating your Control class (MyControl).
  2. Implementing a Skin (MySkin) with VirtualFlow and Cell Factory.
  3. Creating IndexedCell-derived cells for displaying data.
  4. Ensuring correctness in item count and cell update logic.

JavaFX and VirtualFlow empower you to build responsive UI components efficiently, even for massive sets of data. For more JavaFX customizations and ideas, learn from real-world examples on Stack Overflow’s JavaFX section or explore open-source projects on GitHub.

Done building something cool? Did you run into unexpected challenges or find creative solutions? Share your journey in the comments below—let’s learn together!


Like it? Share with your friends!

Shivateja Keerthi
Hey there! I'm Shivateja Keerthi, a full-stack developer who loves diving deep into code, fixing tricky bugs, and figuring out why things break. I mainly work with JavaScript and Python, and I enjoy sharing everything I learn - especially about debugging, troubleshooting errors, and making development smoother. If you've ever struggled with weird bugs or just want to get better at coding, you're in the right place. Through my blog, I share tips, solutions, and insights to help you code smarter and debug faster. Let’s make coding less frustrating and more fun! My LinkedIn Follow Me on X

0 Comments

Your email address will not be published. Required fields are marked *