📜 ⬆️ ⬇️

Using bind in JavaFX

When I first became acquainted with the technique of linking variables, at first I wanted to link everything in a row, it was so fascinating. Like any technology, JavaFX and binding should not be thoughtlessly applied. It should be remembered that the binding is in essence the hidden implementation of the Observer pattern (or Listeners, as you like). As a result, there may be a lot of not obvious problems, such as memory leaks, performance problems, etc.

In this post, I would like to cite a number of patterns and anti-patterns of applying binding in JavaFX. In addition, the second task is to publish the answers to some questions that were often asked on Sun Tech Days when I was on duty at the JavaFX booth. It seems to me that many such questions are poorly lit, and especially in runet.

So, for the cause. It is assumed that the reader has at least some idea of ​​JavaFX. However, in case the reader knows only hearsay about bind, I’ll give a simple example of its use:
	 value: String = "Hello, World";
	 def boundValue: String = bind "The title is: {value}";

	 FX.println ("{boundValue}");

	 value = "Yet another value";

	 FX.println ("{boundValue}");


The bind keyword in the second line says that the value of the variable boundVariable is always equal to the result of evaluating the expression following the keyword (“The title is: {value}”).
')
If you run the application, we get:
	 cy6ergn0m @ cgmachine ~ / test / fx $ javafxc Main.fx
	 cy6ergn0m @ cgmachine ~ / test / fx $ javafx Main    
	 The title is: Hello, World
	 The title is: Yet another value


Whatever way the value of the value variable is changed, the variable boundVariable is updated automatically (due to the runtime code hidden inside JavaFX, which registers the listener to the value variable).

The expression used in the binding can be both simple (one to one) and complex: you can call functions and arithmetic operations.

It should be noted that if a function is called in the bind expression, the dependencies are calculated in such a way that the variable is recalculated only if the variables participating in the expression change, and not those used inside the function used.

For example:
	 var a: Integer = 1;
	 var b: Integer = 2;

	 function test (value: Integer): Integer {
	       value + b
	 }

	 def boundVariable: Integer = bind test (a);

	 FX.println ("bound: {boundVariable}");

	 a = 2;

	 FX.println ("bound: {boundVariable}");

	 b = 3;

	 FX.println ("bound: {boundVariable}");


Perform and see:
	 cy6ergn0m @ cgmachine ~ / test / fx $ javafxc Main2.fx 
	 cy6ergn0m @ cgmachine ~ / test / fx $ javafx Main2
	 bound: 3
	 bound: 4
	 bound: 4
	


It can be seen that when we updated the “a” variable, the boundVariable was recalculated, and when we updated “b”, nothing happened, since “b” is not used in the bind expression.

JavaFX perceives the function involved in the expression as a “black box” and does not attempt to take into account possible side effects from both the function itself and the rest of the code (except for the expression itself in bind of course).

This seemingly inappropriate behavior can be used to advantage. Thus, it is possible to protect against overly frequent variable recalculation (useful in difficult cases when the calculated expression creates complex and heavy objects). It often turns out that we don’t really want to recalculate the whole expression if ANY values ​​involved in it change.

In such cases, such “passive” parts can be brought inside a separate function. In addition, it can help avoid cascading updates, when many variables depend on each other and begin to update many times. Sometimes the number of recalculations becomes so large, and the objects created are so heavy that the application performance can suffer greatly.

It should also be noted that EACH change to a variable with which some other is associated results in an immediate transfer of all dependent variables. So the assignment operation (the hidden call to the set method) will not complete until all dependencies have been updated. If some other variables depend on the dependent variables, they will also be recalculated, etc. all variables will be recursively updated. In this case, you can get a cyclic "infinite" update (it is of course finite, since it will be completed due to the stack overflow).

At the booth they asked questions about the possibility of dynamically creating objects / components in the application. In many cases, you can bind controls to a data model directly using bind. Then, with changes in the application's data model, the display (UI) will be updated automatically.

For example,
	 import javafx.scene.Scene;
	 import javafx.stage.Stage;
	 import javafx.scene.control.Button;
	 import javafx.scene.Group;

	 var itemsList: String [] = ["One", "Two", "Three"];

	 function loadItems (): Void {
	     // do interaction with server-side, read from file, etc ..
	     itemsList = 
	         for (i in [1..10])
	                 "element {i}";
	 }


	 Stage {
	     width: 200
	     height: 400
	     scene: Scene {
	         content: [
	             Button {
	                 text: "Load ..."
	                 action: loadItems
	             }
	             Group {
	                 content: bind
	                     for (item in itemsList)
	                         Button {
	                             text: "{item}"
	                             height: 20
	                             translateX: 10
	                             translateY: 28 + 22 * ​​indexof item
	                         }
	             }
	         ]
	     }
	 }
	


If we run the application, we will see a window. The list of buttons under the “Load” button is associated with the “itemsList” list, and always when we update it (the itemsList list), the composition of the buttons also changes. Thus, we have linked the display (buttons) with the application's data model (in this case, just a list of strings). In addition, here we have applied the binding of not just values, but the associated sequences (Sequence, list in JavaFX terminology) with lists.

application view after clicking the Load button

It should be noted that when recalculating the expressions for Group.content all the buttons will be recreated, and the old instances of the buttons will be destroyed. So if an application has a complex and large scene, then you should not associate it entirely with the data model, since frequent updates of the model will lead to frequent re-creation of all components / objects of the scene, which will lead to performance problems. At the same time, such a binding is extremely convenient and reliable, and the probability of errors is much less.

