...and they shall know me by my speling errors.

Danno Ferrin (aka shemnon) on stuff that matters to him.

The Life and Times of a JavaFX Skin

As a consequence of the talk I gave at Gr8Conf US I’ve been wanting to create a real “Deck” control. Basically it is a stack of nodes that can be added to and navigated arbitrarily. And it displays these nodes in interesting ways. Something like a Powerpoint deck or a deck of cards. Also useful for UIs that use the “back” metaphor but the path forward isn’t fixed, like a Wizard.

Last evening I finally got around to coding some stuff up. My work is currently on github under my craftily named DeckControl project. Nothing earth shattering (yet). So there will be no screenshots or demos in this post. What I do want to talk about is the Skin model JavaFX is using. These tidbits I have yet to find in any documentation. But that also may be because I haven’t really read the documentation beyond a cursory level, or any of the books for that matter. I still haven’t finished reading the Wheel of Time, so it’s not too surprising.

Skins in JavaFX provide something Swing never could fully arrive at: total separation between the abstract control and the literal representation of the control on the screen. There were always ways to subvert and abuse the PLAF mechanisms. While a poorly constructed control can still present problems the architecture is there for a complete look and structure separation.

Structure of a Skin

A Skin is mostly a class that extends javafx.scene.control.Skin. I say mostly because there are two caveats. First, the Skin interface is a generic interface. The type parameter must be the same type as the control you are skinning. Second you must provide a constructor that takes a single argument of the type of the generic parameter. A piece of trivia is that the Skin need not be a javafx.scene.Node, although they often are a StackPane. Just to prove my point my skin didn’t extend from Node.

Lifecycle of a Skin

The lifecycle of the skin is fairly straight forward. Simplified by the fact that skins are created for only one control, ever. No double dipping or reuse here. Garbage Collection has come a long way in the last 15 years.

  1. Construct the Skin
    First the single argument constructor is called. If you are subclassing a skin pass the argument to the super. If not, stash the node you are passed in. You will need it later.
    Next you will need to prepare everything you need. The constructor is a good place, although lazily in the next step is an option. But I think initializing in the constructor is the best place.
  2. Stuff Happens
    Stuff in the world will now happen. Some of this may or may not affect your control you are skinning. It’s hard to tell, unless you attached listeners to the parts of the world you care about.
  3. Node and Control Queried
    Two of the interface methods may now get called. They may get called multiple times, or once. Or not at all in pathological cases. (The kind of pathological cases QA departments are paid to find).
    First for getSkinnable() you must return the control passed into your constructor. If you subclassed an existing skin you are in the clear.
    Second, getNode() will be called to get the singular node you are using to represent the control. If you extend a Node class and it is the representation, return this. Otherwise return the node you are using. But whatever you do you must always return the same value (until you don’t have to, see step 6).
  4. More Stuff Happens
    This is much like step 2. Except now that your node is in the wild anything in the world that changes that you need to reflect in the skin node tree must be reflected in that node. In my case I have to pay attention to what nodes are being added to the deck and what node in the deck is showing. This step repeats with step 3 for an indeterminate number of cycles, zero or more times. Make sure you play nicely with repetition
  5. Disposal
    Finally, dispose() is called. This is JavaFX saying “we’re through” and storming out the door, never(?) to be seen again. There is no getting back together, if a new skin of the same kind is needed it will be created anew.
    All that is left to do now is to clean up whatever messes you made. Most importantly this includes any listeners you have attached outside the world of your Skin. So keep track of listeners you attach. And if you are not sure if the listener left your little world then cut it off, just to be sure.
  6. Afterlife
    There is a chance that JavaFX may drunk-dial you and getNode() and getSkinnable() may be called again. Don’t get excited, things won’t be like they were before, this was an accident. You can return null at this point as long as you immediately start returning null after dispose() is called.

Clean up after yourself

If there is one thing to point out in the lifecycle, it is that you should clean up after yourself. The listeners you attach don’t always fall away magically. And you should design for on the fly skin changes. This isn’t like Swing where changing the PLAF after the controls were shown was the exception to the rule, and formally dissed by core developers in conference sessions. Skins will change at runtime and they will result in magical effects, so plan accordingly.