web-dev-qa-db-ja.com

ラベルのテキストプロパティ(FXMLファイル内)をIntegerProperty(コントローラー内)にバインドする

FXMLファイルのLabelと関連するコントローラーのIntegerPropertyの間にデータバインディングを設定しました。問題は、ラベルは初期化時に正しい値に設定されますが、プロパティの値が変更されても更新されないことです。

FXMLファイル

<?xml version="1.0" encoding="UTF-8"?>

<?import Java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<GridPane xmlns:fx="http://javafx.com/fxml"
   fx:controller="application.PaneController" minWidth="200">
   <Label id="counterLabel" text="${controller.counter}" />
   <Button translateX="50" text="Subtract 1"
      onAction="#handleStartButtonAction" />
</GridPane>

コントローラー

package application;

import Java.net.URL;
import Java.util.ResourceBundle;

import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

public class PaneController implements Initializable
{
    private IntegerProperty counter;

    public int getCounter()
    {
        return counter.get();
    }

    public void setCounter(int value)
    {
        counter.set(value);
    }

    public PaneController()
    {
        counter = new SimpleIntegerProperty(15);
    }

    @Override
    public void initialize(URL url, ResourceBundle resources)
    {
    }

    @FXML
    private void handleStartButtonAction(ActionEvent event)
    {
        setCounter(getCounter() - 1);
        System.out.println(getCounter());
    }
}

期待

「減算1」ボタンを押すたびに、カウンターが1ずつ減り、counterLabelが自動的に更新されます。

現実

カウンターは1ずつデクリメントしますが、counterLabelは15(初期値)のままです。

質問

私は(たとえば、 このフォーラムの投稿 から)私がしたことはうまくいくはずだという印象を受けました。何が足りないのですか?

13
devuxer

JavaFX固有のアクセサーvariableNamePropertyをコントローラーに追加する必要があります。

_public IntegerProperty counterProperty() {
    return counter;
}
_

編集:詳細
APIドキュメントには、このJavaFXのJavaBeansアーキテクチャについてはあまり言及されていません。ここではそれについての紹介( JavaFXプロパティとバインディングの使用 )ですが、その必要性については何もありません。

では、ソースコードを掘りましょう! :)
簡単に、最初に FXMLLoaderコード を調べ始めます。バインディング式のプレフィックスは次のようになります。

_public static final String BINDING_EXPRESSION_PREFIX = "${";
_

さらに279行目で、FXMLLoaderはif (isBindingExpression(value))を決定し、バインディングを作成し、BeanAdapterをインスタンス化して、propertyModelを取得します。

_BeanAdapter targetAdapter = new BeanAdapter(this.value);
ObservableValue<Object> propertyModel = targetAdapter.getPropertyModel(attribute.name);
_

BeanAdapter#getPropertyModel()を調べると、

_public <T> ObservableValue<T> getPropertyModel(String key) {
    if (key == null) {
        throw new NullPointerException();
    }
    return (ObservableValue<T>)get(key + BeanAdapter.PROPERTY_SUFFIX);
}
_

_String PROPERTY_SUFFIX = "Property";_を追加した後、BeanAdapter#get()に委任します
get()メソッドでは、リフレクションによって呼び出されたゲッター(counterProperty、getCounter、isCounterのいずれか)だけで、結果が返されます。ゲッターが存在しない場合、nullが返されます。つまり、「counterProperty()」がJavaBeanに存在しない場合、この場合はnullが返されます。この場合、FXMLLoaderのステートメントif (propertyModel instanceof Property<?>)が原因で、バインディングは実行されません。その結果、「counterProperty()」メソッドにはバインディングがありません。

ゲッターがBeanで定義されていない場合はどうなりますか?再びBeanAdapter#get()コードから、「getCounter()」が見つからない場合、nullが返され、呼び出し元はそれをno-opimoとして無視すると言うことができます。

37
Uluk Biy