Custom controls often feature “read-only” properties. This means that they can not be set from outside the control, not even from their own skin class. It is often the behaviour of a control that leads to a change of the read-only property. In JavaFX this behaviour can be implemented in the control itself and in the skin. So we sometimes end up with a skin wanting to update a read-only property of the control. How can this be done?

Backdoor: Property Map

The solution is quite simple: use the properties map of the control as a backdoor to the control class. The properties map is observable, so if the skin sets a value in the map then the control will be informed and can update the value of the read-only property itself.

The Control Class

The property in the control class might be defined like this:

private final ReadOnlyDoubleWrapper myReadOnly =
   new ReadOnlyDoubleWrapper();

public final ReadOnlyDoubleProperty myReadOnlyProperty() {
    return myReadOnly.getReadOnlyProperty();
}

public final Double getMyReadOnly() {
    return myReadOnly.get();
}

To update the property the control class registers a listener with its own property map and listens for changes to the property called “myReadOnly”:

getProperties().addListener(new MapChangeListener() {
  public void onChanged(Change c) {
    if (c.wasAdded() && "myReadOnly".equals(c.getKey())) {
      if (c.getValueAdded() instanceof Number) {
        myReadOnly.set((Double) c.getValueAdded());
      }
      getProperties().remove("myReadOnly");
    }
  }
});

Important: make sure to use a unique name for the property key or you might end up with naming conflicts. It is good practise to prefix the name with the package name of your control, e.g. com.myframework.myReadOnly.

The Skin Class

Now the skin class can update the property by setting the property value in the control’s property map:

getSkinnable().getProperties().put("myReadOnly", 42);