JavaFX: Building custom controls

One of the big criticisms leveled at JavaFX presently is its lack of controls (or components if you come from the Swing world). This was addressed to some degree in JavaFX 1.2, but there are still many controls missing, and some of the controls in the latest release have relatively basic API’s at this point. Certainly this will improve in future releases, but I thought I’d try to simply explain the process you need to go through to build your own controls. Who knows, you might be able to sell them if you get them good enough.

In JavaFX, there are three classes that you should become familiar with: Control, Skin and Behavior. Simply put, the Control class should be used to maintain state. It can have it’s look (but not feel) modified by assigning a different Skin instance to the skin variable. To separate the look of the control from how a user interacts with it, you should put all user interaction code into a Behavior instance, and assign it to the behavior variable within Skin.

I’ll jump right into a demo now. The demo is of a button (this term is used very, very loosely as I wanted to keep the code simple), and is split between three classes, as well as a test script to use the new button control. I have neglected to include any package and import statements, just to keep the code concise.

Firstly, the control itself simply has a text variable to store the buttons text. It also creates and sets an instance of the new skin, which handles the painting.
[sourcecode language=’javafx’]
public class JGButton extends Control {
public var text: String;

override function create(): Node {
skin = JGButtonSkin{};
super.create();
}
}
[/sourcecode]
Secondly, we have the skin class shown below. It is necessary to implement the contains and intersects methods, but you can normally just reuse the code I have below for that. Note that I can access the control variable directly, but I cast it to be an instance of JGButton so I can access the text variable. Note also that I override the behavior variable to use my own implementation. Finally, you should note that I create a Group of nodes to represent my button, and I assign this to the node variable. I attach a few mouse listener functions to this node, allowing me to palm off the functionality to the behavior class. This allows me to change the skin without worrying about the behavior code needing to be needlessly duplicated to all skins.
[sourcecode language=’javafx’]
public class JGButtonSkin extends Skin {
public var buttonControl = bind control as JGButton;
override var behavior = JGButtonBehavior{};

init {
node = Group {
content: [
Stack {
content: [
Rectangle {
width: 140
height: 90
fill: Color.RED
},

Label {
text: bind buttonControl.text;
textFill: Color.BLACK;
}
]
}
]

onMouseClicked: function(me: MouseEvent) {
(behavior as JGButtonBehavior).buttonPressed();
}

onMouseEntered: function(me: MouseEvent) {
(behavior as JGButtonBehavior).buttonEntered();
}

onMouseExited: function(me: MouseEvent) {
(behavior as JGButtonBehavior).buttonExited();
}
}
}

override function contains(localX: Number, localY: Number): Boolean {
node.contains(localX, localY);
}

override function intersects(localX: Number, localY: Number, localWidth: Number, localHeight: Number): Boolean {
node.intersects(localX, localY, localWidth, localHeight);
}
}
[/sourcecode]

Thirdly, we have the simple behavior class, which is where I have put in functions related to the buttons behavior. Oddly, the Behavior class offered by JavaFX only currently supports keyboard input, so it is necessary to write mouse input events from scratch. I’m not sure why this is, but perhaps Richard might leave a comment related to this. The only thing to note in this class is that I can access the skin and control through the skin and skin.control variables respectively.

[sourcecode language=’javafx’]
public class JGButtonBehavior extends Behavior {

public function buttonPressed(): Void {
println(“Button pressed”);
}

public function buttonEntered(): Void {
(skin.control as JGButton).text = “Button entered”;
}

public function buttonExited(): Void {
(skin.control as JGButton).text = “Button exited”;
}
}
[/sourcecode]

Finally, we have a very simple test script, with nothing really worth noting. When the application starts, we have a red ‘button’ with the text “Button Text” printed atop it. When the mouse clicks on it, “Button pressed” is printed to the console. When the mouse moves over it, the text of the button changes to “Button entered”, and when it moves away from the button, the text changes to “Button exited”. Yes, it’s a very useful button.

[sourcecode language=’javafx’]
Stage {
width: 250
height: 200
scene: Scene {
content: [
JGButton {
text: “Button Text”
}
]
}
}
[/sourcecode]

I hope this quick and simple tutorial on creating custom controls in JavaFX has been useful. I tried to keep everything as simple as possible, but if you have any more detailed questions, please leave a comment or email me. Cheers!

8 thoughts on “JavaFX: Building custom controls”

  1. nice post but the source code is unviewable under safari iphone because there is no horizontal scroll. pleaze check the look and feel under safari iphone user agent. thanks

  2. @joke: I’m aware it doesn’t work on iPhone – I have one. I can’t resolve this issue without changing how my site does formatting, which is too much work. I suggest you try on a real browser 🙂

    1. Eric,

      Thanks for this. Unfortunately I presently use the Google Syntaxt Highlighter for wordpress, which for better or worse is the one I am stuck on unless I want to go back and rework all my source code examples.

      Thanks though – and thanks for the JavaFX improvements. You should get them into the Google one – it’s the same base.

      — Jonathan

  3. Hi Johnathan. I stumbled on your blog post after having problems creasting my own UI control. Your example works fine but I am not sure how. If you println the buttonControl variable at the top of the init() method in the skin class it’s null. This is the problem I am facing in my control but what is strange is although it’s null it seems to be working in your example. I suspect it’s somethiing to do with binding but any ideas?

    init {
    println(“skininit{buttonControl}”); //null
    ..
    ..
    ..
    text: bind buttonControl.text;
    }

  4. Thanks for this post. What would be the best way to add for example animated hover effect on JGButton control. I could add them in place where I initiate control in form of onMouseEntered, or should it be in JGButton as properties(hoverColor, hoverOpacity, hoverDuration etc) and animated in JGButtonBehavior class?

    1. @Marko: Sorry for taking so long to reply – things have been hectic around here. To quickly answer your question – if the property is a publicly-settable property, then it should be on your subclass of Control. If it is not something you want to expose publicly, it should be on your subclass of Skin. Additionally, because this is related to the skin and not behavior, I would say that this should have nothing to do with behavior.

      My recommendation would be to probably consolidate everything inside the Skin subclass – detect the hover and then start the animation.

      Hope that helps – sorry for the brevity – but feel free to ask any further questions.
      Cheers,
      Jonathan

Comments are closed.