Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I am only now learning JavaFX so take this answer with a grain of salt... and any corrections are welcome. I was interested myself with this, so did a little research.</p> <h2>Invalidation listeners</h2> <p>The answer to this problem is partially the <a href="http://docs.oracle.com/javafx/2/api/javafx/beans/InvalidationListener.html" rel="nofollow"><code>InvalidationListener</code></a>. You can read the docs in detail <a href="http://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm" rel="nofollow">here</a>, but the essence is that a <code>ChangeLister</code> propagates the change immediately, while the <code>InvalidationListener</code> takes a note that a value is invalid but defers the computation until it is needed. An example demonstrating the two cases based on the "z / (x - y)" calculation:</p> <p>First, the trivial stuff:</p> <pre><code>import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.binding.DoubleBinding; import javafx.beans.binding.NumberBinding; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableNumberValue; import javafx.beans.value.ObservableValue; public class LazyExample { public static void main(String[] args) { changeListenerCase(); System.out.println("\n=====================================\n"); invalidationListenerCase(); } ... } </code></pre> <p>The 2 cases (change and invalidation listener) will set up 3 variables, <code>x</code>, <code>y</code>, <code>z</code>, the computed expression <code>z / (x - y)</code> and the appropriate listener. Then they call a <code>manipulate()</code> method to change the values. All steps are logged:</p> <pre><code> public static void changeListenerCase() { SimpleDoubleProperty x = new SimpleDoubleProperty(1); SimpleDoubleProperty y = new SimpleDoubleProperty(2); SimpleDoubleProperty z = new SimpleDoubleProperty(3); NumberBinding nb = makeComputed(x,y,z); nb.addListener(new ChangeListener&lt;Number&gt;() { @Override public void changed(ObservableValue&lt;? extends Number&gt; observable, Number oldValue, Number newValue) { System.out.println("ChangeListener: " + oldValue + " -&gt; " + newValue); } }); // prints 3 times, each after modification manipulate(x,y,z); System.out.println("The result after changes with a change listener is: " + nb.doubleValue()); } public static void invalidationListenerCase() { SimpleDoubleProperty x = new SimpleDoubleProperty(1); SimpleDoubleProperty y = new SimpleDoubleProperty(2); SimpleDoubleProperty z = new SimpleDoubleProperty(3); NumberBinding nb = makeComputed(x,y,z); nb.addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { System.out.println("Invalidated"); } }); // will print only once, when the result is first invalidated // note that the result is NOT calculated until it is actually requested manipulate(x,y,z); System.out.println("The result after changes with an invalidation listener is: " + nb.doubleValue()); } </code></pre> <p>And the common methods:</p> <pre><code> private static NumberBinding makeComputed(final ObservableNumberValue x, final ObservableNumberValue y, final ObservableNumberValue z) { return new DoubleBinding() { { bind(x,y,z); } @Override protected double computeValue() { System.out.println("...CALCULATING..."); return z.doubleValue() / (x.doubleValue()-y.doubleValue()); } }; } private static void manipulate(SimpleDoubleProperty x, SimpleDoubleProperty y, SimpleDoubleProperty z) { System.out.println("Changing z..."); z.set(13); System.out.println("Changing y..."); y.set(1); System.out.println("Changing x..."); x.set(2); } </code></pre> <p>The output from this is:</p> <pre class="lang-none prettyprint-override"><code>...CALCULATING... Changing z... ...CALCULATING... ChangeListener: -3.0 -&gt; -13.0 Changing y... ...CALCULATING... ChangeListener: -13.0 -&gt; Infinity Changing x... ...CALCULATING... ChangeListener: Infinity -&gt; 13.0 The result after changes with a change listener is: 13.0 ===================================== ...CALCULATING... Changing z... Invalidated Changing y... Changing x... ...CALCULATING... The result after changes with an invalidation listener is: 13.0 </code></pre> <p>So in the first case there is an excessive number of calculations and an <code>infinity</code> case. In the second the data is <em>marked</em> invalidated on the first change and then recalculated only when needed.</p> <h2>The Pulse</h2> <p>What about binding graphical properties, e.g. the width and height of something (as in your example)? It seems that the infrastructure of JavaFX does <em>not</em> apply changes to graphical properties immediately, but according to a signal called the <a href="http://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm#sthref5" rel="nofollow">Pulse</a>. The pulse is scheduled asynchronously and, when executed, will update the UI based on the current state of the properties of the nodes. Each frame in an animation and each change of UI properties will schedule a pulse to be run.</p> <p>I do not know what happens in your example case where, having initial width=1px and height=10<sup>6</sup>px, the code sets width=10<sup>6</sup>px (in one step, schedules pulse) and then height=1px (second step). Does the second step emit another pulse, if the first hasn't been processed? The reasonable thing to do from JavaFX's point of view is for the pipeline to process only 1 pulse event, but I need some reference for that. But, even if two events are processed, the first should process the entire state change (both width and height) so changes occur in one visual step.</p> <p>The developer will have to be consious of the architecture I believe. Suppose a separate task does (pseudocode):</p> <pre><code>width = lengthyComputation(); Platform.runLater(node.setWidth(width)); height = anotherLengthyComputation(); Platform.runLater(node.setHeight(height)); </code></pre> <p>I <em>guess</em> if the first pulse event has a chance to run, then the user will see a change of width - pause - a change of height. It would be better to write this as (again, always in a background task) (pseudocode):</p> <pre><code>width = lengthyComputation(); height = anotherLengthyComputation(); Platform.runLater(node.setWidth(width)); Platform.runLater(node.setHeight(height)); </code></pre> <p>UPDATE (Comment from john16384): According to <a href="http://mail.openjdk.java.net/pipermail/openjfx-dev/2013-November/011308.html" rel="nofollow">this</a> it is not possible to listen to the pulse directly. However, one can extend certain methods of <code>javafx.scene.Parent</code> that are run once per pulse and achieve the same effect. So you either extend <code>layoutChildren()</code>, if no changes to the children tree is required or either of <code>computePrefHeight(double width)</code>/<code>computePrefWidth(double height)</code>, if the children tree will be modified.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload