There are two ways how a custom control/node can be created. The simple way is to extend CustomNode. Then everything should work as expected.
The second possibility is to extend Control, Skin and Behaviour. This is the better way if you want to support different skins. And of course it seems to be the more MVC-like approach. Therefore I decided to try it that way…
Problems with the *bounds*
Implementing a new control seems to be very straight forward. Just follow the examples found everywhere. But if you try to use your newly created component, you probably be surprised…
If placing the control within a Stack, the control sticks to the upper left corner. It is not layouted as I expected. During debugging I realized that the layoutBounds look strange. They are not bound to the node created by the skin. Instead they seem to be hardcoded to 0/0/100/50.
But boundsInLocal seems to be okay…
Finding the reason..
So what can we do? Just taking a look at the source code of Control should be enough… But those sources are not available!
Okay, let’s find out on our own. I created a small scene containing a Stack with its width/height bound to the scene dimensions (800/600). That stack contains the custom control.
Step 1: Simple Control without any Skin
I created a control and I did not set a skin. What happened?
As expected, the layoutBounds and the boundsInLocal where both set to (0/0/0/0).
Step 2: Simple Control with an empty Skin
I now set a skin in create() of my Control. The skin does not do anything (node is null). Result?
The layoutBounds are initially set to (0/0/100/50)!
They are then adjusted to the size of the Stack (as expected?). The layoutBounds seem to be bound to width/height of the control, as expected when reading the jfxdoc for Resizable.
The boundsInLocal stay at (0/0/0/0).
Conclusion:
There exist hard coded default values for each control (100/50). Those are used for the inital layoutBounds and the prefWidth/Height.
Step 3: Skin containing just a Rectangle
The skin assigns a Rectangle {width:70 height:150} to the node in init.
The layoutBounds didn’t change: (0/0/800/600) – influenced by the Stack size. But the boundsInLocal are now calculated correctly based on the rectangle: (0/0/150/70).
The Rectangle is now painted in the upper left corner of the stack. This is not surprising since the Stack expects the Control to be much bigger (800/600).
The prefWidth/Height returns also the hard coded values of 50/100.
Conclusion:
The boundsInLocal are bound to the node created by the skin automatically. But the layoutBounds are bound to the width and size inherited by Resizable (correctly following the documentation). But there is missing a connection between the layoutBounds of the control and those of the node created by the skin.
Additionally the prefWidth/Height and min/max methods have to be connected to the skin.
Step 4: Create custom maxWidth/Height methods
The skin overrides the maxWidth/Height methods and returns 340/320. Since the control delegates to the skin the control don’t have to be modified.
The Stack now resizes the control to its maximum width/height. Therefore the control has a size of 340/320 that are reflected by the layoutBounds. The boundsInLocal stay at the “real” size of 150/70.
The positioning of the control is different now. The stack uses the changed layoutBounds for its calculations. Therefore the rectangle has been moved towards the center. But it is still not at the center (because the layoutBounds are larger than the drawn rectangle).
Resizing the scene changes the actual width/height and layoutBounds of the control. But of course the Rectangle keeps its size.
Step 5: Implement minWidth/Height methods
Those methods return a minWidth/Height of 50/70.This does not have any effects. The Stack seems to ignore them. So the actual size of a control may become smaller than the values returned by the min-methods.
Step 6: Bind the Rect width/height to control.width/height
Since the control is resizable we bind the width/height of the Rectangle to the width/height of the control (that is modified by the Stack).
The control has the size of 340/320 (provided by getMaxWidth/Height). The rectangle has the same dimensions and therefore is placed at the center of the Stack.
The layoutBounds and the boundsInLocal are the same now. Resizing works as expected.
Step 7: Fixing getPrefWidth/Height
Now we want to adjust the preferred width and height. Therefore we override those methods. It is probably a good idea to delegate those requests to the skin… For now those methods return 110/90.
When the control is placed within a Group, the preferred size and with is used. Since the Group is not resizable the Stack can’t change the size.
The control is layouted with its preferred size in the center of the Stack.