Future of Skins
John Hendrikx
hjohn at xs4all.nl
Tue Jan 7 02:30:40 PST 2014
Hi List,
I'm wondering if Skins in their current form are "finished" or that more
changes are still likely to come. The Skin interface is public, and
SkinBase is as well. Unfortunately, it seems that even though SkinBase
is treated differently by JavaFX from regular Skins, the opportunity was
not used to give SkinBase an initialize method and make Skins truly
reusable.
The reason I'm asking is because I'm developing a control that would
greatly benefit from customizable and switchable skins, which differs
quite a bit from the use patterns I've seen so far for Controls in
JavaFX (I don't think any of them has more than one Skin out of the
box). My control would need:
- Multiple different layouts, each with potentially different (CSS)
properties to customize the chosen layout further (the properties would
only make sense for the given layout (or skin)).
- Easy switching between different layouts
- Highly customizable (possibility to override methods in the layouts or
provide custom code for certain parts of the rendering)
I've been looking into providing this with Skins, but I keep bumping
into odd choices made, especially related to their resuability. As far
as I know, a Skin is created with a reference to the control it is
supposed to Skin -- at this time the Skin (if it is a SkinBase)
apparently has done all kinds of stuff to the control, whether setSkin
is called or not.... ehr?? Then later you are supposed to call setSkin
on the control with the newly created Skin... which is apparently an
almost optional step, unless it is not a SkinBase...?
- What happens when I create the Skin but donot call setSkin? -->
Undefined, depends on whether or not it is extended from SkinBase (?)...
SkinBase skins seem to make this step almost optional.
- What happens when I call setSkin on a different control with this
Skin? --> Depends on whether or not SkinBase is extended. Would break
controls in most situations -- no check is done if getSkinnable matches
AFAICS.
- What happens when a disposed skin is setSkin'ned again? --> Seems to
break most controls, no check is done if getSkinnable is null AFAICS.
This arrangement where the Skin class must be constructed with some
parameter that must later match with the setSkin feels incredibly
fragile to me. It would be far nicer if a Skin was simply given an
initialize method (which would also nicely match the dispose method)
that takes the Skinnable as a parameter. This initialization can be
enforced in the setSkin method. There is even a remark in the code
about this:
// Note I do not remove any children here, because the
// skin will have already configured all the children
// by the time setSkin has been called. This is because
// our Skin interface was lacking an initialize method
(doh!)
// and so the Skin constructor is where it adds listeners
// and so forth. For SkinBase implementations, the
// constructor is also where it will take ownership of
// the children.
Would it not have been a better idea to add an #initialize method to
SkinBase if the code has two paths for "legacy" Skins and SkinBase skins
anyway? Or a Skin2 interface with the extra method + instanceof check?
Or Java8 default #initialize method which does nothing by default?
Any of those would open up Skins to be far easier to use by making them
reusable. I really want to be able to do something like this:
ComboBox<Skin> comboBox = new
ComboBox<>(FXCollections.observableArrayList(
new Skin1(),
new Skin2(),
new Skin3(),
new SubClassOfSkin1(Color.RED)),
new Skin2() {
// with some methods overriden
}
));
Then bind those simply with:
myControl.skinProperty().bind(comboBox.getSelectionModel().selectedItemProperty());
This would allow a ComboBox to control the Skin of another Control,
because skins are reusable AND donot require knowledge of the control
they are Skinning at construction time. This is currently not possible
without creating somekind of SkinFactory and a ChangeListener on the
ComboBox that does something like:
myControl.setSkin(skinFactory.create(myControl));
This problem gets worse when you also want to customize the Skins a
bit. All the customization must be done in the SkinFactory part, every
time the Skin is switched. With reusable skins, they can just be
configured once.
So I'm left with a bit of a choice. Do I make multiple Skins and a
bunch of Factories? Or do I simply create just one fixed Skin that just
delegates everything it does to a second light-weight Skinning mechanism
that is easier to switch, customize and extend?
--John
More information about the openjfx-dev
mailing list