Control
class works. In addition, you’ll create your first custom control, which will use some exciting JavaFX features.Control
class and classes that inherit from it, like Button
and CheckBox
. All these classes define the model of a control in JavaFX, which means all properties of a control are encapsulated in the Control
class (Chapters 5 and 6 described the architecture and properties of Control
). To render a Control
instance on the screen, you’re missing some important information, though. Specifically, how are all the different components of the control laid out in its bounds?Control
instance, and the skin defines the look and feel of the control. As mentioned in Chapter 3 (see Figure 3-6 for an example for the CheckBox
), all controls are defined as a composition of basic JavaFX Node
instances. Each basic control is created by using JavaFX Shape
objects such as Text, Rectangle, or Line
. All these basic nodes are managed in the skin of a control. This part defines the look and layout of a control. In addition, the skin manages the “feel,” or behavior, of a control; in other words, the skin defines the events and actions that will be called and handled when the user does some input action. If a user presses CTRL-A in a TextField
instance, the complete text in the TextField
will be selected, for example. Figure 10-1 shows the structure of a JavaFX control.Skin<C extends Skinnable>
interface. The interface defines the visual representation of user interface controls and defines three methods, as described in Table 10-1.Skinnable
. The Skinnable
instance will be returned by the getSkinnable()
method. Skinnable
is an interface that defines the basic methods for an object that can hold a Skin
instance. The Control
class in JavaFX implements the Skinnable
interface, and therefore each control can hold a skin. Almost all skins in JavaFX extend the SkinBase<C extends Control>
class that implements the Skin
interface. Figure 10-2 shows the inheritance of all the named classes and interfaces in an UML diagram.Control
class, and its skin implementation will normally extend the SkinBase
class. The two classes will look like the following code snippet:skin
property of the Control
class, and the control can be referenced in a skin by calling the getSkinnable()
method. The skin is designed as a black box from the perspective of the control. Normally, it will listen only to changes in the state of the control and handle them.NOTE
The Skin classes of the basic JavaFX controls don’t extend SkinBase directly. All these skins are part of the private API and extend the com.sun.javafx.scene.control.skin.BehaviorSkinBase class. As you can see in the package, this class is part of the private API, too. So, you shouldn’t extend this class for custom controls. Instead, the SkinBase class should be extended directly. The BehaviorSkinBase class is used internally to separate the behavior and the look of a control. If you want this architecture for custom controls, an additional custom basic Skin class is needed. Maybe the BehaviorSkinBase class will become part of the public API in a future version of JavaFX. Until then, it shouldn’t be used. |
SkinBase
class, so you will now take a deeper look at this class.SkinBase
class defines a basic implementation of a skin for a JavaFX control. As described earlier, the Skin
interface can be used for each Skinnable
instance. The SkinBase
class directly works with the Control
class that implements the Skinnable
interface, and therefore SkinBase
contains some useful basic implementations and methods when skinning a Control
instance. Therefore, each skin that is used to skin a control should extend this class
.SkinBase
class defines a set of methods containing some basic implementation; it can be simply overridden in concrete implementations to define a custom look and behavior of a control. Table 10-2 contains the methods of the SkinBase
class.SkinBase
class and defines a set of special features for the control. In the following section, you will create a custom button-like control and extend its skin with new features.Control
class. Here’s the source code of the class:Control
instance must hold its skin, and to define the default skin of a Control
instance, the Control
class defines the Skin<?> createDefaultSkin()
method. To define a default skin for the TriangleButton
control, you override only this method, as shown here:TriangleButton
control. The method will be called whenever a new instance of the TriangleButton
is created and defines a TriangleButtonSkin
instance as the skin of the control. The TriangleButtonSkin
class defines the skin of the custom control. So, let’s take a look at this class. You create the class without defining or overriding any methods, as shown here:TriangleButtonSkin
class, you can access the TriangleButton
instance that is managed by the skin by calling the getSkinnable()
method. Thanks to Generics, no class cast is needed here. Let’s start with the size of the control. The SkinBase
class defines some methods that will be used to calculate the control’s size. To define a specific size for the custom control, you override all these methods, as shown in the following code snippet:TriangleButton
control:TriangleButton
control to a huge StackPane
instance and don’t define any insets (padding or border) for it, the control will have a size of 200 × 200 pixels.NOTE
There are some other ways to define the size of the control. Because the maximum width is always equal to the preferred one, getSkinnable().setMaxWidth(Region.USE_PREF_SIZE) could be called in the constructor of the skin. For more information about the size calculation, refer to Chapter 4. |
SkinBase
class, and therefore they don’t need to be overridden. Its default implementation calculates the needed sizes by using the managed children of the control when they are positioned at their current positions at their preferred size. You should look at the source of these methods as they are defined in the SkinBase
class to decide when they should be overridden or not.Path
class, which extends the Shape
class and defines a simple shape with its geometric path. (You can find a complete feature overview of the Path
class in the JavaDoc.) The path that is needed to visualize the TriangleButton
is defined as shown in the following code snippet:TriangleButton
. The following code defines the first version of a skin that defines a triangle as the visual representation of the TriangleButton
:NOTE
In the code, I’ve omitted all the methods to compute the size of the control to give you a better overview of the code. Because the demo classes in this chapter are huge, I will do this in most of the code snippets in this chapter. |
updateTriangle(…)
method creates a new triangle and adds it as a child to the control. So, the triangle shape will be rendered onscreen whenever the TriangleButton
control is rendered. The method changes the internal node hierarchy of the control, and therefore it should be called on every rendering loop. This would result in poor performance; therefore, the invalidTriangle
flag is introduced. Whenever the size of the control changes, you set the flag to true. You do this in the constructor of the skin by adding listeners to the width and height
property of the control. Whenever the flag is true, the control changes its size. Because the triangle is defined by a static size, it must be re-created in this case. The re-creation is triggered in the layoutChildren(…)
method of the skin. This method is called whenever the layout of the control needs to be recalculated by the JavaFX rendering loop. If invalidTriangle
is true, the layout method will trigger the updateTriangle(…)
method to create a new version of the triangle that matches the current size of the control. In addition to this, the updateTriangleColor()
method is introduced. This method changes the fill color of the triangle shape to the color that is defined by the backgroundFill
property of the TriangleButton
class. Whenever the value of this property changes, the method is called.NOTE
You might wonder why the triangle isn’t created directly when the size of the control changes. This is done because of a performance issue. Maybe the size changes but the control isn’t visible onscreen. In this case, the triangle mustn’t be re-created until the control appears onscreen. As you will see later in this chapter, there are a lot of performance tricks that can be used to create a reusable control. Most of these tricks are reproduced by analyzing the basic Control and Skin classes that are part of JavaFX. |
TriangleButton
in JavaFX and add it to a scene graph. The following code shows an example that adds an instance of the custom control to a scene. The result was shown in Figure 10-3.EventHandler
class should be used here. The TriangleButton
should fire action events whenever the button is clicked, so you can reuse the ActionEvent
class that is a specific event implementation and used by the JavaFX Button control. To do this, you introduce a new property in the TriangleButton
class called onAction
:onAction
property is defined as ObjectProperty<EventHandler<ActionEvent>>
, and it can be used like all the other properties that are mentioned in earlier examples in this book. In the following code snippet, a custom event handler is defined to handle the action events of the TriangleButton
instance. Once this is done, the background color of the TriangleButton
will change whenever the action event is fired:TriangleButtonSkin
. As mentioned, new ActionEvent
instances should be fired whenever the user clicks the triangle button. To do this, a mouse handler will be defined for the triangle shape. Therefore, the updateTriangle(…)
method in the skin will be extended:fireEvent(…)
method that is defined in the Node
class is used here. You should always use this method instead of dealing directly with the event handlers when firing events. Whenever this method is called, the created event will travel through the hierarchy from the stage to the TriangleButton
node. Any event filter encountered will be notified and can consume the event. If the event is not consumed by the filters, the event handlers on the TriangleButton
are notified. This workflow can be guaranteed only when using the fireEvent(…)
method. Once this is done, an ActionEvent
will be fired whenever the user clicks the triangle. In the sample, the RectangleButton
will change its background color once the user clicks it.NOTE
In the example, an ActionEvent will be fired only when the visible triangle is clicked. As with each node, the bounds of the TriangleButtons are defined as a rectangle. This is specified in the basic functionality of the scene graph. But whenever a user clicks an empty area of the control, nothing will happen. If the mouse-click event handler were registered on the control, the events would be fired whenever a user clicks any area inside the bounds of the control. By using the demonstrated approach, a developer can specify the event handling behavior in a much better way. |
TriangleButton
with CSS, so you’ll need some additional classes and interfaces. Before the TriangleButton
classes are refactored, I will introduce the needed classes. Until now, the TriangleButton
class contained an ObjectProperty
that defined the background color. To make this property stylable by CSS, you need a property of type StyleableProperty<T>
. This interface defines a JavaFX property that can be styled by CSS. The interface defines three methods, as described in Table 10-3.StyleableProperty<T>
by yourself. JavaFX provides a set of default implementations that can be used in almost all use cases. When refactoring the TriangleButton
later, you’ll use a default implementation. When taking a look at the methods of the interface, you’ll see two new types are mentioned: StyleOrigin
and CssMetaData. StyleOrigin
is an enumeration that defines the origin of a CSS style such as a user-agent style sheet or inline style. (You can find more information about different CSS origins in Chapter 9.)CssMetaData<? extends Styleable, T>
class defines information about the CSS property that can be used to style the JavaFX property. In addition, the class defines the hooks that allow CSS to set a property value. So, for an additional CSS property definition that should be used to style the JavaFX property of a control, you need a CssMetaData
instance.CssMetaData
class and stylable properties should be used in the source of the default JavaFX Control
classes. As a first step, you will define this structure in the TriangleButton
class. Because the CssMetaData
instance and the stylable property depend on each other, the code is complex at first sight. So, let’s take a deeper look at the source code:TriangleButton
class, but I’ve removed all unchanged methods in the code example to give you a better overview. The complete source of the class will follow after this example.backgroundFill
property has changed. It is now defined as a SimpleStyleableObjectProperty<Paint>
. This class is one of the default implementations of the StyleableProperty
interface. But the biggest change is the new internal class StyleableProperties
. This class contains all the static information needed for CSS styling. In this class, the CssMetaData
instances that define the link between the JavaFX properties and the CSS properties are defined. This is done in a static inner class because of some performance issues. All the information defined here is the same for all instances of the custom Skin
class. Therefore, the defined instances mustn’t be created for each new Skin
instance. The private class contains the CssMetaData
instance that defines the CSS property that is linked to the backgroundFill
property of the control. The CSS property name -fx-triangle-fill
and its default value are passed to the constructor of the CssMetaData
class. In addition, the two methods isSettable(…)
and getStyleableProperty(…)
of the CssMetaData
class are overridden. As a result, the static CssMetaData
instance can work with the backgroundFill
property of a given Control
instance. This is achieved by passing the static CssMetaData
instance as a parameter to the constructor of the StyleableProperty
. So, the property of a specific Control
instance will use the static CssMetaData
instance that describes the link to the CSS styling internally.CssMetaData
instance, the static class StyleableProperties
defines a list of all CssMetaData
instances that can be used to style the control. This list contains the CssMetaData
instances that are defined by the control and all instances that are added by the skin. In addition, the method getControlCssMetaData()
has been overridden. This method returns a list of all CssMetaData
instances. Because you defined this list in the inner static private class, it can be returned here. Instead of returning the static list directly, a static utility method called getClassCssMetaData()
is defined and called. In this first example, the static method theoretically isn’t needed, but as you will see later, this structure is useful.NOTE
The shown structure looks complex at first, but it is a best practice when defining custom controls. JavaFX uses the same approaches internally, which results in great performance: All global objects are defined in a static class and therefore need to be created only one time. In addition, no listener instances are needed. When working with controls, especially on mobile or embedded devices, memory usage and performance are important topics. Therefore, custom controls should be designed as shown here and done in the default JavaFX controls. Even if internal classes and anonymous inner classes are not a best practice when talking about design, they should be used in this case. |
TriangleButton
class will look like this:TriangleButton
with CSS. Here is an example of a CSS rule that can be used to style a TriangleButton
:CssHelper
class that is provided at www.guigarage.com. You can find a description of the API here: http://www.guigarage.com/2014/03/javafx-css-utilities/. By using the CssHelper
class, you can define the stylable properties and the needed CssMetaData
instances in only a few lines of code; the following code block is an example. Here, a second styleable property is added to the TriangleButton
.CssHelper
class provides static methods to generate stylable properties and CssMetaData
instances.StyleableProperty
, some changes in the TriangleButtonSkin
are needed, as shown here:TriangleButton
control can now be styled in CSS by using all the properties that are defined for the basic Control
class and the new CSS properties that are added to the TriangleButton
class. Normally, a control inherits the stylable property of its parent class for CSS styling. If you want to eliminate a stylable property from a parent class, don’t add the CSSMetaData
from the parent class to the CSSMetaData
list of the control. You can use the CssHelper
class like in the following code snippet:createCssMetaDataList(…)
method. This parameter defines the basic list of CssMetaData
instances. The complete list of all CssMetaData
instances that are used for the class are defined by this list and all additional instances that are committed as parameters to the method. Normally, the static getClassCssMetaData()
method of the parent class will be used here.TriangleButton
; aspects of the animation will be changeable by CSS.Animation
class, and all animations should extend this class. JavaFX already contains a set of specific animations. One of them will be shown in the next example. An animation can run either only one time or in a loop, and in addition, it can run forward and backward while looping. The Animation
class provides methods such as play()
and pause()
to handle the state of an animation. In most use cases, the animation API will be used to animate JavaFX properties such as the rotation
property of a control. For some property types, JavaFX contains default classes that can be used to define an animation for a property instance. An example is the ScaleTransition
class. This class can be easily used to define an animation for the scale properties (scaleX, scaleY, scaleZ
) of a control.NOTE
If you need an animation for a property type that isn’t supported directly by a concrete Animation class in JavaFX, you can use a more general Animation class. This class can be found at www.guigarage.com/2012/12/bindabletransition/. |
TriangleButton
should zoom in and out once the user enters the mouse over the control. Because this feature is part of the behavior and look of the control, it should be defined in the Skin
class. Therefore, the TriangleButtonSkin
class will be extended, as shown here:zoomIn()
and zoomOut()
. These methods will be called whenever the mouse cursor enters or exits the triangle and starts an animation. The animation automatically changes the values of the scaleX
and scaleY
properties of the control. As a result, the triangle will zoom in or out as a reaction to a mouse hover. In addition, the dispose()
method of the SkinBase
class has been overridden. In JavaFX, the skin of a control can change at run time. Therefore, the animation must stop whenever the skin is disposed. Figure 10-4 shows an example of the current state.StyleConverter<F, T>
class, which can be extended to define new converters for special data types. Therefore, you must override the convert(…)
method that is defined by the StyleConverter
class. The following code defines a StyleConverter
that will create Duration
instances from CSS values:CssMetaData
instances to convert the value that is defined in a CSS rule and set it to a stylable property.NOTE
The Font parameter of the convert(…) method normally isn’t needed. But sometimes you will specify CSS types that are defined as relative values. These values specify a length relative to another length. Normally, the font size will be taken as a reference in this case. You can find more information about relative values at www.w3.org/TR/css3-values/#relative-lengths. |
CssMetaData
instance. Let’s make the duration of the example animation in the TriangleButtonSkin
stylable. You define the CssMetaData
instance as shown in the following code snippet:TriangleButtonSkin
should look like this:getCssMetaData()
method has been overridden. In addition, a static section was added to define the CssMetaData
of the class. Why this has been done and how the CssMetaData
objects that are defined in the skin are bound to a CSS style sheet will be discussed in a moment. But before this, it’s time to change the style sheet of the control and test all the changes.NOTE
Whenever a control is styled with CSS, JavaFX will fetch the list of CssMetaData objects by the skin of the control. By default, the List<CssMetaData<? extends Styleable, ?>> getCssMetaData() method that is defined in the Region class will be called to fetch all CssMetaData instances. Therefore, each class that extends the Region class can define its own CSS properties. This method is overridden by the Control class that merges the metadata of the Control instance and its skin. So, you can specify stylable properties in custom Control and Skin classes. |
TriangleButton
; you can define a CSS pseudoclass in JavaFX by using the PseudoClass
class. Each CSS pseudoclass is defined with a unique name and can be used to style different states of a control. Chapter 9 shows how this can be done with CSS.PseudoClass
instance as shown in this code snippet:armed
. The Node
class in JavaFX provides the pseudoClassStateChanged(…)
method that should be used to activate a pseudoclass. The method getPseudoClassStates()
returns a set with all currently active pseudoclasses. The following sample shows how pseudoclasses can be used and combined:TriangleButton
control, you need to define an armed
pseudoclass, and the CSS pseudoclass should be active whenever the button is armed by a mouse click. This means that whenever the mouse is pressed on the triangle, the custom control should have an active armed
pseudoclass. The same behavior is defined for the default JavaFX button.BooleanProperty
instance called armed
to the TriangleButton
. Whenever the value of this property is true, the pseudoclass should be active. The following code shows the new version of the TriangleButton
class:armed
property like how pseudoclasses are defined in the JavaFX basic controls. Whenever you create custom controls that should be used heavily in applications or released as open source controls, this is the most performant way to do this. The invalidated()
method of the property class is overridden to directly change the pseudoclass. The same behavior can be defined by the following code:armed
property is defined as a SimpleBooleanProperty
like in some of the earlier examples. In the constructor of the TriangleButton
, you add a ChangeListener<Boolean>
instance to the property, and whenever its value changes, the armed
pseudoclass is activated or deactivated. In addition, no anonymous classes are used in the code. But the code has some performance drawbacks in comparison to the first approach: In the second code snippet, the armed
property will be instantiated directly in the constructor of the class. So, memory will be used for the property object even if it’s never used. In addition, a ChangeListener
instance is created. When taking a look at the internal JavaFX classes, you will learn that a lot more objects will be generated when the ChangeListener
is registered. So, the second approach uses a lot more objects and therefore more memory. Thus, you should always use the first approach when developing reusable controls.armed
property and the pseudoclass are defined in the Control
class, the behavior of the control will be adapted in the Skin
class. Therefore, you add two mouse handlers to the updateTriangle(…)
method, as shown here:armed
pseudoclass. You can now use the pseudoclass in a CSS style sheet, as shown here:TriangleButton
will change whenever the mouse is pressed on it.TriangleButton
is armed, let’s add an effect to it. JavaFX provides an effect API with several effect types. You can find the API in the javafx.scene.effect
package. The following sample shows how to assign effects to any JavaFX node:Button
instances. Figure 10-5 shows the result. As you can see in the code, an effect can simply be applied to a node by using its effect
property. Effects will transform the visualization of the node by adding shadows and reflections, or will render the node by using a blur filter, for example. As shown with the last button of Figure 10-5, most JavaFX effects can be stacked on top of one another by using its input
property. All effect types that are defined by JavaFX can be configured. To see all the different effect types and how they can be configured and used, refer to the JavaDoc of the javafx.scene.effect
package.NOTE
The Node class defines the -fx-effect CSS property, and therefore you can define an effect for each JavaFX node using CSS. At the moment, JavaFX CSS supports only the DropShadow and InnerShadow effects. You can learn how these effects can be applied in CSS in the JavaFX CSS documentation: http://download.java.net/jdk8/jfxdocs/javafx/scene/doc-files/cssref.html#typeeffect. |
TriangleButton
should be rendered with an effect whenever it is armed. To do this, you add the method updateEffect()
to the Skin
class. The method will be triggered whenever the armed state of the TriangleButton
changes. Whenever the control is armed, an inner shadow will be applied as an effect on the triangle. Figure 10-6 shows how the control will now look in the armed state.TriangleButtonSkin
won’t be changed anymore, here is the final code of the class:Control
class and a Skin
class, you can easily change the skin. This can be done even at run time or with CSS. To see this feature, you need another skin; it should render a triangle button with a triangle that has one of its apexes on the bottom instead of on the top. Figure 10-7 shows an example of how this will look.Skin
class. For this example, the Skin
class will look mostly like the current one. Only the definition of the triangle in the updateTriangle(…)
method has changed. Here is the code of the second skin:Control
class defines the createDefaultSkin(…)
method that can be overridden. If the new skin should be the default skin of the TriangleButton
, you do it like this:skin
property of the control. The following code snippet shows how to do this:Control
class provides the CSS property -fx-skin
. By using this property, the Skin
class of a control can be defined in CSS. Therefore, the class of the skin must be defined as its CSS value in a style sheet, as shown here:NOTE
CSS properties can be defined in the Control class and the Skin class. In the TriangleButtonSkin , you defined the CSS property -fx-animation-duration , for example. Once the skin changed, this property isn’t used anymore because it is not defined in the current skin. Frameworks such as AquaFX use this approach to add custom CSS properties to the default JavaFX control types by providing custom Skin classes for these controls and adding new CSS metadata to these skins. |
SkinBase
class defines the dispose()
method. As already mentioned, this method will be called each time the skin of a control changes. The method will be called on the old skin instance of the control.Skin
class. In the following code, the listeners that are registered to the properties of the control are extracted as fields of the Skin
class. As a result, they can easily be registered in the constructor of the skin and deregistered in the dispose()
method:NOTE
For default control implementations that are part of JavaFX, there is a special mechanism that registers and deregisters all these listeners automatically. But, sadly, this is defined in the BehaviorSkinBase class that is part of the private API of JavaFX and, therefore, shouldn’t be used. If you want to develop a lot of custom controls, you should have a look at this class and define your own basic class for skin implementations, that provides comparable functionality. |
Region
node, and so on. If possible, I stick with the Region
-based control because it’s stylable by CSS and only one file. But when I create controls for a library, I usually use the approach where you create a Control
class and a Skin
class in combination with a CSS file.Region
class as the base class for custom controls instead of using the Control
class. Let’s discuss these two topics in more detail. How do you convert an SVG file to a Java-based UI?Region
node and the node will be filled with the shape that is defined in -fx-shape
. If you would like to scale that shape automatically, you simply have to set -fx-scale-shape: true;
, and the SVG path will always be scaled to the size of the Region
node. So what you need to create an SVG shape is a vector-drawing program like Inkscape, Illustrator, or others that are capable of handling SVG. With such a program, you can draw the shapes in the program, export them as an SVG file, and use the paths in the CSS file of your JavaFX application.Region
class instead of the Control
class. Can you explain this decision?Region
node is a lightweight container that is stylable via CSS. That makes it a perfect component to extend and use as a custom control. If you would like to create a not-too-complex control, the Region
is a perfect choice because it’s only one class and a CSS file. That means you combine the logic with visualization code within one class. Like I said, that’s fine for simple controls, but if it gets more complex, it’s a good idea to split the logic from the visualization by extending the Control
class for the logic and creating a skin for the visualization.Canvas
class to create custom controls. Have you tested these approaches too? If so, can you explain in a few words the problems in these approaches and why a developer should use the architecture that is described here?Canvas
node. This approach is useful if you have to draw very complex things in your control. Because the Canvas
node is more like an image you can draw on, you can reduce the number of nodes on the scene graph. The disadvantage of using the Canvas
node for a control is the fact that you can’t easily attach event listeners to subareas of your control but only to the Canvas
node alone. This means controls based on the Canvas
node are great for controls that only visualize something and don’t offer any user interaction. I’ve never really tried the FXML-based approach because in my eyes, FXML is not meant to be for creating controls but more to be read/written by a computer and not for manual editing.Regions
for your controls. Somehow, the control approach doesn’t work on these platforms at the moment. This might change in the future, but at the moment you are bound to JDK 7 and JavaFX 2 on these platforms, and only Region
-based custom controls work.Region
and make sure that the controls resize correctly. With this approach, the controls will run on JavaFX 2 and JavaFX 8 and also on all devices. The screen resolution really could be a problem, especially if you use fonts. You have to make sure that the control also renders nicely when it is very small or very big. So, getting that right is the hardest part, and unfortunately, there is only one way to figure it out…you have to try it on the device.Region
class instead of the Control
class and develop all the features in only one class. However, in that case, you won’t get all the benefits of the Skin
class and the performance tricks shown in this chapter. Therefore, usually you should invest the time to design a custom control as shown in this chapter. The workflows that are shown here were used to develop the default controls of JavaFX and were used in some other important open source libraries such as ControlsFX.18.226.88.110