Apr 19 2010

JavaFX: Creating custom controls – the right way

Is is quite difficult to create custom controls in JavaFX that perform like they should. This is the result of lack of documentation, unavailability of source code and a not-so-good API…

I struggled with that issue for two days. And finally I think I understood the hole concept. I wrote about that process in this blog some days ago.

If you are just looking for a template, continue here.

Here is a small drawing that visualizes the relevant methods/fields related to custom controls:
control2skin

width/height

The width and height are set by the container. The container calculates the width/height based on the values returned by getPrefWidth/Height and getMaxWidth/Height.

layoutBounds

The layoutBounds are calculated automatically based on the values of width/height of the control.

Attention: The layoutBounds do not depend on the layoutBounds of the node created by the Skin!

This might be counter-intuitive. But it is necessary since the Control is resizable. And therefore the layoutBounds have to depend on the width/size (which are set by the container).

boundsInLocal

The boundsInLocal are bound to the boundsInLocal of the node created by the skin. Just as you might have expected.

getMin/Pref/MaxWidth/height

The control delegates those method calls to the currently active Skin. At least it is necessary to implement the getPrefWidth/Height methods. The default implementation just returns hard coded values (100/50). If you don’t have implemented those methods, the layoutBounds of the control will be wrong. And this will result in odd layout behaviour of your custom node!

Which methods to extend? Which fields to bind?

There are two things you have to do:

Step 1: Override getPrefWidth/Height in Skin

This is *really* necessary. Those methods should have been made abstract. I can’t imagine any case where those methods don’t have to be overridden… So just override them. Returning hard coded values is ok for most cases.

Step 2: Bind the size of the node to Control#width/height

Whenever the layoutBounds of the control change, the size of the node must be adjusted. It depends on your implementation how the relationship might be. For simple nodes without any effects a simple bind should be enough:

[cc lang="c" escaped="true" tab_size="2" lines="20"]
class MySkin extends Skin {
init {
node = Rectangle {
width: bind control.width
height: bind control.height
}
}

[/cc]

Now you have created a custom Control that behaves as expected – and is resizable…

I have created a template to create custom a control and skin.


Apr 19 2010

JavaFX: Bounds by example

There are several misunderstandings related to bounds. (Amy Fowler tried to explain the layout mechanisms some days ago)

There are three bounds that can be used:

  • layoutBounds – represent the *logical* bounds used to layout the node
  • boundsInLocal – represent the *real* bounds used to paint the node
  • boundsInParent – the boundsInLocal transformed to the coordinates of the parent.

And now there is simple stuff you should remember:

layoutBounds are used for layouting (only). They do not have anything to do with boundsInLocal. They are just a logical concept. Sometimes they are the same as the boundsInLocal. Sometimes they are smaller, sometimes larger.

Some examples

layoutBounds == boundsInLocal

This is the case for many very simple nodes. For example a rectangle without any effects like drop shadows.

layoutBounds < boundsInLocal

This is the case for nodes that have some type of effect – for example a drop shadow. That shadow must not be included into the layout. If the layout algorithm used the boundsInLocal (including the drop shadow) the nodes would look misaligned to the human eye.

Aligned using the layoutBounds:

layoutBounds used

Aligned using the boundsInLocal:

sample-unaligned

layoutBounds > boundsInLocal

This is the case if some parts of the node are (yet) invisible, but the human eye interpolates them:

PacMan with bounds

If those Pacman like circles are aligned using the boundsInLocal this will result in: Pacmans aligned with boundsInLocal

While the human eye expects the alignment to be more like that: Pacmans aligned by layoutBounds

Disclamier:

I know that this post does not contain many (if any) new informations. It is just a little bit shorter. And therefore might be helpful for somebody.


Apr 14 2010

JavaFX and custom controls

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.


Apr 14 2010

[JavaFX] Bind bug? Or am I just stupid?

I really love that binding stuff in JavaFX. I am looking forward until some smart guy implements something similar in Scala…

But I also have some problems with it. Sometimes “on replace” triggers are called with the same values for <old> and <new>. Here is a small sample script that shows it:

[cc lang="c" escaped="true" tab_size="2" lines="20"]
import java.lang.RuntimeException;

/**
* @author johannes
*/
println( “Starting binding test…” );
var i = 0.5 on replace old {
println( “i changed to <{i}>” );
if ( i == old ) {
throw new RuntimeException( “This will never happen!” );
}
};
var b = bind calc( i ) on replace old {
println( “b has changed from <{old}> to <{b}>” );
if ( b == old ) {
println( “!!!!!!!!!!!!!!!!!!!!!!!” );
println( “Why is on replace called? Both objects are the same: {isSameObject( b, old )}” );
println( “!!!!!!!!!!!!!!!!!!!!!!!” );
}
};

println( “i: {i}” );
i = 0.5;

println( “i: {i}” );
i = 0.9;

println( “i: {i}” );
i = 1.0;

println( “i: {i}” );

function calc( i ) {
return i < 1;
}
[/cc]

Of course I have posted a question in the JavaFX forum. But no answers yet….

UPDATE:
Has finally been fixed in 1.3! Now it works as expected.