Search code examples
javabindingconvertersjgoodies

Read-only binding and one-way converters using JGoodies binding


I am attempting to use JGoodies Binding 2.9.1 to bind the enabled property of a JTextField to an enum property. I want this to be a one-way operation: when the property changes, I want the JTextField to become enabled or disabled, but I never want to go the other way and set the enum property based on the enabled value.

Although I think I set up everything properly, I am getting a PropertyAccessException: Failed to set an adapted Java Bean property.

My model is a standard Java Bean with property change events:

public static String MY_PROPERTY = "myProperty";
private MyEnum myProperty;

public MyEnum getMyProperty() { return myProperty; }

public void setMyProperty(final MyEnum newValue) {
    final MyEnum oldValue = myProperty;
    if (newValue == oldValue) { return; }
    myProperty = newValue;
    changeSupport.firePropertyChange(MY_PROPERTY, oldValue, newValue);
}

In my view I have a one-way converter that will return true when the bound value matches the provided value:

private final class EnumMatchToEnabledConverter implements BindingConverter<MyEnum, Boolean> {
    private MyEnum match;

    public EnumMatchToEnabledConverter (MyEnum match) {
        this.match = match;
    }

    @Override
    public Boolean targetValue(MyEnum source) {
        return (source == match);
    }

    @Override
    public MyEnum sourceValue(Boolean target) {
        // this wouldn't make sense
        throw new UnsupportedOperationException();
    }
}

Then I set up the binding:

PresentationModel<MyModel> pm = new PresentationModel<MyModel>(model);
Bindings.bind(
    myTextField, "enabled", new ConverterValueModel(
        pm.getModel(MyModel.MY_PROPERTY),
        new EnumMatchToEnabledConverter(MyEnum.MyValue)));

To my surprise, EnumMatchToEnabledConverter's sourceValue() method gets called, and because it raises an UnsupportedOperationException I get a PropertyAccessException from the binding.

I also tried explicitly telling the binding not to use the setter, but I still got the same behavior:

Bindings.bind(
    myTextField, "enabled", new ConverterValueModel(
        pm.getModel(MyModel.MY_PROPERTY, "getMyProperty", null), // null setter!
        new EnumMatchToEnabledConverter(MyEnum.MyValue)));

Where am I going wrong?


Solution

  • I found a workaround. It's ugly, but it works.

    I wrap my ConverterValueModel with a BufferedValueModel whose ValueModel trigger never triggers.

    Bindings.bind(
        myTextField,
        "enabled",
        new BufferedValueModel(
            new ConverterValueModel(
                pm.getModel(MyModel.MY_PROPERTY),
                new EnumMatchToEnabledConverter(MyEnum.MyValue)),
            new AbstractValueModel() {
                @Override public Object getValue() { return null; }
                @Override public void setValue(Object o) {}
            }));