Thursday, 2 September 2010

How to centre nodes using bind ??

Hi guys,
Haven't been here for a while... Fortunately I came back and I will drop some knowledge today.
I would like to describe one of the techniques used to centre nodes within the scene. In the following application I will use only bind statements, which enables me to keep the text centred even when the window has been resized.

Here you have the window before resizing:


... and here after the window has been resized:




As you can see the text is still in the centre position. It is achieved through using the bind statement. Below I'll reveal the "magic" in the source code:


import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;

Stage {
    title: "Centering Nodes"
    var scene: Scene;
    scene: scene = Scene {
        width: 400
        height: 100
        var textNode: Text;
        content: textNode = Text {
                layoutX: bind (scene.width - textNode.layoutBounds.width) / 2
                layoutY: bind (scene.height - textNode.layoutBounds.height) / 2
                textOrigin: TextOrigin.TOP
                font : Font.font(null, FontWeight.BOLD, 16)
                content: "www.javafxscripting.blogspot.com"
            } // end Text
    } // end Scene
} // end Stage


Most of the code should be clear for you guys as it was described in previous posts. If you cannot understand scroll below and read the posts. I will concentrate on the following piece of code, which keeps the text centred:


Text {
                layoutX: bind (scene.width - textNode.layoutBounds.width) / 2
                layoutY: bind (scene.height - textNode.layoutBounds.height) / 2
                textOrigin: TextOrigin.TOP
                font : Font.font(null, FontWeight.BOLD, 16)
                content: "www.javafxscripting.blogspot.com"
} // end Text


layoutX and layoutY is responsible for positioning the node at the proper coordinates. In this case  they are both calculated. As you can remember bind "glues" two variables together. In the listing both layoutX and layoutY is dependent on the product of:

 (scene.width - textNode.layoutBounds.width) / 2

and

(scene.height - textNode.layoutBounds.height) / 2.

Those are in turn dependent on the width and height of the scene. Therefore, every time the we resize the window the values change and the position of the text is calculated again and text moved to the appropriate position. Simple...

I know what you wan to ask now. What the hell is textNode.layoutBounds.height and textNode.layoutBounds.width??  
The answer is very simple as well. textNode is a reference to the text node(see listing above). layoutBounds is responsible for holding information about the borders of the particular node. This example uses the width and height in order to calculate the position in which drawing should begin.

That's all for now. Hope you all can work something out from the example above. If not.... why not ask a question?

1 comment:

  1. Hi!

    I read your post and can't argue against it, because it works. :) However, I have a consideration you may find interesting.

    The fact is you are binding a control's layout properties. This on its own is not bad. In a project I am currently working on, we ran into a slight problem with this approach. When you have a lot of controls/shapes (in the hundres) on screen, whose layout properties are bound, you may end up with some poor performance. This is what Jim Conners has coined as 'bindstorm'.

    The problem behind this is the way binds are treated. Any update to a variable that is bound to (like the scene size), may cause a multitude of calculations to be fired. All of these calculations happen in the event-thread and will slow down the graphical performance of your application.

    To counter this, I think it is better to allow the JavaFX platform to handle the centering of the controls. After all, they optimize the crap out of their layout containers. Updates to layouts are not performed directly after a change in sizes, but rather on the next 'pulse'. (A puls is just before JavaFX decides to re-render the screen).

    So instead of binding, I would suggest the following piece of code, which does the same as your code, but leaves the layout to a JavaFX Stack layout container in this case.

    Stage {
    title: "Centering Nodes"
    var scene: Scene;
    scene: scene = Scene {
    width: 400
    height: 100
    content: Stack {
    layoutInfo: LayoutInfo {
    width: bind scene.width
    height: bind scene.height
    } // end LayoutInfo
    content: Text {
    textOrigin: TextOrigin.TOP
    font : Font.font(null, FontWeight.BOLD, 16)
    content: "www.javafxscripting.blogspot.com"
    layoutInfo: LayoutInfo {
    hpos: HPos.CENTER
    vpos: VPos.CENTER
    } // end LayoutInfo
    } // end Text
    } // end Stack
    } // end Scene
    } // end Stage


    This code only binds one variable: the Stack's size. Any layout changes caused by this will be handled by the Stack and JavaFX's optimizations. In our project, this severely reduced the slow-down when resizing the window.

    Cheers!

    --JH (www.jhkuperus.nl)

    ReplyDelete