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