2

我有一个包含ModelItem项目的主/详细信息面板。每个ModelItem都有一个ListProperty<ModelItemDetail>,每个ModelItemDetail都有几个StringPropertys。

在详细信息面板中,我想展示一个Label将其文本绑定到并派生自ModelItemDetail当前选定的每个属性的文本ModelItem。最终值可能取决于其他外部属性,例如CheckBox在“详细信息”面板上选择了 a(即,如果选择了复选框,则 的值bProperty不包含在结果中)。

这个绑定完成了我想要的,使用Bindings.createStringBinding()

ObservableValue<ModelItem> selectedItemBinding = EasyBind.monadic(mdModel.selectedItemProperty()).orElse(new ModelItem());

// API Label Binding
apiLabel.textProperty().bind(Bindings.createStringBinding( 
    () -> selectedItemBinding.getValue().getDetails().stream()
        .map(i -> derivedBinding(i.aProperty(), i.bProperty()))
        .map(v->v.getValue())
        .collect(Collectors.joining(", "))
    , mdModel.selectedItemProperty(), checkBox.selectedProperty()));

例如:

private ObservableValue<String> derivedBinding(ObservableValue<String> aProp, ObservableValue<String> bProp) {
    return EasyBind.combine(aProp, bProp, checkBox.selectedProperty(), 
            (a, b, s) -> Boolean.TRUE.equals(s) ? new String(a + " <" + b + ">") : a);
}

我最近发现了 EasyBind,我正在尝试用它替换一些 API 绑定。我找不到用 EasyBind 表达这种绑定的方法。显然,我的代码的主要问题是因为 selectedItem 是一个属性,所以我不能将其详细信息用作ObservableList,我必须坚持使用ObservableValue<ObservableList>>. 这不方便通过EasyBind.map(ObservableList)和链接转换EasyBind.combine(ObservableList),这似乎是实现此绑定的理想候选者。在某些时候,我考虑过创建一个本地 ListProperty 并通过 selectedItem 上的侦听器将其绑定到 selectedItem 的详细信息,但它看起来太冗长和不干净。

我试过像这样强制 EasyBind API:

ObservableValue<ObservableList<ModelItemDetail>> ebDetailList = EasyBind.select(selectedItemBinding).selectObject(ModelItem::detailsProperty);
MonadicObservableValue<ObservableList<ObservableValue<String>>> ebDerivedList = EasyBind.monadic(ebDetailList).map(x->EasyBind.map(x, i -> derivedBinding(i.aProperty(), i.bProperty())));
MonadicObservableValue<ObservableValue<String>> ebDerivedValueBinding = ebDerivedList.map(x->EasyBind.combine(x, stream -> stream.collect(Collectors.joining(", "))));
easyBindLabel.textProperty().bind(ebDerivedValueBinding.getOrElse(new ReadOnlyStringWrapper("Nothing to see here, move on")));

但是我感觉最后一个getOrElse只是在初始化时被调用,并且在更改时不会更新selectedItem

我也尝试过ObservableList立即获得,但不能指望任何其他空列表:

ObservableList<ModelItemDetail> ebDetailList = EasyBind.select(selectedItemBinding).selectObject(ModelItem::detailsProperty).get();
ObservableList<ObservableValue<String>> ebDerivedList = EasyBind.map(ebDetailList, i -> derivedBinding(i.aProperty(), i.bProperty()));
ObservableValue<String> ebDerivedValueBinding = EasyBind.combine(ebDerivedList, stream -> stream.collect(Collectors.joining(", "))).orElse("Nothing to see here, move on");
easyBindLabel2.textProperty().bind(ebDerivedValueBinding);

我什至尝试使用EasyBind.subscribe来监听 selectedItem 的更改并重新绑定(对此不太确定,但我认为不需要重新绑定,一切都可以执行计算):

EasyBind.subscribe(selectedItemBinding, newValue -> {
    if (newValue != null) {
            ObservableList<ObservableValue<String>> l = 
                EasyBind.map(newValue.getDetails(), 
                             i -> derivedBinding(i.aProperty(), i.bProperty()));
            easyBindLabelSub.textProperty().bind(
                    EasyBind.combine(l, 
                            strm -> strm.collect(Collectors.joining(", "))
                    ));}});

这部分有效,实际上它正在监听复选框更改,但奇怪的是只监听第一个更改。我不知道为什么(知道会很好)。如果我添加另一个 EasyBind.Subscribe订阅 checkbox.selectedProperty,它会按预期工作,但这也太冗长和不干净。如果我自己将 API 侦听器添加到 selectedItemProperty 并在那里执行绑定,也会发生同样的情况。

我使用 EasyBind 来表达这种绑定的动机正是摆脱了显式表达绑定依赖关系的需要,并尝试进一步简化它。我提出的所有方法都明显比 API 差,尽管我对它并不完全满意。

我对 JavaFX 还是很陌生,我正在努力解决这个问题。我想了解正在发生的事情,并找出是否有一种简洁而优雅的方式来表达这个使用 EasyBind 的绑定。我开始怀疑 EasyBind 是否没有为这个用例做好准备(顺便说一句,我认为这并不罕见)。不过,可能我错过了一些微不足道的东西。

这是一个 MVCE,显示了我尝试过的一些方法,并且 API 绑定按预期工作:

package mcve.javafx;

import java.util.*;
import java.util.stream.*;

import javafx.application.*;
import javafx.beans.binding.*;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;

import org.fxmisc.easybind.*;
import org.fxmisc.easybind.monadic.*;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    private CheckBox checkShowMore;

    @Override
    public void start(Stage primaryStage) {
        try {
            // Initialize model
            MasterDetailModel mdModel = new MasterDetailModel();
            ObservableList<ModelItem> itemsList = FXCollections.observableArrayList();
            for (int i=0;i<5;i++) { itemsList.add(newModelItem(i)); }

            // Master
            ListView<ModelItem> listView = new ListView<ModelItem>();
            listView.setItems(itemsList);
            listView.setPrefHeight(150);
            mdModel.selectedItemProperty().bind(listView.getSelectionModel().selectedItemProperty());

            //Detail
            checkShowMore = new CheckBox();
            checkShowMore.setText("Show more details");
            VBox detailVBox = new VBox();           
            Label apiLabel = new Label();
            Label easyBindLabel = new Label();
            Label easyBindLabel2 = new Label();
            Label easyBindLabelSub = new Label();
            Label easyBindLabelLis = new Label();
            detailVBox.getChildren().addAll(
                    checkShowMore, 
                    new TitledPane("API Binding", apiLabel), 
                    new TitledPane("EasyBind Binding", easyBindLabel),
                    new TitledPane("EasyBind Binding 2", easyBindLabel2),
                    new TitledPane("EasyBind Subscribe", easyBindLabelSub),
                    new TitledPane("Listener+EasyBind Approach", easyBindLabelLis)
            );

            // Scene
            Scene scene = new Scene(new VBox(listView, detailVBox),400,400);
            primaryStage.setScene(scene);
            primaryStage.setTitle("JavaFX/EasyBind MVCE");

            // --------------------------
            // -------- BINDINGS --------
            // --------------------------
            ObservableValue<ModelItem> selectedItemBinding = EasyBind.monadic(mdModel.selectedItemProperty()).orElse(new ModelItem());

            // API Label Binding
            apiLabel.textProperty().bind(Bindings.createStringBinding( 
                () -> selectedItemBinding.getValue().getDetails().stream()
                    .map(i -> derivedBinding(i.aProperty(), i.bProperty()))
                    .map(v->v.getValue())
                    .collect(Collectors.joining(", "))
                , mdModel.selectedItemProperty(), checkShowMore.selectedProperty()));

            // EasyBind Binding Approach 1
            {
            ObservableValue<ObservableList<ModelItemDetail>> ebDetailList = EasyBind.select(selectedItemBinding).selectObject(ModelItem::detailsProperty);
            MonadicObservableValue<ObservableList<ObservableValue<String>>> ebDerivedList = EasyBind.monadic(ebDetailList).map(x->EasyBind.map(x, i -> derivedBinding(i.aProperty(), i.bProperty())));
            MonadicObservableValue<ObservableValue<String>> ebDerivedValueBinding = ebDerivedList.map(x->EasyBind.combine(x, stream -> stream.collect(Collectors.joining(", "))));
            easyBindLabel.textProperty().bind(ebDerivedValueBinding.getOrElse(new ReadOnlyStringWrapper("Nothing to see here, move on")));
            }

            // EasyBind Binding Approach 2
            {
            ObservableList<ModelItemDetail> ebDetailList = EasyBind.select(selectedItemBinding).selectObject(ModelItem::detailsProperty).get();
            ObservableList<ObservableValue<String>> ebDerivedList = EasyBind.map(ebDetailList, i -> derivedBinding(i.aProperty(), i.bProperty()));
            ObservableValue<String> ebDerivedValueBinding = EasyBind.combine(ebDerivedList, stream -> stream.collect(Collectors.joining(", "))).orElse("Nothing to see here, move on");
            easyBindLabel2.textProperty().bind(ebDerivedValueBinding);
            }

            // Subscribe approach
            EasyBind.subscribe(selectedItemBinding, newValue -> {
                if (newValue != null) {
                        ObservableList<ObservableValue<String>> l = EasyBind.map(newValue.getDetails(), i -> derivedBinding(i.aProperty(), i.bProperty()));
                        easyBindLabelSub.textProperty().bind(
                                EasyBind.combine(l, 
                                        strm -> strm.collect(Collectors.joining(", "))
                                ));
                }
            });
            //With this it works as intended, but something feels very wrong about this
             /*
            EasyBind.subscribe(checkShowMore.selectedProperty(), newValue -> {
                if (selectedItemBinding != null) {
                        ObservableList<ObservableValue<String>> l = EasyBind.map(selectedItemBinding.getValue().getDetails(), i -> derivedBinding(i.aProperty(), i.bProperty()));
                        easyBindLabelSub.textProperty().bind(
                                EasyBind.combine(l, 
                                        strm -> strm.collect(Collectors.joining(", "))
                                ));
                }
                });
            */

            // Listener approach
            selectedItemBinding.addListener( (ob, o, n) -> {
                ObservableList<ModelItemDetail> ebDetailList = n.getDetails();
                ObservableList<ObservableValue<String>> ebDerivedList = EasyBind.map(ebDetailList, i -> derivedBinding(i.aProperty(), i.bProperty()));
                ObservableValue<String> ebDerivedValueBinding = EasyBind.combine(ebDerivedList, stream -> stream.collect(Collectors.joining(", "))).orElse("Nothing to see here, move on");
                easyBindLabelLis.textProperty().bind(ebDerivedValueBinding);                
            });





            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private ObservableValue<String> derivedBinding(ObservableValue<String> aProp, ObservableValue<String> bProp) {
        return EasyBind.combine(aProp, bProp, checkShowMore.selectedProperty(), 
                (a, b, s) -> Boolean.TRUE.equals(s) ? new String(a + " <" + b + ">") : a);
    }   

    private ModelItem newModelItem(int number) { 
        ModelItem item = new ModelItem();
        item.itemNumber = number+1;
        for (int i=0;i<2;i++) { 
            ModelItemDetail detail = new ModelItemDetail();
            detail.setA("A" + (i+item.itemNumber));
            detail.setB("B" + (i+item.itemNumber));
            item.getDetails().add(detail);
        }
        return item;
    }

    /** GUI Model class */ 
    private static class MasterDetailModel {
        private ObjectProperty<ModelItem> selectedItemProperty = new SimpleObjectProperty<>();
        public ObjectProperty<ModelItem> selectedItemProperty() { return selectedItemProperty; }
        public ModelItem getSelectedItem() { return selectedItemProperty.getValue(); }
        public void setSelectedItem(ModelItem item) { selectedItemProperty.setValue(item); }
    }

    /** Domain Model class */
    private static class ModelItem { 
        int itemNumber;
        private ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
        public ListProperty<ModelItemDetail> detailsProperty() { return detailsProperty; }
        public ObservableList<ModelItemDetail> getDetails() { return detailsProperty.getValue(); }
        public void setDetails(List<ModelItemDetail> details) { detailsProperty.setValue(FXCollections.observableList(details)); }
        public String toString() { return "Item " + itemNumber; }
    }

    /** Domain Model class */
    private static class ModelItemDetail {
        private StringProperty aProperty = new SimpleStringProperty();
        public StringProperty aProperty() { return aProperty; }
        public String getA() { return aProperty.get(); }
        public void setA(String a) { aProperty.set(a); }

        private StringProperty bProperty = new SimpleStringProperty();
        public StringProperty bProperty() { return bProperty; }
        public String getB() { return bProperty.get(); }
        public void setB(String b) { bProperty.set(b); }
    }
}

更新:我取得了一些进展。

以下代码工作正常,但神秘地仍然只听 CheckBox 上的第一个更改:

ListProperty<ModelItemDetail> obsList = new SimpleListProperty<>(FXCollections.observableArrayList(i->new Observable[] { i.aProperty(), i.bProperty(), checkShowMore.selectedProperty()}));
obsList.bind(selectedItemBinding.flatMap(ModelItem::detailsProperty));
ObservableList<ModelItemDetail> ebDetailList = obsList; // WHY ??
ObservableList<ObservableValue<String>> ebDerivedList = EasyBind.map(ebDetailList, i -> derivedBinding(i.aProperty(), i.bProperty()));
ObservableValue<String> ebDerivedValueBinding = EasyBind.combine(ebDerivedList, stream -> stream.collect(Collectors.joining(", "))).orElse("Nothing to see here, move on");
labelPlayground.textProperty().bind(ebDerivedValueBinding);

显然,我遇到麻烦的主要原因是因为我没有看到如何使用 EasyBind fluent APIObservableList从绑定电流中获取一个。selectedItem声明一个本地ListProperty并将其绑定到所选项目的我可以利用ListProperty成为ObservableList. 我认为 EasyBind 某处没有遵循。感觉类型信息在某处丢失了。我无法将所有这些变量放在最后一个代码中,我不明白为什么 EasyBind.map() 会ebDetailList在最后一个代码中接受,但不会接受obsList

那么,现在的问题是,为什么这个绑定只在第一次监听 CheckBox 事件?支持列表中的提取器ListProperty不执行任何操作。我想这obsList.bind()是用模型中的一个替换后备列表,它没有提取器。

4

2 回答 2

3

如果我理解正确,您希望标签显示 selectedModelItem的文本,该文本由ModelItemDetail它包含的所有内容组成。ModelItemDetail每当添加或删除a以及更新其列表中任何 s的ab属性时,此文本都应更新。ModelItemDetail

ModelItemDetail对于这个 1 级深度绑定 ( -> a, b) ,您不需要外部库。对ModelItemDetails 列表的更改由 报告ObservableList提取器可以报告列表中项目属性的更改:

ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(
                FXCollections.observableArrayList(i -> new Observable[]{i.aProperty(), i.bProperty()}));

事实上,你不需要一个ListProperty,一个简单的ObservableList就足够了。

在下面的示例中,

  • 单曲ModelItem显示在 中ListView。它用 3 初始化,ModelItemDetail带有一些ab属性。
  • 底部的文本标签显示组合ModelItemDetails 的文本。
  • CheckBox顶部的 决定是否显示该属性b。请注意,即使未选中,更改b也会继续报告(但未显示)。
  • 右侧的“添加项目详细信息”按钮将ModelItemDetail在列表中添加另一个随机编号。这种变化将立即通过ObservableList.
  • 右侧的“更改一些 A”按钮将设置从列表中a随机选择的属性的值。ModelItemDetail此更改将立即通过ObservableList' 提取器反映出来。

public class Main extends Application {

    public Main() {}

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

    @Override
    public void start(Stage stage) throws Exception {
        // Mock initial data
        ModelItem item = new ModelItem();
        ModelItemDetail mid1 = new ModelItemDetail();
        mid1.setA("a1");
        mid1.setB("b1");
        ModelItemDetail mid2 = new ModelItemDetail();
        mid2.setA("a2");
        mid2.setB("b2");
        ModelItemDetail mid3 = new ModelItemDetail();
        mid3.setA("a3");
        mid3.setB("b3");
        ObservableList<ModelItemDetail> details = item.getDetails();
        details.add(mid1);
        details.add(mid2);
        details.add(mid3);

        // Create binding

        CheckBox showB = new CheckBox("Show b");
        Label label = new Label();

        label.textProperty().bind(Bindings.createStringBinding(() -> {
            return details.stream()
                .map(mid ->
                    Boolean.TRUE.equals(showB.isSelected()) ? new String(mid.getA() + " <" + mid.getB() + ">") : mid.getA()
                ).collect(Collectors.joining(", "));
        }, details, showB.selectedProperty()));

        // Create testing components

        Button add = new Button("Add item detail");
        add.setOnAction(e -> {
            Random r = new Random();
            int i = r.nextInt(100) + 3;
            ModelItemDetail mid = new ModelItemDetail();
            mid.setA("a" + i);
            mid.setB("b" + i);
            details.add(mid);
        });
        Button changeA = new Button("Change some A");
        changeA.setOnAction(e -> {
            Random r = new Random();
            ModelItemDetail detail = details.get(r.nextInt(details.size()));
            detail.setA("a" + r.nextInt(100) + 3);
        });

        // Display everything

        BorderPane pane = new BorderPane();
        ListView<ModelItem> list = new ListView<>();
        list.getItems().add(item);
        pane.setCenter(list);
        pane.setRight(new VBox(add, changeA));
        pane.setTop(showB);
        pane.setBottom(label);
        stage.setScene(new Scene(pane));
        stage.show();
    }

    private static class ModelItem {
        int itemNumber;
        private ObservableList<ModelItemDetail> detailsProperty = FXCollections.observableArrayList(i -> new Observable[]{i.aProperty(), i.bProperty()});
        public ObservableList<ModelItemDetail> getDetails() { return detailsProperty; }
        @Override public String toString() { return "Item " + itemNumber; }
    }

    /** Domain Model class */
    private static class ModelItemDetail {
        private StringProperty aProperty = new SimpleStringProperty();
        public StringProperty aProperty() { return aProperty; }
        public String getA() { return aProperty.get(); }
        public void setA(String a) { aProperty.set(a); }

        private StringProperty bProperty = new SimpleStringProperty();
        public StringProperty bProperty() { return bProperty; }
        public String getB() { return bProperty.get(); }
        public void setB(String b) { bProperty.set(b); }
    }
}

您可以添加更多ModelItemsListView并让标签显示所选文本的文本。

于 2017-08-03T14:16:34.377 回答
1

经过一段时间的练习和对 Bindings、Properties 和 Observables 的熟悉,我想出了我想要的东西。一个简单、强大、简洁且类型安全的 EasyBind 表达式,不需要侦听器、复制或显式声明绑定依赖项或提取器。绝对看起来比 Bindings API 版本好得多。

 labelWorking.textProperty().bind(
    selectedItemBinding
    .flatMap(ModelItem::detailsProperty)
    .map(l -> derivedBinding(l))
    .flatMap(l -> EasyBind.combine(
             l, stream -> stream.collect(Collectors.joining(", "))))
    );

private ObservableList<ObservableValue<String>> derivedBinding(ObservableList<ModelItemDetail> l) { 
        return l.stream()
                .map(c -> derivedBinding(c.aProperty(), c.bProperty()))
                .collect(Collectors.toCollection(FXCollections::observableArrayList));
    }

Eclipse/javac 中的类型推断显然存在一些错误。当我试图找到让 IDE 引导我的正确表达式时,这无助于让事情变得清晰。

为了完整起见,具有工作绑定的 MVCE:

package mcve.javafx;

import java.util.List;
import java.util.stream.Collectors;

import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.monadic.MonadicBinding;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    private CheckBox checkShowMore;

    @Override
    public void start(Stage primaryStage) {
        try {


            // Initialize model
            MasterDetailModel mdModel = new MasterDetailModel();
            ObservableList<ModelItem> itemsList = FXCollections.observableArrayList();
            for (int i=0;i<5;i++) { itemsList.add(newModelItem(i)); }

            MonadicBinding<ModelItem> selectedItemBinding = EasyBind.monadic(mdModel.selectedItemProperty()).orElse(new ModelItem());

            // Master
            ListView<ModelItem> listView = new ListView<ModelItem>();
            listView.setItems(itemsList);
            listView.setPrefHeight(150);
            mdModel.selectedItemProperty().bind(listView.getSelectionModel().selectedItemProperty());

            //Detail
            checkShowMore = new CheckBox();
            checkShowMore.setText("Show more details");
            VBox detailVBox = new VBox();           
            Label apiLabel = new Label();
            Label labelPlayground = new Label();
            detailVBox.getChildren().addAll(
                    checkShowMore, 
                    new TitledPane("API Binding", apiLabel), 
                    new TitledPane("EasyBind", labelPlayground)
            );


            // Scene
            Scene scene = new Scene(new VBox(listView, detailVBox),400,400);
            primaryStage.setScene(scene);
            primaryStage.setTitle("JavaFX/EasyBind MVCE");

            // --------------------------
            // -------- BINDINGS --------
            // --------------------------

            // API Label Binding

            apiLabel.textProperty().bind(Bindings.createStringBinding( 
                () -> selectedItemBinding.getValue().getDetails().stream()
                    .map(i -> derivedBinding(i.aProperty(), i.bProperty()))
                    .map(v->v.getValue())
                    .collect(Collectors.joining(", "))
                , mdModel.selectedItemProperty(), checkShowMore.selectedProperty()));

            // EasyBind non-working attempt
            /*
            ListProperty<ModelItemDetail> obsList = new SimpleListProperty<>(FXCollections.observableArrayList(i->new Observable[] { i.aProperty(), i.bProperty(), checkShowMore.selectedProperty()}));
            obsList.bind(selectedItemBinding.flatMap(ModelItem::detailsProperty));
            ObservableList<ModelItemDetail> ebDetailList = obsList; // WHY ??
            ObservableList<ObservableValue<String>> ebDerivedList = EasyBind.map(ebDetailList, i -> derivedBinding(i.aProperty(), i.bProperty()));
            ObservableValue<String> ebDerivedValueBinding = EasyBind.combine(ebDerivedList, stream -> stream.collect(Collectors.joining(", "))).orElse("Nothing to see here, move on");
            labelPlayground.textProperty().bind(ebDerivedValueBinding);
            */

            // Working EasyBind Binding
            labelPlayground.textProperty().bind(
                    selectedItemBinding
                    .flatMap(ModelItem::detailsProperty)
                    .map(l -> derivedBinding(l))
                    .flatMap(l -> EasyBind.combine(l, stream -> stream.collect(Collectors.joining(", "))))
                    );

            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private ObservableList<ObservableValue<String>> derivedBinding(ObservableList<ModelItemDetail> l) { 
        return l.stream()
                .map(c -> derivedBinding(c.aProperty(), c.bProperty()))
                .collect(Collectors.toCollection(FXCollections::observableArrayList));
    }

    private Binding<String> derivedBinding(ObservableValue<String> someA, ObservableValue<String> someB ) { 
        return EasyBind.combine(someA, someB, checkShowMore.selectedProperty(), 
                        (a, e, s) -> a + (Boolean.TRUE.equals(s) ? " <" + e + ">" : ""));
    }

    private ModelItem newModelItem(int number) { 
        ModelItem item = new ModelItem();
        item.itemNumber = number+1;
        for (int i=0;i<2;i++) { 
            ModelItemDetail detail = new ModelItemDetail("A" + (i+item.itemNumber), "B" + (i+item.itemNumber));
            item.getDetails().add(detail);
        }
        return item;
    }

    /** GUI Model class */ 
    private static class MasterDetailModel {
        private ObjectProperty<ModelItem> selectedItemProperty = new SimpleObjectProperty<>();
        public ObjectProperty<ModelItem> selectedItemProperty() { return selectedItemProperty; }
        public ModelItem getSelectedItem() { return selectedItemProperty.getValue(); }
        public void setSelectedItem(ModelItem item) { selectedItemProperty.setValue(item); }
    }

    /** Domain Model class */
    private static class ModelItem { 
        int itemNumber;
        private ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
        public ListProperty<ModelItemDetail> detailsProperty() { return detailsProperty; }
        public ObservableList<ModelItemDetail> getDetails() { return detailsProperty.getValue(); }
        public void setDetails(List<ModelItemDetail> details) { detailsProperty.setValue(FXCollections.observableList(details)); }
        public String toString() { return "Item " + itemNumber; }
    }

    /** Domain Model class */
    private static class ModelItemDetail {

        public ModelItemDetail(String a, String b) { 
            setA(a);
            setB(b);
        }

        private StringProperty aProperty = new SimpleStringProperty();
        public StringProperty aProperty() { return aProperty; }
        public String getA() { return aProperty.get(); }
        public void setA(String a) { aProperty.set(a); }

        private StringProperty bProperty = new SimpleStringProperty();
        public StringProperty bProperty() { return bProperty; }
        public String getB() { return bProperty.get(); }
        public void setB(String b) { bProperty.set(b); }
    }
}
于 2017-08-09T22:45:37.220 回答