Saturday, May 29, 2010

Into the Background

Update: This post has been edited to show you how to use a gradient as the background of a ScrollView.

The new and improved support for CSS in JavaFX 1.3 includes a .scene style class which is a convenient place to initialize all sorts of application settings such as fonts and colors.  Therefore, you would also think that the .scene class is the place to specify the background color for the scene, right?  If you do, you're in good company.  And just like the rest of us, you would be wrong.

It turns out that the only place to set the fill property of the Scene is in your JavaFX code.  But there are some tricks you can use if you want the ability to control the background of your scene from a CSS file.

ScrollViews and Rectangles

The simplest way to create a background that can be styled is to use a ScrollView as the background node in your scene.  The advantage of using a ScrollView is that if the window is resized to be smaller than the content, scrollbars will automatically appear.  This is a handy feature to have in a main window.  The following code demonstrates the technique.

Stage {
    var sceneRef: Scene;
    title: "ScrollView Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}scrollViewBackground.css"
        content: [
            ScrollView {
                width: bind sceneRef.width
                height: bind sceneRef.height
                node: // Rest of your content goes here
            }
        ]
    }
}

Here I simply placed the ScrollView first in the Scene's content and bound its width and height to the size of the Scene.  Instant background.

If you then create a style sheet that sets the -fx-background property on the .scene selector, the ScrollView will pick it up automatically.

.scene {
    -fx-background: skyblue;
}

The results are pictured below.

ScrollView.png

Using a gradient as a background for the ScrollView is also possible, although it requires a little more work in your style sheet.  You need to add two new styles that redefine the default background of the ScrollView so that it incorporates a gradient rather than a solid color.

.scene {
    -fx-background: skyblue;
    -my-scene-background: linear (0%, 0%) to (0%, 100%) stops (0%, derive(-fx-background, 25%)) (100%, derive(-fx-background, -35%));
}
#sceneBackground {
    -fx-background-color: -fx-box-border, -my-scene-background;
}
#sceneBackground:focused {
    -fx-background-color: -fx-focus-color, -fx-box-border, -my-scene-background;
}

Note that I have used an ID selector for these new styles so that it overrides only the -fx-background-color of the single ScrollView that I have designated as my scene's background.  I was careful to preserve the -fx-box-border and -fx-focus-color settings of the default ScrollView styles (from caspian.css) so that, other than the gradient, the background will look exactly like a standard ScrollView.

I have also defined my background gradient in the .scene section of the style sheet so that it can be reused in both of the #sceneBackground styles. Declaring CSS "variables" like this is a feature of the JavaFX CSS support.

Once these additions to the application's style sheet have been made, you just need to add the proper ID to the ScrollView being used as the scene's background and you are done.  This is what the JavaFX code and our window look like now.

Stage {
    var sceneRef: Scene;
    title: "ScrollView Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}scrollViewBackground.css"
        content: [
            ScrollView {
                id: "sceneBackground"
                width: bind sceneRef.width
                height: bind sceneRef.height
                node: // Your content goes here...
            }
        ]
    }
}

ScrollViewGrad.png

Rectangle Backgrounds

Another lightweight option for creating a styled background is to use a simple Rectangle.

Stage {
    var sceneRef: Scene;
    title: "Rectangle Background"
    scene: sceneRef = Scene {
        width: 400
        height: 300
        stylesheets: "{__DIR__}rectangleBackground.css"
        content: [
            Rectangle {
                styleClass: "background"
                width: bind sceneRef.width
                height: bind sceneRef.height
            }
            // Rest of your content goes here
        ]
    }
}

This is similar to the ScrollView version except that a Rectangle is inserted as the first node in the Scene and its width and height are bound to the Scene's size.  Note that I assigned a styleClass of "background" to the Rectangle.  The style sheet now looks like this:

.scene {
    -my-scene-background: linear (0%, 0%) to (0%, 100%) stops (0%, derive(skyblue, 25%)) (100%, derive(skyblue,-25%));
}
.background {
    -fx-fill: -my-scene-background;
}

In the style sheet above, I've assigned a gradient to the -my-scene-background property in the .scene section and then used it to set the Rectangle's fill (-fx-fill) in the .background section which was the styleClass for the background Rectangle.  Of course, I could have just assigned the gradient to the -fx-fill property directly, but as was pointed out above, doing it this way makes that gradient available for use by other styles in the style sheet.

Regions: Imagine the Possibilities

For the more adventurous, you can also use a Region for your background.  This gives you the ability to have background colors, borders, and even images!

warningsignsmall.png

Warning!  Non-public APIs in use beyond this point. Proceed at your own risk!  (It's really not that risky)

 

All of the core controls in JavaFX 1.3 are made of Regions.  Because of this, Regions are heavily tied into the new CSS support and have a lot more styling options than any other type of node.  You can assign background colors and images, style borders, and even change the shape of the Region all from a style sheet.

But you don't have to write a control to make use of a Region.  Regions are a subclass of the public Stack container and therefore can be used anywhere in the scene graph.  Which means that a Region makes a great background for a Scene.

RegionGray.png

RegionSpring.png

webstartsmall2.gif

The top image shows a window with a gray gradient background that is typical of a default Caspian color scheme.  The bottom image shows a combination of a background color and a background image that is positioned along the bottom of the window and repeated in the x-direction to fill the window with flowers in a celebration of Spring (with apologies to all of our friends in the Southern Hemisphere).

The code and the stylesheet are shown below in their entirety.

Stage {
    var sceneRef: Scene;
    var backgroundRef: Node;
    title: "Region Background"
    scene: sceneRef = Scene {
        width: 600
        height: 300
        stylesheets: ["{__DIR__}jfxtras.css", "{__DIR__}sceneStyles.css"]
        content: [
            backgroundRef = Region {
                styleClass: "background1"
                width: bind sceneRef.width
                height: bind sceneRef.height
            }
            VBox {
                var fillWidth = LayoutInfo { hfill: true }
                spacing: 10
                padding: Insets { top: 10, right: 10, bottom: 10, left: 10 }
                content: [
                    XEtchedButton {
                        text: "Business As Usual"
                        layoutInfo: fillWidth
                        action: function () {
                            backgroundRef.styleClass = "background1"
                        }
                    }
                    XEtchedButton {
                        text: "Yay, It's Spring!"
                        layoutInfo: fillWidth
                        action: function () {
                            backgroundRef.styleClass = "background2"
                        }
                    }
                ]
            }
        ]
    }
}
.scene {
    -fx-font: 24pt "Amble Condensed"
}

 

.background1 {     -fx-color: lightgray;     -fx-background-color: -fx-body-color;     -fx-background-image: null; }

.background2 { -fx-background-color: #81E2F7;     -fx-background-image: "flowersbluesky.png";     -fx-background-position: left bottom;     -fx-background-repeat: repeat-x; }

Regions definitely provide the most flexibility and power as the background of a Scene.  The only drawback is that Region is not part of the JavaFX runtime's public API.  As such, there is always the possibility that the class may change or disappear in future releases.  In this case, I think the fact that the core controls now rely on Regions means that they will be around in one form or another for a long time.