property improvement suggestion

steve.x.northover at oracle.com steve.x.northover at oracle.com
Tue Oct 9 08:29:11 PDT 2012


Is Bindings.when() your friend here?  Here is some example code that 
sets the fill color of a scene based on height:

         scene.fillProperty().bind(
                 Bindings.when(scene.heightProperty().lessThan(100))
                     .then(Color.RED)
                     .otherwise(Color.BLUE));

Steve

On 08/10/2012 1:08 PM, Tom Eugelink wrote:
> 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