JavaFXでバインドを使用する

私が最初に変数バインディングのテクニックに慣れたとき、最初はすべてを連続してバインドしたかったので、とても刺激的でした。 他のテクノロジーと同様に、JavaFXとバインディングは無頓着に使用しないでください。 バインディングは、基本的にオブザーバーパターン(または必要に応じてリスナー)の隠された実装であることに注意してください。 その結果、「メモリリーク」、パフォーマンスの問題など、多くの明白でない問題が発生する可能性があります。

この投稿では、JavaFXでバインディングを使用する場合のパターンとアンチパターンをいくつか紹介します。 さらに、2番目のタスクは、私がJavaFXブースで「勤務中」だったときにSun Tech Daysでよく聞かれたいくつかの質問への回答を公開することです。 これらの問題の多くは、特にRunetで十分にカバーされていないように思えます。

だから、ポイントに。 読者は少なくともJavaFXをある程度理解していることを前提としています。 ただし、バインドについて読者が直接知っている場合は、その使用の簡単な例を示します。
	値:String = "Hello、World";
	 def boundValue:String = bind "タイトルは:{値}";

	 FX.println( "{boundValue}");

	 value = "まだ別の値";

	 FX.println( "{boundValue}");


2行目のbindキーワードは、boundVariableの値が常にキーワードに続く式の評価結果と等しいことを示しています(「タイトルは{値}」)。

アプリケーションを実行すると、以下が得られます。
	 cy6ergn0m @ cgmachine〜/ test / fx $ javafxc Main.fx
	 cy6ergn0m @ cgmachine〜/ test / fx $ javafxメイン    
	タイトルは:Hello、World
	タイトルは:さらに別の価値


値変数の値がどのように変更されても、boundVariable変数は自動的に更新されます(値変数のリスナーを登録するJavaFXランタイム内に隠されたコードのため)。

リンクで使用される式は、単純(1対1)または複雑のいずれかです。関数と算術演算を呼び出すことができます。

関数がバインド式で呼び出される場合、式に含まれる変数が変更された場合にのみ変数が再カウントされるように依存関係が計算され、使用される関数内で使用される変数ではないことに注意してください。

例:
	 var a:整数= 1;
	 var b:整数= 2;

	関数テスト(値:整数):整数{
	      値+ b
	 }

	 def boundVariable:整数=バインドテスト(a);

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

	 a = 2;

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

	 bは3です。

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


実行して確認します:
	 cy6ergn0m @ cgmachine〜/ test / fx $ javafxc Main2.fx 
	 cy6ergn0m @ cgmachine〜/ test / fx $ javafx Main2
	バウンド:3
	バウンド:4
	バウンド:4
	


変数「a」を更新すると、boundVariableが再計算され、「b」を更新すると、バインド式で「b」が使用されないため、何も起こらなかったことがわかります。

JavaFXは、式に参加している関数を「ブラックボックス」として認識し、関数側とコードの残りの両方から起こりうる副作用を考慮しようとしません(もちろん、式自体はバインドされます)。

この一見劣った振る舞いをうまく利用できます。 したがって、頻繁に変数を再カウントすることから自分自身を守ることができます(計算式が複雑で重いオブジェクトを作成する複雑な場合に役立ちます)。 多くの場合、式に関係する量が変化した場合、式全体を再集計したくないことが判明します。

そのような場合、そのような「パッシブ」パーツは、別の機能内で実行できます。 さらに、これは、多くの変数が互いに依存しており、何度も更新され始めるときに、カスケード更新を回避するのに役立ちます。 カウントの数が非常に多くなり、作成されたオブジェクトが非常に重い場合、アプリケーションのパフォーマンスに深刻な影響を与えることがあります。

また、他の一部の変数が関連付けられている変数のすべての変更は、すべての従属変数の即時変換につながることに注意する必要があります。 そのため、すべての依存関係が更新されるまで、割り当て操作(setメソッドの非表示呼び出し)は終了しません。 他の変数が従属変数に依存している場合、それらも再計算されます。 すべての変数が再帰的に更新されます。 この場合、周期的な「無限」更新を取得できます(スタックオーバーフローにより終了するため、もちろん有限です)。

