簡體   English   中英

JavaFX 從 controller 到嵌套屬性的 FXML 綁定

[英]JavaFX FXML binding from controller to nested property

來自 WPF 和 XAML,回到 JavaFX 我很難綁定。 總的來說,如果我在 Java 中對 GUI 元素進行編碼,我就會得到 model class 的要點和綁定屬性確實有效。但是樣板代碼太多了,真的很累。

在過去,我避免使用 FXML,並且總是使用普通的 Java 構建我的 GUI。現在我想看看我錯過了什么。 已經熟悉 XAML 可以更輕松地進入 FXML,但我遇到了綁定問題。 有一個具有類似背景的人的帖子: How to do binding in FXML, in JavaFX 2?

對於“問題”本身:同樣,來自 WPF,從代碼到 GUI 的簡單字符串綁定似乎需要大量開銷和樣板,我想知道是否有更簡單、更現代或更方便的綁定方式特性。

除了舊帖子和最近的答案(截至 2020 年)之外,我沒有發現太多關於綁定和 FXML 的信息,這些答案指出 FXML 的 state 仍然不是很好。

在這一點上,我不再確定到底該做什么。 為如此多的屬性編寫大量樣板代碼以獲得適當的綁定......實際上直接更新 GUI 組件以減少代碼可能更可行(例如通過從模型傳遞數據來設置 label 本身的文本屬性)?

用例子更新(抱歉一開始沒有包括這個); 請注意,我正在使用 Lombok 生成帶有注釋的 getter 和 setter,以減少樣板代碼的數量:

簡單 model class:

public class Model {
    @Getter @Setter
    private StringProperty someString  = new SimpleStringProperty("test");
}

簡體 controller:

public class Controller {
    @FXML
    public Label myLabel;
    @FXML
    public Label myOtherLabel;
    
    @Getter @Setter
    private StringProperty someString = new SimpleStringProperty("test");
    @Getter @Setter
    private Model model = new Model();

    public void init() {
        // if I wouldn't use binding within FXML I would do the following:
        myLabel.textProperty().bind(someString);
        myOtherLabel.textProperty().bind(model.getSomeString());
    }

    public void onButtonClick(ActionEvent e) {
        someString.set("clicked");
        model.getSomeString().set("clicked");
    }
}

簡化的 FXML 視圖:

<VBox>
    <Text>Some text</Text>
    <Label fx:id="myLabel" text="${controller.someString}"/>
    <Label fx:id="myOtherLabel" text="${controller.model.someString}"/>
</VBox>

更正我最初的陳述:我實際上遺漏了一個細節,所以我想我明白了為什么它不起作用。 看起來我總是需要四個項目才能使一個綁定起作用:

// the actual property holding the data
private StringProperty someString = new SimpleStringProperty("Test");

// a method to return the above property
public StringProperty someStringProperty() {
    return someString;
}

// a getter for the actual text of the property
public String getSomeString() {
    return someString.get();
}

// a setter for the property string (optional)
public void setSomeString(String text) {
    someString.set(text);
}

我所做的只是生成一些需要在 GUI 上顯示的數據。 為 FXML 中的綁定手動編寫每個屬性最多四個成員似乎很多。 另一種方法是為我希望顯示的每個屬性在代碼中使用someProperty.bind(someOtherProperty)進行綁定。 同時,我可以只寫someLabel.setText(someString); 我會完成它。

在代碼中創建綁定(如上所示)也有一個缺點; 如果我想換出(或重新生成)model,綁定將不會更新。 我將不得不重新使用 model 的一個實例並設置它的內容,以免破壞綁定。

再舉一個我來自哪里的例子,這可能有助於理解我提出這個問題的原因。 在 WPF/XAML 中,您將只擁有以下內容:

public class Model
{
    public string SomeString { get; set; }
}

public partial class View ...
{
    public Model DataModel { get; set; }
}

在 XAML 文件中:

<Label Text = "{Binding DataModel.SomeString}" />

... 然后將 Model object 設置為數據上下文,完成。 像這樣比較這兩個框架可能是不公平的,這並不是要抱怨,也不是要說“WPF 比 bla bla 好多了”。 再一次,只是想解釋我來自哪里。

TLDR; 我應該硬着頭皮和 go 來獲取大量樣板文件以獲得良好的 FXML 綁定,還是以“原始”方式直接設置 GUI 內容,從而省去所有額外代碼?

到目前為止,就我個人而言,似乎放棄 FXML 並完全在 Java 中構建 GUI 似乎更可行,因為它似乎會大大減少代碼量,因為即使您在 FXML 中定義 GUI,您無論如何,d 可能必須加倍努力重新定義 controller 中的控件才能訪問它們。

只是對一些評論的總結,我認為這是對問題的完整回答。

JavaFX 屬性模式通過添加額外的“屬性訪問器”方法擴展了標准 Java Bean 模式。 其中標准 Java Bean 模式通過兩種方法定義了類型T的屬性x

