property improvement suggestion

Tom Eugelink tbee at tbee.org
Mon Oct 8 10:08:16 PDT 2012


Hmmmm, not sure we are on the same line here. The essence of the proposal is that the user of a property can specify a constraint. Let's see how I can make a good argument for this.

There are two angles. The first being the binding; I recently recoded my Agenda (Google Calendar) control to use binding and really liked that, but I ran into some omissions IMHO. Suppose I bind an appointment height to the duration of the appointment. So, basically you get something like:

appointment.duration().bind(area.height().multiply(pixelToSecondsFactor));

A UI feature is that the user can resize an area by dragging the end time (bottom of the rectangle) with the mouse. Theoretically he can drag the mouse above the start time, resulting in a negative height. The binding will then result in a negative duration, and that is something I do not want. The problem is that I am not the owner of the appointment (it comes from the business model), nor the owner of the area (it's a rectangle; and even if I extend Rectangle, I cannot extend the height property). So I need a way to prevent that situation to occur, because the BM code handling the appointment will never expect a negative duration, or an end time that falls before the start time (the constraints of the BM entity :-).

Because the binding allows calculation, it almost requires constraints, or a decent hook system. Or I must find a way to get a man-in-the-middle setup (I did that with JGoodies binding), but native constraints are better.

The second angle is that the properties concept is a powerful one, and I expect them to become popular in none JavaFX contexts as well. Formal properties is something the community have been begging for for a long time. These other context, say a mortgage business model, may be less lenient towards the possible values a property can have and require a strict handling of the values, and maybe (yes) throw exceptions. Further more these constraints may be fairly complex, so a simple "if a then b" might not cut it. So if we're contemplating constraints, then let's look at the bigger picture.

Now, I'm not knowledgable enough on the implementation that I can discuss the implementation details, but I understand the problem of the lazy binding your describing. Maybe, in light of the bigger picture, eager bindings are important. I only know that being able to hook into binding to make sure a value never goes out of range, both directly on a property (as a permanent constraint) or inside a binding chain (as a local constraint), seems like a very natural thing.

Tom



On 2012-10-08 16:26, Richard Bair wrote:
> It looks like there are two elements of this proposal. The first is that the author of a bean might want to specify some inherent constraint
> on the property -- such as, it should never be null, or it should never be negative. The second is that a user of the property might want to constrain the property in some way -- such as, in my app the width should never be negative.
>
> I see the value immediately in the first (in fact, this has always been a problem with our properties, going all the way back to JavaFX Script), but I struggle to see much value in the second. Not because you might not want to do this sort of thing, but because the first (inherent constraints) is not presently possible at all, while the second (app constraints) you can already do in your application code, one way or another. I am inclined not to add API for cases that are already solvable, unless it turns out to be so compelling and widely used.
>
>  From a semantic point of view, binding was a "mathematical" relationship between two properties in JavaFX Script, such that when you read code like:
>
> x: bind 10 * someProperty
>
> you new that "x" was going to be 10 * someProperty, and not anything else. Of course as an API designer I have never liked that because I have to handle exceptional conditions in my code rather than being able to verify that a property can never go outside its valid range of values.
>
> I am pleased to see your proposal doesn't include throwing exceptions -- I think throwing an exception from a property on invalid input is a very bad idea. The problem is in lazy evaluation of properties. Suppose:
>
> widthProperty().bind(someProperty)
>
> Further suppose width is constrained to never be < 0, and somebody were to try to set it to be < 0 it would throw an exception. In this case, someProperty might be set to -10 at some time, and the width property is invalidated. But until somebody tries to read width, it won't throw the exception. So the problem with bound properties & exceptions is that the exception is not thrown at the time the error is introduced, but rather at the time the error is discovered, which turns out to be an awful behavior.
>
> However your proposal instead says "if it is outside the allowable range, I will just adjust the value to keep it within the allowable range". I think this is a really good idea. The property implementations would have to be modified such that on read, instead of:
>
>      /**
>       * {@inheritDoc}
>       */
>      @Override
>      public boolean get() {
>          valid = true;
>          return observable == null ? value : observable.get();
>      }
>
> we instead do something like:
>
>      /**
>       * {@inheritDoc}
>       */
>      @Override
>      public boolean get() {
>          if (!valid && observable != null) {
>             value = constrain(observable.get());
>          }
>          valid = true;
>          return observable == null ? value : observable.get();
>      }
>
>
> And add a protected 'constrain' method. Or perhaps we can just call the "set" method and let the set method deal with constraining. This would require taking some care, however, because it means set methods will now be called even when the property is bound which is different than at present, and calling super.set() would throw an exception in the case that it is bound. But still, as a Java developer I'm used to doing constraining from within the setter, so doing it inside the set seems the most natural, but this would be an area that needs to be figured out.
>
> Richard
>
> On Oct 7, 2012, at 12:10 AM, Tom Eugelink wrote:
>
>> As far as I understand, the current the approach is that properties should accept any value, and the usage of these properties have to work with that. Personally I'm prefer a different approach, because I feel the responsibility to have sensible values should be constrained to one place. Therefore I often override the set method of my properties to make sure the value is within the range that my remaining code expect it to be; either by throwing an exception or by clamping the value to a range.
>>
>> But this I can only do with my own properties. It would be nice to also do this to existing properties and therefor I would like to suggest to extend the property concept with constraints or conditions. The important point being that these constraints are applied before the value is set, and not (like a change listener approach) afterwards and then correct value; a value should never be outside it allowed range.
>>
>> The constraints or conditions could have an if-then structure: if value < 0 then 0. An example in Java 8 lambda notation:
>> someNode.widthProperty().*constraint*( w -> w < 0, 0);
>>
>> This would also be something relevant to binding, especially when doing calculations:
>> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*constraint*( w -> w < 0, 0) );
>>
>> There is a difference between a permanent constraint on the property and a constraint only applicable to a binding. The notation above is is too similar to my taste and too easily a binding constraint can become a property constaint. To make this difference more explicit, the word "if" could be better on binding:
>> someNode.widthProperty().*constraint*( w -> w < 0, 0);
>> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*if*( w -> w < 0, 0) );
>>
>> Something to think about is how far these constraints should be taken. These are simple if-then constraints. But what if "else", "elsif" constructions or "case" statements are needed to express the range? In order to keep all options open, maybe the best approach would be to provide constraining listeners. A constraining listener would get the to-be-set value as a parameter and return the new value. For example:
>>
>> widthProperty.addConstrainingListener( v -> { if (v < 0.0) return 0.0; return v; } );
>>
>> or in normal notation:
>>
>> widthProperty.addConstrainingListener( new ConstaintListener<Double>() {
>>     public Double constaint<Double>(Double value) {
>>         if (value < 0.0) return 0.0;
>>         return value;
>>     }
>> });
>>
>> (NB: I'm not sure how to use Java 8 lambda with the addListener method, differentiating between a change, invalidation and constraint listener.)
>>
>> This constraining listener approach would allow for all scenario's to be handled. So that is the final improvement I would like to suggest: constrainting listeners.
>> someNode.widthProperty().*constraint*( v -> { if (v < 0.0) return 0.0; return v; } );
>> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*if*( v -> { if (v < 0.0) return 0.0; return v; } ) );
>>
>> And if possible normal constraints, because that is a easier notation.
>>
>> I'd like to hear if I'm making sense or am missing the ball completely.
>>
>> Tom



More information about the openjfx-dev mailing list