スタンドでは、アプリケーションでオブジェクト/コンポーネントを動的に作成する可能性について質問しました。 多くの場合、バインドを使用してコントロールをデータモデルに直接バインドできます。 その後、アプリケーションデータモデルが変更されると、表示(UI)が自動的に更新されます。

例えば
	 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 {
	     //サーバー側とのやり取り、ファイルからの読み取りなどを行います。
	     itemsList = 
	         for([1..10]のi)
	                 "要素{i}";
	 }


	ステージ{
	    幅:200
	    高さ:400
	    シーン:シーン{
	        コンテンツ:[
	            ボタン{
	                テキスト:「読み込み...」
	                アクション:loadItems
	             }
	            グループ{
	                内容:バインド
	                     for(itemsListのアイテム)
	                        ボタン{
	                            テキスト:「{item}」
	                            高さ:20
	                             translateX:10
	                             translateY:28 + 22 *アイテムのインデックス
	                         }
	             }
	         ]
	     }
	 }
	


アプリケーションを実行すると、ウィンドウが表示されます。 「ロード」ボタンの下のボタンのリストは「itemsList」リストに関連付けられており、更新するたびに(itemsListリスト)、ボタンの構成も変更されます。 したがって、表示(ボタン)をアプリケーションデータモデル(この場合は文字列のリスト)に関連付けました。 さらに、ここでは、値だけでなく、リストに関連付けられたシーケンス(シーケンス、JavaFX用語のリスト)のバインディングを適用しました。

ロードボタンを押した後のアプリケーションビュー

Group.contentの式を再計算すると、すべてのボタンが再作成され、古いボタンインスタンスが破棄されることに注意してください。 そのため、アプリケーションに複雑で大きなシーンがある場合、モデルを頻繁に更新するとシーンのすべてのコンポーネント/オブジェクトが頻繁に再作成され、パフォーマンスの問題が発生するため、完全にデータモデルに関連付けないでください。 同時に、このようなバインディングは非常に便利で信頼性が高く、エラーの可能性がはるかに低くなります。

loadItems関数のロジックは、もっと興味深い方法で実装できます。たとえば、Webサービスを呼び出してサーバーからデータを取得できます。

とりわけ、リンクは、シーケンス全体を再割り当てする場合だけでなく、シーケンスの要素の1つだけを変更する場合にも機能します。
たとえば、loadItems関数コードを次のコードに置き換えた場合:
	 function loadItems():Void {
	     //サーバー側とのやり取り、ファイルからの読み取りなどを行います。
	     itemsList [1] = "こんにちは、Iamです!";
	 }


次に、[ロード]ボタンをクリックすると、下のボタンのリストも再作成され、変更が有効になります。

とりわけ、Javaと併用した場合、JavaFXでバインドが機能する可能性についても質問されました。 この場合の答えは簡単です。JavaFXフィールドをJavaから更新すると、JavaFXコンパイラによって生成されるこのフィールドのsetメソッドをJavaから呼び出すため、すべてのバインドが機能します。 このメソッドは、フィールド値を更新するときに必要なすべてを順番に実行します。

ただし、場合によっては反対のことが必要になります。JavaFX変数をJavaの変数に関連付ける必要があります。 この場合、次のように手動で行う必要があります。
-Javaリスナーインターフェイスを作成する
-このインターフェースを実装し、パブリック読み取りフィールドを含む一種のJava-JavaFXアダプターをJavaFX上に作成し、特定のメソッドがそれをJavaコードリスナーのリストに追加します。
-Javaコードで何かが変更されると、Javaコードは、パブリック読み取りフィールドを更新するJavaFXリスナーを含むすべてのリスナーを呼び出します。

これは最も一般に公開されているフィールドであり、JavaFXコードの残りの部分から既に結び付けることができます。

例:

リスナーインターフェイス(Listener.java):
パブリックインターフェイスリスナー{
	 void notifyChanged(int newValue);
 }


