Writing custom controls in JavaFX is a simple and straight forward process. A control class is needed for controlling the state of the control (hence the name). A skin class is needed for the apperance of the control. And more often than not a CSS file for customizing the apperance.
A common approach for controls is to hide the nodes they are using inside their skin class. The TextField control for example uses two instances of javafx.scene.text.Text. One for the regular text, one for the prompt text. These nodes are not accessible via the TextField API. If you want to get a reference to them you would need to call the lookup(String) method on Node. So far so good. It is actually hard to think of use cases where you would actually need access to the Text nodes.
But…
It becomes a whole different story if you develop complex custom controls. The FlexGanttFX Gantt charting framework is one example. The GanttChart control consists of many other complex controls and following the “separation of concerns” principle these controls carry all those methods and properties that are relevant for them to work properly. If these controls were hidden inside the skin of the Gantt chart then there would be no way to access them and the Gantt chart control would need to implement a whole buch of delegation methods. This would completely clutter the Gantt chart API. For this reason the GanttChart class does provide accessor methods to its child controls and even factory methods for creating the child nodes.
Example
The following screenshot shows a new control I am currently working on for the ControlsFX project. I am calling it ListSelectionView and it features two ListView instances. The user can move items from one list to another by either double clicking on them or by using the buttons in the middle.
List views are complex controls. They have their own data and selection models, their own cell factories, they fire events, and so on and so on. All of these things we might want to either customize or listen to. Something hard to do if the views are hidden in the skin class. The solution is to create the list views inside the control class via protected factory methods and to provide accessor methods.
The following code fragment shows the pattern that can be used:
public class ListSelectionView<T> extends Control { private ListView<T> sourceListView; private ListView<T> targetListView; public ListSelectionView() { sourceListView = createSourceListView(); targetListView = createTargetListView(); } protected ListView<T> createSourceListView() { return new ListView<>(); } protected ListView<T> createTargetListView() { return new ListView<>(); } public final ListView<T> getSourceListView() { return sourceListView; } public final ListView<T> getTargetListView() { return targetListView; } }
The factory methods can be used to create standard ListView instances and configure them right there or to return already existing ListView specializations. A company called ACME might already provide a standard set of controls (that implement the company’s marketing concept). Then the factory methods might return a control called ACMEListView.
[…] tips, including ‘Beauty is Skin Deep‘, ‘Do Not Mix Swing / JavaFX‘, ‘Custom Composite Controls‘, and ‘Updating Read-Only […]
FYI: the ListViewSelection control will have a different API in ControlsFX as it would clash with the API concepts of the other controls. In ControlsFX we try to hide as much of the inner workings as possible. This means that the list views will be hidden inside the skin and that maybe the skin class will have the createSource/TargetListView methods.
I’m new to JavaFX, but not a fan of calling methods intended to be overridden from constructors.
Where do you see a problem?