Posts Tagged ‘Scrolling’

Implementing a Scroll View in JavaFX

August 30th, 2009

JavaFX does not seem to have support for scroll views. So, you have to
implement it on your own. Here is how I started implementing my own scroll view supporting
vertical scrolling:

package scrollviewdemo;

import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.util.Math;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Paint;

public class ScrollView extends CustomNode {
    public-init var node: Node;
    public var width: Float;
    public var height: Float;

    var group: Group;
    var gap = bind group.layoutBounds.height - height;

    def scrollBar: ScrollBar = ScrollBar {
        translateX: bind width - scrollBar.width
        min: 0
        max: bind Math.max(0, gap)
        visible: bind gap > 0
        vertical: true
        height: bind height
    }

    override function create() {
        Group {
            content: [
                Group {
                    content: [
                        group = Group {
                            translateY: bind - scrollBar.value
                            content: [node]
                        }
                    ]
                    clip: Rectangle {
                        width: bind width - {if (gap > 0) scrollBar.width else 0}
                        height: bind height
                    }
                },
                scrollBar
            ]
        }
    }
}

This is how it can be used in a simple application:

var stage: Stage = Stage {
    title: "Application title"
    width: 200
    height: 200
    scene: Scene {
        content: [
            ScrollView {
                width: bind stage.scene.width
                height: bind stage.scene.height
                node: VBox {
                    content: for (i in [0..20]) ImageView {
                        image: Image {
                            // some picture
                            url: "{__DIR__}pic.png"
                        }
                    }
                }
            }
        ]
    }
}

To support mouse scroll wheel I added the following to the ScrollView class:

    override var onMouseWheelMoved = function(event) {
        // multiplying by 4 makes the scrolling faster and still smooth
        scrollBar.value += event.wheelRotation * 4;
    }

However, as it turned out, transparent components don’t receive mouse events, so the mouse
wheel worked only when the mouse pointer was on top of a non-transparent object. So, to work
around this, I had to fill the scroll view background using an opaque rectangle. The resulting
ScrollView component looks like this:

package scrollviewdemo;

import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.util.Math;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Paint;

public class ScrollView extends CustomNode {
    public-init var node: Node;
    public var width: Float;
    public var height: Float;
    public var fill: Paint;

    var group: Group;
    var gap = bind group.layoutBounds.height - height;

    def scrollBar: ScrollBar = ScrollBar {
        translateX: bind width - scrollBar.width
        min: 0
        max: bind Math.max(0, gap)
        visible: bind gap > 0
        vertical: true
        height: bind height
    }

    override function create() {
        Group {
            content: [
                Rectangle {
                    fill: bind fill
                    width: bind width - {if (gap > 0) scrollBar.width else 0}
                    height: bind height
                },
                Group {
                    content: [
                        group = Group {
                            translateY: bind - scrollBar.value
                            content: [node]
                        }
                    ]
                    clip: Rectangle {
                        width: bind width - {if (gap > 0) scrollBar.width else 0}
                        height: bind height
                    }
                },
                scrollBar
            ]
        }
    }

    override var onMouseWheelMoved = function(event) {
        // multiplying by 4 makes the scrolling faster and still smooth
        scrollBar.value += event.wheelRotation * 4;
    }
}

And the following line should be added to the Main.fx to make it work:

package scrollviewdemo;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;

var stage: Stage = Stage {
    title: "Application title"
    width: 200
    height: 200
    scene: Scene {
        content: [
            ScrollView {
                width: bind stage.scene.width
                height: bind stage.scene.height
                fill: Color.WHITE
                node: VBox {
                    content: for (i in [0..20]) ImageView {
                        image: Image {
                            // some picture
                            url: "{__DIR__}pic.png"
                        }
                    }
                }
            }
        ]
    }
}