Javaコード(JavaPart.java):
パブリッククラスJavaPart {
	 private int observerableValue;

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

		リスナーl =リスナー;
		 if(l!= null)
			 l.notifyChanged(newValue);

	 }

	 public int getObserverableValue(){
		 return observerableValue;
	 }

	プライベートリスナーリスナー。

	 public void setListener(リスナーl){
		リスナー= l;
	 }

 }


JavaFX熟達者(JavaPartAdapter.fx):
パブリッククラスJavaPartAdapterはリスナーを拡張します{

	 public-init var javaPart:JavaPart;

	 init {
		 javaPart.setListener(this);
	 }

	 public-read var currentValue:整数;

	パブリックオーバーライド関数notifyChanged(newValue:Integer):Void {
		 currentValue = newValue;
	 }

 }


そして、上記のクラスを使用してその変数をJavaクラス(Main.fx)の変数にバインドするJavaFXコード:
 import javafx.scene.Scene;
 import javafx.stage.Stage;
 import javafx.scene.control.Button;
 import javafx.scene.Group;

 def javaPart:JavaPart = new JavaPart();
 defアダプター:JavaPartAdapter = JavaPartAdapter {
	 javaPart:javaPart;
 };

ステージ{
    幅:200
    高さ:300
    シーン:シーン{
	コンテンツ:[
	    ボタン{
		テキスト:バインド「{adapter.currentValue}を変更」
		アクション:関数(){
			 javaPart.setObserverableValue(77);
		 }
	     }
	 ]
     }
 }


これが実行時の様子です。 左側-ボタンを押す前、右側-後。 ご覧のとおり、setObserverableValueメソッドを呼び出すと、リスナー呼び出し(JavaPartAdapter)が発生し、UIで参照するcurrentValue変数が更新されました。

に後

一見、この手法はやや面倒に見えますが、本質的に複雑ではなく、確実に機能します。 したがって、JavaFX変数をJava変数に関連付ける「適切な」方法が存在すると想定できます。

次の潜在的な危険も見落としてはなりません。 JavaFXでは、バインドの不注意な処理により「メモリリーク」が発生する可能性があります。 前述のように、バインドは実際に暗黙的にリスナーを作成します。 戻るリンクが表示されます。 リンクがタイムリーに無効にされない場合、リスナーは未使用のオブジェクトに到達可能のままにして、ガベージコレクション中に破棄されないようにすることができます。 このシナリオでは、メモリ不足が発生する可能性があります。

例:
クラスA {
         var a:整数;
 }

クラスB {
         var b:整数;
 }

 def b:B = B {};

 for(i in [1..100000]){
         var a:A = A {
                 a:bbをバインド
         };
 }


ここでは、クラスAの作成されたインスタンスへの参照をクラスBの変数bに暗黙的に渡します。変数bは、それに依存する変数の長い長いリストを形成します。 。

 cy6ergn0m @ cgmachine test / fx / oom $ javafx -Xmx16mメイン
 java.lang.OutOfMemoryError:Javaヒープスペース
         Main.javafxで$ run $(Main.fx:11)
 cy6ergn0m @ cgmachine test / fx / oom $ 


ただし、クラスAのすべての作成済みインスタンスは簡単にリリースできるため、プログラムが正常に実行されるため、行a:bind bbをコメントアウトする価値があります。

代わりに、次のように記述します。
クラスA {
         var a:整数;
 }

クラスB {
         var b:整数;
 }

 var b:B = B {};

 for(i in [1..100000]){
         {
                 a:bbをバインド
         };
         b = B {};

 }


その後、強打ではなく(ややゆっくり)行われます。
残念ながら、一般的な場合、バインドを削除する方法はありません。 ただし、多くの場合、回避策を見つけることができますが、その本質はすでに詳細に依存しています。 一般に、bindがリスナー(a)を追加することを忘れないようにアドバイスすることができます。そのため、値(bb)を変更すると、すべての依存関係(この例ではAのすべてのインスタンス)を見つけ、それらの値を再計算する必要があることを通知できます(aa) 。

これで、JavaFXでバインドを使用することについての私の話は終わりです。 この記事を最後まで読んだ勇敢な勇気ある人々への帽子を脱ぎ、この驚くべきエッセンス技術であるJavaFXをマスターする幸運を祈ります。

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


All Articles