JavaFX : Centering Controls

Across the blogosphere, I found a post on how to center controls by using binding. The proposed method certainly works, but there is a catch.

When you are doing your layout by binding the layoutX and layoutY properties of your controls, you may experience a degradation of performance while resizing or scrolling. This will probably not be noticeable with only a handful of controls/shapes on screen. If however you start drawing hundreds of shapes/controls with this method, you will certainly see the slowdown. Let’s see why…

Currently, I am involved in building an application that visualizes railway tracks and train positions on screen. This visualization literally consists of hundreds of lines, circles and triangles. At first we also used binding to perform the layout of these shapes, but eventually scrolling and zooming became very slow.

Eventually, we decided to switch the layout from binding to a Panel‘s onLayout function. This performed a lot better. The reason this performs better lies in the different timings of binding and calls to onLayout.

A bound variable will be updated any time its source variable is updated. If you bind a lot of controls’ layout to the size of a Scene, you will eventually end up with something called bindstorm (term coined by Jim Conners).

The JavaFX platform performs layout functions only periodically, on so-called pulses. All the JavaFX layout container classes have optimizations to perform their layout as efficiently as possible, during these pulses. Because resizing will trigger a new layout on the next pulse, layouting is done less frequently than bind updates, causing these actions to be more smoothly and less cpu-hungry.

We certainly saw an increase in performance when we switched to the onLayout method. However, I may have misinterpreted the cause. If I am wrong about the way these pulses work, I invite someone from the JavaFX team to correct me in a comment. :)

Now, coming back to the post I referred to in the first section of my rambling, let’s talk about centering controls. Knowing the difference between bind and the way layout containers work, I think it is better to let a highly optimized container handle the centering of your controls. There are a number of different containers you can use for this.

The code sample below is derived from the bind-example, only this time notice the Stack and the LayoutInfo objects, which make sure the Label remains centered.

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.TextOrigin;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.layout.Stack;
import javafx.scene.layout.LayoutInfo;
import javafx.scene.text.Text;
import javafx.geometry.HPos;
import javafx.geometry.VPos;

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: "Look ma! I centered this text!"
        layoutInfo: LayoutInfo {
          hpos: HPos.CENTER
          vpos: VPos.CENTER
        } // end LayoutInfo
      } // end Text
    } // end Stack
  } // end Scene
} // end Stage

The only binding I used in this example, is the size of the Stack. If the user now resizes the window, binding makes sure the Stack is resized. JavaFX then requests the Stack to redo its layout on the next pulse. Due to the vpos and hpos hints, the Stack knows that this control must be centered. All calculations will then also be handled by the Stack, which takes everything like effects, translations and transforms into account.

Happy layouting!

–JH

Posted in JavaFX, Tips and Tricks and tagged , , .