The logic of the loadItems function could be implemented somehow more interesting, for example, one could call a web service and retrieve data from the server.

Among other things, the binding works not only if we reassign the entire sequence, but also if we change only one of the elements of the sequence.
For example, if we replace the loadItems function code with the following:
	 function loadItems (): Void {
	     // do interaction with server-side, read from file, etc ..
	     itemsList [1] = "Hey, Iam here!";
	 }


When you click on the Load button, the list of buttons below will also be recreated and our change will take effect.

Among other things, I was also asked about the possibility of bind working in JavaFX when used with Java. The answer in this case is simple: if we update the JavaFX field from Java, then all the binds will work, since from Java we call the set method for this field, which is generated by the JavaFX compiler. This method in turn will do everything necessary when updating the field value.

However, in some cases there is a need for the opposite: you need to bind the JavaFX variable with a variable in Java. In this case, you will have to do it manually as follows:
- create a Java listener interface
- create a kind of Java-JavaFX adapter on JavaFX that would implement this interface and contain a public-read field, and a certain method would add it to the list of Java code listeners;
- when something changes in the Java code, the Java code calls all listeners, including our JavaFX listener, which will update its public-read field.

This is the most public-read field and you can already be tied up from the rest of the JavaFX code.

Example:

Listener Interface (Listener.java):
 public interface Listener {
	 void notifyChanged (int newValue);
 }


Java code (JavaPart.java):
 public class JavaPart {
	 private int observerableValue;

	 public void setObserverableValue (int newValue) {
		 observerableValue = newValue;

		 Listener l = listener;
		 if (l! = null)
			 l.notifyChanged (newValue);

	 }

	 public int getObserverableValue () {
		 return observerableValue;
	 }

	 private Listener listener;

	 public void setListener (Listener l) {
		 listener = l;
	 }

 }


JavaFX adepter (JavaPartAdapter.fx):
 public class JavaPartAdapter extends Listener {

	 public-init var javaPart: JavaPart;

	 init {
		 javaPart.setListener (this);
	 }

	 public-read var currentValue: Integer;

	 public override function notifyChanged (newValue: Integer): Void {
		 currentValue = newValue;
	 }

 }


And the JavaFX code that uses the classes above to bind its variable to a variable from the Java class (Main.fx):
 import javafx.scene.Scene;
 import javafx.stage.Stage;
 import javafx.scene.control.Button;
 import javafx.scene.Group;

 def javaPart: JavaPart = new JavaPart ();
 def adapter: JavaPartAdapter = JavaPartAdapter {
	 javaPart: javaPart;
 };

 Stage {
     width: 200
     height: 300
     scene: Scene {
	 content: [
	     Button {
		 text: bind "Change {adapter.currentValue}"
		 action: function () {
			 javaPart.setObserverableValue (77);
		 }
	     }
	 ]
     }
 }


This is how it looks when executed. On the left - before pressing the button, and on the right - after. As you can see, calling the setObserverableValue method led to a listener call (JavaPartAdapter), which updated the currentValue variable, which we refer to in the UI.

BeforeAfter

Although, at first glance, the technique looks somewhat cumbersome, but it is simple in its essence and works reliably. Thus, we can assume that there is a “decent” way to associate JavaFX variables with Java variables.

Nor should the following potential danger be overlooked. In JavaFX, it is possible to get a “memory leak” due to inattentive handling with bind. As previously noted, bind actually implicitly creates a listener, i.e. backlink occurs. If links are not reset in a timely manner, then listeners may cause unused objects to be still achievable, so that they will not be destroyed during garbage collection. In this scenario, you can get Out Of Memory.

For example:
 class A {
         var a: Integer;
 }

 class B {
         var b: Integer;
 }

 def b: B = B {};

 for (i in [1..100000]) {
         var a: A = A {
                 a: bind bb
         };
 }


Here, we implicitly pass references to instances of class A to variable b of class B. Variable b has a long, long list of variables dependent on it, so instances of class A cannot be thrown away during garbage collection, since they are still reachable. .

 cy6ergn0m @ cgmachine test / fx / oom $ javafx -Xmx16m Main
 java.lang.OutOfMemoryError: Java heap space
         at Main.javafx $ run $ (Main.fx: 11)
 cy6ergn0m @ cgmachine test / fx / oom $ 


But it is worth commenting out the line a: bind bb, how programs run successfully, since all created instances of class A can be easily released.

If instead write something like this:
 class A {
         var a: Integer;
 }

 class B {
         var b: Integer;
 }

 var b: B = B {};

 for (i in [1..100000]) {
         A {
                 a: bind bb
         };
         b = b {};

 }


It will be fulfilled, though not with a bang (rather slowly).
Unfortunately, in general there is no way to remove the bind. However, in many cases, you can find a workaround work, the essence of which will depend on the specifics. In general, you can give advice not to forget that bind adds a listener (a), so that when the value (bb) changes, you can find all the dependent (all A instances in my example) and notify them about the need to recalculate their value (aa) .

This is where my story about bind in JavaFX comes to an end. I take off my hat to those brave brave men who have read this to the end and wish you good luck in mastering this remarkable technology, JavaFX.

Source: https://habr.com/ru/post/92099/


All Articles