Node
class has some methods that can be used to define a transformation on it, and you can combine transformations to rotate and scale a node, for example. Table 4-1 contains an overview of the methods that can be used to define transformations on a node.HBox
. Because in the scene graph a transformation affects all nodes under the transformed one, the buttons in this application will be transformed as well.HBox
and the buttons in the scene graph.Shape3D
class is the base class for three-dimensional nodes. By using these nodes, you can create a three-dimensional view, as shown in Figure 4-3.javafx.scene.transform
package. These classes define the different transformation types that can be applied to a node. As a base class, the abstract class Transform
defines the complete logic and mathematical material that is needed to render a transformation. You’ll also find a set of classes that derive the Transform
class in the javafx.scene.transform
package. These classes define different transformation types. Figure 4-4 shows an overview of the class hierarchy.Translate, Shear, Rotate
, and Scale
classes should be used when a simple transformation will be executed. These classes are designed for the special requirements of the different transformation types. If you want to create a general affine transformation, you can use the Affine
class. This class defines a general affine transformation, and all the other mentioned transformations are special types of an affine transformation. An affine transformation can be generally described as a transformation between two affine spaces that preserves points, straight lines, and planes. Matrix operations can be performed on instances of the Affine
class, allowing this class to provide a better fit for complex transformations.StackPane
control that will be used to visualize different transforms. The second pane is an HBox
called menu. This pane contains buttons that will handle the action of the application. Whenever one of these buttons is clicked, a list of Transform
instances will be executed on a rectangle, which will be shown in the StackPane
node. Each button defines a different order of the transforms, and the result of the rectangle will be completely different. This depends on the order in which the transforms are executed on the rectangle. To visualize the steps of transformations, the useTransform(…)
method is used. This method adds a rectangle for each transformation step onscreen. Figure 4-5 shows the different results of the transforms. In the screen on the left, the rectangle is rotated, and after the rotation, it is translated. The rotation always has the top-left corner of the rectangle as its center. A rotation of a node affects its complete coordination system. Therefore, the translation in the left screen is executed on a rotated coordination system. In the screen in the middle, the translation is performed before the rotation of the rectangle. In both examples, the same transforms are executed on the rectangle, but the resulting location of the rectangle is different because of the changed order of the transforms. The rightmost screen adds a shearing to the list of transforms.NOTE
In JavaFX, each transformation is represented by a matrix. Each point in the node is multiplied by the matrix to determine its new position. Matrix multiplication is not associative, so changing the order in which the transforms are applied will produce different results, as shown in Figure 4-5. |
Node
class provides directly. It is important to know that the transforms defined by these methods are executed in a given order: translate, scale, and rotate. Additionally, it is important that the pivot of the rotation is not the top-left corner of a node. When using the internal rotation of a node, the pivot point is computed as the center of the node. Figure 4-6 shows an example of a node that is rotated by using the setRotate(45)
method. As you can see, the rotation pivot is a different one from in the previous example. All additional transformations that are set on a node by using the transforms property will be applied before the inner transforms of the node are executed. Whenever both types of transformation are mixed, developers have to know this order to avoid any unwanted behavior. It is not necessary to mix the different approaches. As already mentioned, it is a best practice to use the internal methods of a node to transform it.Pane
class and some concrete implementations such as the StackPane
were shown. Figure 4-7 shows a short overview of the class hierarchy. The four pane types (StackPane, BorderPane, AnchorPane
, and FlowPane
) that are shown in the figure are only examples.Pane
class. In the following example, a special layout is needed that sorts all child nodes by the preferred width of the node. It should act like an HBox
, but all nodes should appear in order of their width instead of the default child order. The following code block shows how a custom pane that implements this feature should look in a first iteration:layoutChildren(…)
method and all the methods that compute the size of the pane. Let’s take a look at all the compute …(…)
methods first. Most developers who have worked with UI toolkits know the three different sizes that can be defined for a component: minimum, maximum, and preferred. In JavaFX, all of these types are supported, and the scene graph API offers methods to calculate the width and height of these sizes. A great benefit of these methods is that you can calculate the width depending on the height, and vice versa. I’ll cover this further a bit later, but let’s first consider the simple calculations that don’t depend on each other. All the nodes that are wrapped in the pane should fit in its bounds. To do so, you calculate the preferred width and height of the pane using the methods computePrefHeight(…)
and computePrefWidth(…)
. Because all child nodes should be ordered in a horizontal direction, the preferred height of the pane is equal to the height of the highest child node. This is done in the computePrefHeight(…)
method by simply iterating over all the children and finding the maximum height with the help of Math.max(…)
. The preferred width of the pane is equal to the sum of all the child nodes’ widths. By calculating this, the preferred height of the pane is also calculated. These values are needed to lay out the parent region of the pane once it is added to a scene graph. Each pane in JavaFX should use the calculated sizes (maximum, minimum, and preferred) to calculate its own size. In the given example, you want the pane to always be as big as its children. To achieve this, you can simply override the computeMin …(…)
and computeMax…(…)
methods by returning the preferred size. Other pane implementations, such as the StackPane
, use Double.MAX_VALUE
to define the maximum size. Here, the pane can be bigger than its children, for example.layoutChildren()
methods that can be overridden by a custom pane. In this example, you create a sorted list of all child nodes and then define the bounds of them. Here, you use the preferred size of a child node and calculate only the X position of the nodes. By doing this, all nodes will appear in a sorted line. To place a node in a pane or region, you can use the layoutInArea(…)
method, which has a default implementation to define the bounds of a child. You will take a deeper look at this mechanism later in the chapter. First, you need to know only the first five parameters of this method: the child nodes and its calculated bounds that are defined by its x-position, y-position, width, and height. For the last three parameters, default values are used here.SortedPane
class, you can simply use it in an application. The following code defines a demo application that uses the SortedPane
class. Figure 4-8 shows how the example should look onscreen.SortedPane
appear in a sorted order depending on its visual width. The given example contains a good overview of the basic methods that need to be known to create a custom Pane
class, but the layout API of JavaFX offers many more possibilities. In a professional application, the shown mechanisms are not enough to define a pane that fits completely into any JavaFX application. By default, JavaFX provides properties to define the padding and insets of a pane. Additionally, a border can be set to a pane. This border should not hide the underlying child nodes. To strictly follow all the given definitions that are part of the JavaFX layout API, you will need the information in the next section.snapToPixel
property that is defined in the Region
class. This Boolean
property is set true by default and will adjust the position, spacing, and size of all children of the node to pixel boundaries. Once this property is set to false, a fractional alignment is used, which may lead to “fuzzy-looking” borders.Region
class. This class defines some special properties and behaviors of how it appears onscreen. You need to know these features to lay out custom controls and panes in the right way. These definitions are not reinvented by JavaFX, though. The CSS3 specification of the World Wide Web Consortium (W3C) for backgrounds and borders is used internally. You can find the complete specification at www.w3.org/TR/2012/CR-css3-background-20120724/.HBox
, for example, has the static method setMargin(…)
to define a margin for a child node inside an HBox
instance.SortedPane
class that was created earlier doesn’t use the given insets when laying out all child nodes or computing the size of the pane. To fit in the defined specifications, some additions are needed. The following code contains a new version of the SortedPane
that internally uses the border and padding insets for calculation:insets
property of a region is a union of the border area and the padding area. Therefore, this property can be used to calculate the content area of the SortedPane
. All child nodes of the pane will now be rendered inside this area. The size of the border and the custom padding of the pane are added to the calculation. In the computePrefHeight(…)
and computePrefWidth(…)
methods, the insets
property is used to calculate the preferred size. The particular values of the property (top/bottom for the height and left/right for the width) are added to the earlier calculated values. As a result, the pane will be as big is its content in union with the border and padding. In the layoutChildren()
method, the insets
property is used too. The y-position of all components is equal to the height of the insets area, and the x-position of the first child node is equal to the width of the left insets. In JavaFX, different values can be defined for the top, bottom, right, and left of the padding or the border. You can have a border that has a thickness of 3 pixels on the left side and 20 pixels on the right side, for example.SortedPane
, you need to implement one additional feature. In the first demo, all child nodes are rendered without any spacing between them. This should be variable. To do so, a spacing property is needed. The property API was mentioned earlier in this book, and some properties have already been used in the examples, but this is the first point where a custom property is added to a class. The following example shows the final version of the SortedPane
. Here, a DoubleProperty
is added to define the spacing between all child components. The property is defined because it is a best practice in JavaFX. All internal classes of JavaFX define properties, as shown in this example.SortedPane
and adds padding and spacing to the pane. Figure 4-10 shows how the application will look onscreen.setPrefHeight(…)
or setMinWidth(…)
. Once a value is set, it will be used instead of calculating a value with the help of the shown methods. In addition, you can use constants to add flexibility: If the preferred width is set by using the setPrefWidth(…)
method, it can be simply unset by using the Region.USE_COMPUTED_SIZE
constant. After calling setPrefWidth(USE_COMPUTED_SIZE)
, the layout mechanism will use the computePrefWidth(…)
method again. If the minimum or maximum value should be equal to the preferred one, you can use the Region.USE_PREF_SIZE
flag. By passing this flag to setMinWidth(…)
, the layout mechanism will never call computeMinWidth(…)
. Instead, computePrefWidth(…)
will be used. By using these flags, some of the methods of the SortedPane
class don’t need to be overwritten. The constructor of the class should simply include the following code snippet:computeMinWith(…), computeMinHeight(…), computeMaxWidth(…),
and computeMaxHeight(…)
methods. As a result, the code will be much smaller. This effect can simply be negated by using the Region.USE_COMPUTED_SIZE flag
.PropertyChangeEvent
needs to be fired in each setter. This isn’t needed in JavaFX, thanks to the property API, which offers a complete set of default properties.SimpleDoubleProperty
class is used. This class is the default implementation for a property that holds a double value. The property instance offers read and write access to the property. In addition to this class, a complete set of default implementations is part of the API, such as SimpleStringProperty
. JavaFX contains property classes for all primitive data types. For all value types where no default implementation is defined, the SimpleObjectProperty<T>
class can be used. This class uses generics to define the type of its content. If only read access is allowed for a value, developers can use the ReadOnlyProperty
classes such as ReadOnlyStringProperty
. To add perfect property support to the SortedPane
class, some additional changes are needed. Whenever the spacing property is set, the pane will not automatically re-layout its content. To do so, the requestLayout()
methods that start a redo need to be called. This could be done by adding a listener to them, as shown in the following code:spacingProperty()
method, the instance of the property will be created only when it is needed. As long as only the getSpacing()
method is called, for example, the property instance will never be created. This is done to protect resources. A property instance contains some fields that will need memory when a new instance is created. Additionally, a listener is created in the code snippet. This will allocate memory too. This can be avoided by using the invalidated()
method of the SimpleDoubleProperty
class. This method will be called whenever a new value is set to the property. By overwriting this method as shown in the following code snippet, the requestLayout()
method will be called whenever a new spacing value is set. As a result, the property will be created only when it is needed.SortedPane
instance at runtime. Changing the value of the slider will have direct visual feedback because the spacing between the child nodes of the SortedPane
will change onscreen.layoutInArea(…)
method is used internally in the SortedPane
example and wasn’t described until now. Since I described the basic mechanisms of layout, I’ll discuss these more specific aspects now. These mechanisms will help to create complex custom layout algorithms in JavaFX.Region
instance. To do so, a parameter is defined in the method header. The computePrefWidth(double height)
method, for example, adds a height parameter. The method can then be used to calculate a width that depends on the given height. The size of a region can have only one dependency, of course. The height will depend on the width, or the width will depend on the height. Both dependencies can’t logically be handled. To do that, you must define an additional value in the Node
class: content bias. This value defines the node’s resizing bias for layout purposes. By default, the getContentBias()
method returns null
. This means there is no defined ratio; –1
will be used for all parameters of the compute …(…)
methods in this case. If the getContentBias()
method returns a vertical or horizontal orientation, a ratio is defined, and concrete values are passed to the compute…(…)
methods.computePrefWidth(…)
method will be called first. Here, –1
is passed to the method, and a default width will be calculated. Based on the maximum, minimum, and preferred widths of the node, the parent region will set a width that will be used. This calculated width will now be used to define the height of the node. To do so, the computePrefHeigth(…)
method is called with the calculated width. The implementation of the method can calculate a preferred width that fits the given height. This described behavior is used in the Label
control of JavaFX to add page layout, for example. To gain a better understanding of the mechanism, the following example will use aspect ratio.Region
class and will always have a surface area of a given value. Because the surface area is given, the width of the node depends on its height, or vice versa, depending on its content bias. To show all different variations, the content bias of the node needs to be changeable too. The following class implements the Region
class that supports all the necessary features:contentBias
and surfaceArea
, two additional properties are added to the class by using the property API. The AreaRegion
class overrides the getContentBias()
methods and returns the content bias that is defined by the included property. All compute…(…)
methods that are needed to calculate the size of the node are overridden. In this method, the aspect ratio is defined. This can be easily done by adding an if-else statement to the code. When the internal content bias is set to Orientation.VERTICAL
, all methods that compute a width will be called with a given height. Otherwise, the height is –1
, and a default value can be used. Whenever the content bias or the surface area is set, the parent region of the instance needs to be laid out. To do this, the requestLayout()
method is called in the invalidated()
method of the properties.AreaRegion
class and adds some controls to change its behavior:AreaRegion
is displayed. The CSS string defines a black border and a blue background for the node. This is done here only to render the AreaRegion
onscreen in a special color. CSS will be explained later in the book.AreaRegion
can be changed by two buttons. Additionally, the surface area can be set by a slider. Here, the application takes advantage of the property API’s benefits by simply binding the surface area to the value of the slider. Because this is a dynamic example, no screenshot is shown here. When running the demo and changing some of its input values, you can see how the different content bias will affect the layout of the AreaRegion
inside the StackPane
. By simply changing the size of the application window, the bounds of the AreaRegion
will change by always fitting the given surface area.layoutInArea(…)
method. By using the method inside the layoutChildren()
method, developers can simply define an area in which a defined child component should be laid out. Internally, the method will set the bounds of the child node by calling Node.resize(double width, double height)
and Node.relocate(double x, double y)
. In most cases, a developer should use either the layoutInArea(…)
or positionInArea(…)
method of the Region
class. The difference between these two methods is simple: positionInArea(…)
never changes the size of a child node; it only sets the location. In most cases, layoutInArea(…)
will be used. To prevent the resizing of a node, the Node.isResizable()
method can be overwritten. As a result, a node can control this behavior by itself. A button returns true
here because the size of a button can change. A rectangle returns false
here. If a rectangle is defined with a given size, such as is done with new Rectangle(40,40)
, it should not change its size.javafx.geometry
package. Some of them, such as the Orientation
enum, have already been used in the demo applications in this chapter. Table 4-2 describes the enums that are part of this package.Dimension2D or Point3D
. Whenever it makes sense, these classes should be used in code.Pane
class, as shown in the previous example, constraints can be useful. Constraints describe the layout properties of a child node inside a specific pane in order to define a more specialized layout of the node’s children. More complex panes such as the GridPane
make extensive use of this feature. A simpler example is the margin inside an HBox
. To apply a custom margin on a child node of an HBox
, you can call the static method setMargin(Node child, Insets value)
and define the child node and its special margin. An instance of HBox
will use this margin whenever the child node is laid out. Custom constraints can be easily added to a pane by using the two static methods: Pane.setConstraint(Node node, Object key, Object value) and Pane.getConstraint(Node node, Object key).
Region
and Pane
classes. Additionally, transforms can be applied to a node. Furthermore, you can mix these two APIs without any problems. The following example shows how a child of the SortedPane
can be transformed:SortedPane
. Figure 4-11 shows how the application will be rendered onscreen. As you can see, the Hello World button is transformed after the layout of the SortedPane
is done. Additionally, another feature of the scene graph is shown here: A child node can be rendered outside the bounds of its parent. This isn’t possible in older UI toolkits such as Swing where each container has a defined canvas and can’t render any content outside this canvas. Because a container renders all its child components in Swing, any content that is not inside the area of the canvas will be cut off on the screen because it is never rendered on it.getBoundsInLocal()
method. Once a transform is applied on the node, the bounds onscreen will change, but sometimes you want to access the bounds of a transformed node, so you can use the getBoundsInParent()
method. Figure 4-12 shows an example of a rectangle that is transformed and the different results of the two methods.3.16.79.33