public T getX() ; // makes x readable
public void setX(T x) ; // makes x writable

JavaFX 屬性模式添加了一個額外的方法

public Property<T> xProperty() ; 

它可以訪問可觀察的屬性。 合同是這樣的

xProperty().get() == getX()

的影響

setX(x) 

xProperty().set(x)

是相同的。

屬性訪問器提供的附加功能是客戶端綁定或觀察屬性的能力:

someOtherProperty.bind(xProperty())
xProperty().addListener(...)

通常,該模式的實現方式很簡單

private final ObjectProperty<T> x = new SimpleObjectProperty<>(initialValue);
public ObjectProperty<T> xProperty() {
    return x ;
}
public final T getX() {
    return xProperty().get();
}
public final void setX(T value) {
    xProperty().set(value);
}

這里需要注意的是,屬性類型是T ,但是字段類型不同(它是ObjectProperty<T> )。 與 Java Bean 模式一樣,字段名稱也可以與屬性名稱不同。

Lombok 並不適合所有這些,因為它假定字段名稱和類型都定義了屬性名稱和類型。 所以使用

@Getter @Setter
private ObjectProperty<T> x = new SimpleObjectProperty<>();

將生成“錯誤”的方法:

public ObjectProperty<T> getX();
public void setX(ObjectProperty<T> x);

請注意,您基本上不想ObjectProperty本身 要更改屬性值,您應該在現有屬性實例上調用set() 調用 Lombok 定義的setX(...)方法將導致之前注冊的任何偵聽器無法收到后續數據更改的通知。

可以執行以下操作:

@Getter
private final ObjectProperty<T> x = new SimpleObjectProperty<>();

客戶可以很好地使用它; 他們必須做

getX().get()

檢索值,

getX().set(x)

設置它,然后

someProperty.bind(getX())
getX().addListener(...)

等等 這有效,但不符合正常的命名模式。

FXML 表達式綁定使用反射和Binding API 綁定到屬性(和屬性的屬性)等。

表達式${controller.model.value}基本上轉換為Bindings.select(controller, "model", "value") ,所以

<Label fx:id="label" text="${controller.model.value}"/>

相當於做

label.textProperty().bind(Bindings.select(this, "model", "value"));

在 controller。

Bindings.select又使用反射來識別要綁定到的 JavaFX 屬性。 在這樣做時,它假定方法是根據 JavaFX 屬性模式命名的 因此,嘗試將 FXML 表達式綁定與 Lombok 結合使用是行不通的。

這是一個完整的工作應用程序示例,將 label 的文本綁定到 model 中的屬性,由 controller 持有。

你好-view.fxml

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
      fx:controller="org.jamesd.examples.binding.HelloController">
    <HBox spacing="5">
        <Label text="Current value:"/>
        <Label text="${controller.model.value}"/>
    </HBox>
    <HBox spacing="5">
        <Label text="Update value:"/>
        <TextField fx:id="newValueTF" onAction="#updateValue"/>
    </HBox>
    <Button text="Reset model" onAction="#resetModel"/>
</VBox>

HelloController.java

package org.jamesd.examples.binding;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class HelloController {

    private ObjectProperty<Model> model = new SimpleObjectProperty<>(new Model());


    public Model getModel() {
        return model.get();
    }

    public ObjectProperty<Model> modelProperty() {
        return model;
    }

    public void setModel(Model model) {
        this.model.set(model);
    }

    @FXML
    private TextField newValueTF ;
    @FXML
    private void updateValue() {
        getModel().setValue(newValueTF.getText());
    }

    @FXML
    private void resetModel() {
        setModel(new Model());
    }
}

Model.java

package org.jamesd.examples.binding;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Model {
    private final StringProperty value = new SimpleStringProperty("Test");

    public String getValue() {
        return value.get();
    }

    public StringProperty valueProperty() {
        return value;
    }

    public void setValue(String value) {
        this.value.set(value);
    }
}

和申請 class:

package org.jamesd.examples.binding;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load());
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

請注意,這允許綁定在 controller 中的 model 實例替換后持續存在。這也可以在 controller 中的 Java 代碼中實現,使用

label.textProperty().bind(Bindings.select(model, "value"));

或者,最好是在 JavaFX 19 或更高版本中:

label.textProperty().bind(model.flatMap(Model::valueProperty));

最后,雖然 JavaFX 屬性模式很冗長,但大多數 IDE 都可以自動生成代碼。 例如,從

public class Model {
    private final StringProperty value = new SimpleStringProperty("Test");
}

從菜單中選擇“代碼”、“生成...”和“Getter/Setter”。 然后 select value屬性(你可以 select 任意多個屬性,多個屬性一次完成),然后“確定”,將為你生成三個方法。

Eclipse,安裝E(fx)clipse插件。 然后右擊代碼,選擇“Source”,“Generate JavaFX getters and setters”然后按照向導給select相應的屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM