image
CHAPTER
10
Custom Controls
In this chapter, I show how the internal layout and rendering mechanisms of a control work and how you can create custom JavaFX controls or extend the basic controls with new cool features. To do either of these things, you will need to create a skin, because in JavaFX, the layout and behavior of a control are defined in its skin. In this chapter, you will learn how to define the skin and how its interaction with the Control class works. In addition, you’ll create your first custom control, which will use some exciting JavaFX features.
The Structure of a Control
It is important to know how a JavaFX control works internally. In the previous chapters, I showed most of the internals of the 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?
This functionality is one of the main features of a JavaFX skin. In JavaFX, a skin is defined for each 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.
image
image
image
FIGURE 10-1.Internal structure of a control
As you can see in the figure, the internal architecture of JavaFX controls is based on the Model-View-Controller (MVC) pattern. Since the other parts of a control were already described in earlier chapters, let’s take a look at the skin definition in JavaFX so you understand the complete architecture of JavaFX controls.
The Skin
The skin of a control is defined by the 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.
image
TABLE 10-1.Methods of the Skin Interface
As defined in the interface, a skin needs an object of type 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.
image
image
image
FIGURE 10-2.UML diagram of Control and Skin
As you can see, a custom control implementation will extend the Control class, and its skin implementation will normally extend the SkinBase class. The two classes will look like the following code snippet:
image
image
All basic controls that are part of JavaFX and mentioned in this book are structured like this example. The skin and control are defined as a one-to-one relationship. The skin is defined by the 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.
image
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.
As mentioned, all skins that are defined for custom controls should extend the SkinBase class, so you will now take a deeper look at this class.
The SkinBase Class
The abstract 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.
The 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.
image
image
TABLE 10-2.Methods of the SkinBase Class
To get a better understanding of these methods and how they can be used or must be overridden when creating custom skins, let’s first create a custom control that uses a skin that extends the 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.
Creating a Custom Control
To create your first control in JavaFX, you need a specification of the control and its features. For this example, you will create a button-like control that is a triangle. Figure 10-3 shows how the control will look in its basic version.
image
image
image
FIGURE 10-3.The TriangleButton control onscreen
Later you will add action handling and different visual states for when the control is clicked, but first you will focus on the basic layout and rendering of the control. In its initial version, the control should have a property that defines its background color, so you need the model of the control. As mentioned, the model will be defined in the class that extends the Control class. Here’s the source code of the class:
image
image
This class contains only a property that will be used to define the background color of the control. As mentioned, a skin is needed for this custom control. A 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:
image
image
The method returns a new skin instance that is used as a skin for an instance of the 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:
image
image
Once this is done, you define the look and feel of the control. In the 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:
image
image
By adding these methods, the skin defines preferred, maximum, and minimum sizes for the TriangleButton control:
image  The control has a minimum size (width and height) that is equal to its insets + 20.
image  The control has a preferred size (width and height) that is equal to its insets + 200.
image  The control has a maximum size (width and height) that is equal to its preferred size.
So, if you add a 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.
image
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.
The shown methods are not abstract methods in the 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.
After you define the size of the control, it needs a visualization. So, define a shape that is used as the visual representation of the control. To create a triangle, you will use the 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:
image
image
The path is created by three lines that span a triangle. In the last line of the snippet, the path is closed. A closed path can be filled by a color, and that is exactly what you need for the TriangleButton. The following code defines the first version of a skin that defines a triangle as the visual representation of the TriangleButton:
image
image
image
image
image
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.
Let’s take a look at the new methods and how they work. The 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.
image
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.
Once all this is done, you can use the 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.
image
image
Adding Event Handling
As a next step, you should add event handling to the control. Like the event handling that is defined in the basic controls of JavaFX, the 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:
image
image
As you can see in the code, the 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:
image
image
Once this is done, a developer can programmatically react to action events. But until now, these events will never be fired. Because the skin of a class should handle its behavior, the event should be fired in the 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:
image
image
The 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.
image
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.
Styling the Control
Next, you should style the 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.
image
TABLE 10-3.Methods of the StyleableProperty Interface
Normally, you don’t need to implement these methods and implement the 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.)
The abstract 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.
You can find examples that show how the 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:
image
image
As mentioned, there is a lot of new code in the 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.
The class type of the 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.
In addition to the 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.
image
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.
After all these changes, the TriangleButton class will look like this:
image
image
image
Once this is done, you can style instances of the TriangleButton with CSS. Here is an example of a CSS rule that can be used to style a TriangleButton:
image
image
Using a CssHelper for Styling
As you saw in the previous sample, a lot of code is needed to make a JavaFX control stylable. Most of the code must be defined again for each stylable property of a control. By doing this, you’ll create a lot of boilerplate code. Therefore, you can use helper classes to simplify the definition of stylable controls. One API that can be used is the 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.
image
image
image
As you can see in the example, the CssHelper class provides static methods to generate stylable properties and CssMetaData instances.
For the second StyleableProperty, some changes in the TriangleButtonSkin are needed, as shown here:
image
image
The 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:
image
image
Here, null is used for the first parameter of the 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.
Defining Custom CSS Value Types
As mentioned in this and the previous chapter, CSS supports a lot of different types, such as String, color, and number, in JavaFX. In addition to this, you can define custom value types in JavaFX. As a next step, let’s add some animation to the TriangleButton; aspects of the animation will be changeable by CSS.
Because the JavaFX animation API wasn’t mentioned until now in this book, it’s time to take a short look at it.
JavaFX contains an API that can be used to easily create animations. The basic class is the 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.
image
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/.
To provide better feedback for the user, the 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:
image
image
image
Two new methods were added to the class: 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.
image
image
image
FIGURE 10-4.Animation of the control
At the moment, a hard-coded duration of 250 ms is defined for the animation. As a next step, this duration should be changeable by CSS. It would be easy to define a CSS property that could handle any number value and use this as the duration in milliseconds. But sometimes you need an animation that will take 20 seconds, for example. In this case, it would be better to write 20s instead of 20000 for the CSS value. Therefore, you will define a new CSS converter that can be used to define duration values in CSS.
To create a special converter, JavaFX provides the 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:
image
image
The converter currently supports seconds and milliseconds, and values can be specified like 3s or 250ms. This converter can be used in CssMetaData instances to convert the value that is defined in a CSS rule and set it to a stylable property.
image
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.
Once the converter class is defined, it can be used by any 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:
image
image
After all these changes, the TriangleButtonSkin should look like this:
image
image
image
image
image
As you can see in the code, the 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.
image
image
Once you change the style sheet, the triangle will be shown in orange with a black border. Whenever the mouse enters or leaves the triangle, the animation will start. This will now look very slow because the complete animation will take 2s instead of 250ms.
image
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.
Adding a CSS Pseudoclass
As a next step, a CSS pseudoclass will be added for the 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.
You create a new PseudoClass instance as shown in this code snippet:
image
image
This creates the new pseudoclass 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:
image
image
For the 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.
To implement this feature, you add a new 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:
image
image
image
You set the definition and implementation of the 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:
image
image
This code looks much cleaner than the first version: The 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.
Once the 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:
image
image
Once this is done, the control will handle its armed state and automatically activate and deactivate the armed pseudoclass. You can now use the pseudoclass in a CSS style sheet, as shown here:
image
image
By defining these CSS rules, the background color of the TriangleButton will change whenever the mouse is pressed on it.
Adding an Effect
To create even better visual feedback whenever the 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:
image
image
The demo defines some JavaFX buttons and applies different effects on the 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.
image
image
image
FIGURE 10-5.Effect example that can be created with JavaFX
image
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.
As mentioned, the 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.
image
image
image
FIGURE 10-6.An inner shadow effect
Because the TriangleButtonSkin won’t be changed anymore, here is the final code of the class:
image
image
image
image
image
Adding a Second Skin
You have seen how to develop a custom control and its skin, but the most important benefit of this structure wasn’t mentioned until now. By splitting a JavaFX control into a 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.
image
image
image
FIGURE 10-7.The TriangleButton with an alternative skin
To do this, you need an additional 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:
image
image
image
image
Once you define the second skin, you can use it for the example. There are several different ways to apply the skin to the control. As mentioned earlier, the 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:
image
image
But, as mentioned, there are other ways to change the skin of a control. One way is to set a new skin to the skin property of the control. The following code snippet shows how to do this:
image
image
This code snippet can easily be wrapped in an action handler. As a result, the skin of a control can theoretically be changed by clicking a button.
In addition to this, the 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:
image
image
image
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.
Dispose a Skin
Whenever the skin of a control changes, the old instance should be removed by Java’s garbage collection. To do this, all references of the old skin should be removed. Additionally, it is important that for all listeners the old skin registered to properties of the control are removed too. If this doesn’t happen, the old skin will still be alive and could change the behavior of the control. To avoid this, the 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.
In our example, the skin implementations register some listeners to properties of the control. These listeners should be removed whenever the skin instance is changed. To do so, and to provide a fully useable skin implementation, we need to change one last thing in our 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:
image
image
image
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.
An Interview with Gerrit Grunwald, Canoo Engineering
Hi, Gerrit. Most JavaFX developers know you as @hansolo_ and have seen your awesome custom controls that you have created for JavaFX in the last two years. But before we talk about this cool stuff, can you please introduce yourself?
Hi, Hendrik. My name is Gerrit Grunwald, and I’m working for Canoo Engineering in Switzerland. I started coding Java around 10 years ago when I was working as an engineer in the nanotechnology and semiconductor industry. I’m mainly doing front-end-related stuff, and I’m also very interested in IoT and embedded technologies that run Java and JavaFX. But the thing I like most are still custom controls, preferably in JavaFX.
So, as I understand it, the development of custom controls is one of your hobbies. I know that you already did this with the help of different UI toolkits before you started to create controls with JavaFX. Maybe you can give a short overview of the different languages and frameworks you have already used to create and design controls.
Yes, creating custom controls is something that drives me. For me, trying to transfer a real object into a custom control is a challenge that I simply can’t withstand. So, I started to create my own components around eight years ago in Java Swing and was fascinated by the possibilities of that technology. It was always nice to figure out how to create certain visual effects in Swing manually (which was needed due to the lack of built-in visual effects). Then I tried Microsoft .NET with C#. I created a couple of C#-based controls, but the way that worked in .NET was not that nice, so when HTML5 came along, I saw the canvas element and was thrilled. Now I had a technology that made it possible to create components that ran also on my iOS and Android devices. The only thing with HTML5 that I did not like was JavaScript; it somehow didn’t work for me. So when JavaFX 2 was born in 2011, I gave it a try, and now I’m addicted. It’s powerful, it has lots of nice features, and it’s Java…I love it.
So you tried a lot of different technologies to create custom control, and in the end, you decided to use JavaFX. What are the biggest benefits of JavaFX in this case?
First, and most important for me, is the fact that JavaFX is just Java, so you don’t have to learn a new language, but only a new API. The second important thing is the availability of JavaFX on all major platforms with the same feature set plus the ARM port, which makes it also available to embedded devices. So with this technology, I can create controls that I can reuse wherever I need them. In addition, the JavaFX API is really nice to code with, and the features you get with JavaFX are powerful. Because I’ve tried the other platforms, I know how much time one can save if you don’t have to create everything by yourself.
Let’s talk about custom controls in JavaFX. What is your best-practice workflow when developing a new control?
Usually, I start with drawing a prototype of the control in a vector drawing program. With this approach, I can easily modify the shapes and colors of the control with direct visual feedback. When this is done, I transfer the shapes and colors into code by using SVG for the shapes. In my IDE I’ve created some templates for different kinds of controls, for example, those that are based on extending a 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.
In your workflow description, you mention two steps that are different from the basic practices that are described in this book: SVG as a base for the UI and the usage of the 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?
One of the many great things in JavaFX is the SVG support in CSS, which means that one can define an SVG path in CSS by defining something like this:
image
Now you simply could apply the CSS class that contains the definition to a 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.
That’s a cool tip that wasn’t mentioned in the book until now. So, thank you for that. The next topic I want to talk about is your basic control structure. You said that some of your custom controls extend the Region class instead of the Control class. Can you explain this decision?
The 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.
So, you have two different ways how you create controls. Some people have tried to use FXML or the 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?
Yes, I’ve also created controls based on the 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.
Have you seen any differences in the performance of the discussed approaches?
My experience (especially on embedded devices) is that it really depends on the control itself. Because with JavaFX we get hardware-accelerated graphics (also on embedded), the drawing performance most times is not the problem. One of the reasons for bad performance might be the fact that paths will be calculated by the CPU before they will be rendered by the GPU. That means if you use a lot if complex paths in your control, it might slow down the performance of your control. One thing that also is true is that nothing is faster than code, which means if you overuse CSS, it might also be slower than a pure-code approach. But keep in mind that you might lose flexibility in styling when using code only.
Your answer leads perfectly to the last topic of this interview. You already mentioned embedded devices, and I know that you developed a lot of custom controls for different devices like desktop, mobile, and embedded. Can you share some pitfalls or best practices when developing controls for different devices?
Well, on the desktop, you simply can do everything you like.
With embedded, it’s a bit different; first of all, it depends on the hardware you use. When using a Pi, you have to keep in mind that it has a really good GPU but a really slow CPU. This means every path you use in your controls will be calculated by the relatively slow CPU before it will be rendered by the fast GPU. So, the CPU is the main bottleneck on the Pi. Using an i.MX6-based device with a Vivante GPU might be completely different. Here, you have a fast CPU in combination with a good GPU. So, there is no general rule, but everything depends on the target platform and its capabilities.
As a last tip, let me tell you that on iOS and Android devices you might want to use 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.
Do you have any experience with controls that are made for all the different devices? I think a big problem might be the significant difference in the screen resolution of these devices. How can that be handled?
If you want your controls to run on all devices, you should extend 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.
Most developers don’t have all these devices. Can emulators be used to test the behavior and performance of custom controls?
Unfortunately not. I’ve tested the same control on the Raspberry Pi, an i.MX6 quadcore, an iPad, and Android, and performance-wise it behaves completely differently on all of these platforms. If you use the iPad emulator on the Mac to test your JavaFX application, you will figure out that it is really slow, and if you put it on an iPad mini retina, it will show good performance. But if you put the same code on an iPhone 5 (which is not that old), it will be slow again. Again, it all depends on the target platform.
Thank you for all these important tips. Do you want to mention any other helpful notes about developing custom controls to all the JavaFX developers out there?
I know developers are not designers, but believe me that everyone can learn at least some things related to design, so my advice is…learn how to use a vector-drawing program! Thanks for the interview.
Thanks for your time. I hope to see a lot of cool custom controls designed by you in the future!
Summary
The chapter described how to develop custom controls in JavaFX and showed a lot of best practices used in the default JavaFX control classes. Theoretically, all the functionality shown here could be developed more easily: If you need a special control that is used one time and will never change, you could extend the 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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.239.214