简体   繁体   English

绑定到 ObservableValue<ObservableList> 用 EasyBind 代替 ObservableList

[英]Binding to a ObservableValue<ObservableList> instead of an ObservableList with EasyBind

I've got a master/detail panel with ModelItem items.我有一个包含ModelItem项目的主/细节面板。 Each ModelItem has a ListProperty<ModelItemDetail> , and each ModelItemDetail has a few StringProperty s.每个ModelItem都有一个ListProperty<ModelItemDetail> ,每个ModelItemDetail都有几个StringProperty

In the detail panel, I want to show a Label that will have its text bounded to and derived from the properties of each ModelItemDetail of the currently selected ModelItem .在细节面板中,我想显示一个Label ,它的文本将绑定到当前选定的ModelItem的每个ModelItemDetail的属性并从这些属性派生。 The final value may depend on other external properties, such as having a CheckBox on the Detail panel selected (ie if the checkbox is selected, the values of bProperty aren't included in the result).最终值可能取决于其他外部属性,例如选择了 Detail 面板上的CheckBox (即,如果选中了该复选框,则结果中不包含bProperty的值)。

This binding accomplishes what I want, using Bindings.createStringBinding() :这个绑定完成了我想要的,使用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()));

With for instance:例如:

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);
}

I recently found out about EasyBind, and I'm trying to replace some API Bindings with it.我最近发现了 EasyBind,我正在尝试用它替换一些 API 绑定。 I can't find a way to express this binding with EasyBind.我找不到用 EasyBind 表达这种绑定的方法。 Apparently, the main problem with my code is that because the selectedItem is a property, I can't use its details as an ObservableList , and I must stick to an ObservableValue<ObservableList>> .显然,我的代码的主要问题是,因为 selectedItem 是一个属性,我不能将其详细信息用作ObservableList ,我必须坚持使用ObservableValue<ObservableList>> This is inconvenient to chain the transformations through EasyBind.map(ObservableList) and EasyBind.combine(ObservableList) , which seem ideal candidates to implement this binding.通过EasyBind.map(ObservableList)EasyBind.combine(ObservableList)链接转换是不方便的,这似乎是实现此绑定的理想选择。 At some point I've thought of creating a local ListProperty and binding it to the selectedItem's details through a listener on selectedItem, but it just looks too verbose and unclean.在某些时候,我想过创建一个本地 ListProperty 并通过 selectedItem 上的侦听器将其绑定到 selectedItem 的详细信息,但它看起来太冗长和不干净。

I've tried forcing the EasyBind API like this:我试过像这样强制 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")));

But I've got the feeling the last getOrElse is only getting invoked at initialization time and doesn't update when selectedItem changes.但是我感觉最后一个getOrElse只在初始化时被调用,并且在selectedItem更改时不会更新。

I've also tried getting the ObservableList right away, but couldn't expect any other thing that an empty list:我也试过立即获取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);

I've even tried using EasyBind.subscribe to listen to selectedItem changes and re-bind (not too sure about this but I don't think rebinding would be needed, everything is there to perform the calculations):我什至尝试使用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(", "))
                    ));}});

This works partially, actually it's listening to checkbox changes but curiously only to the first change.这部分起作用,实际上它正在侦听复选框更改,但奇怪的是只听第一个更改。 I don't have a clue why (would be great knowing).我不知道为什么(会很好知道)。 If I add another EasyBind.Subscribe to subscribe to checkbox.selectedProperty, it works as intended, but this also too verbose and unclean.如果我添加另一个EasyBind.Subscribe来订阅 checkbox.selectedProperty,它会按预期工作,但这也太冗长和不干净。 Same happens if I add an API listener myself to the selectedItemProperty and perform the binding there.如果我自己将 API 侦听器添加到 selectedItemProperty 并在那里执行绑定,也会发生同样的情况。

My motivation to use EasyBind to express this binding is precisely get rid of the need to explicitly express dependencies for the binding, and try to simplify it further.我使用 EasyBind 来表达这种绑定的动机正是摆脱了显式表达绑定的依赖关系的需要,并尝试进一步简化它。 All the approaches i've come up with are notably worse than the API one, as much as I'm not fully satisfied with it.我提出的所有方法都明显比 API 差,尽管我对它并不完全满意。

I'm still quite new to JavaFX and I'm trying to wrap my head around this.我对 JavaFX 还是很陌生,我正在努力解决这个问题。 I want to understand what's happening, and find out if there is a short concise and elegant way to express this Binding with EasyBind.我想了解发生了什么,并找出是否有一种简洁而优雅的方式来表达这种使用 EasyBind 的 Binding。 I'm starting to wonder if EasyBind just isn't prepared for this use case (which by the way I don't think is that rare).我开始怀疑 EasyBind 是否没有为这个用例做好准备(顺便说一下,我认为这并不罕见)。 Probably I am missing something trivial, though.不过,可能我遗漏了一些微不足道的东西。

Here is a MVCE showing a few of the approaches I've tried, and the API Binding working as intended:这是一个 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); }
    }
}

UPDATE : I've made some progress.更新:我取得了一些进展。

The following code works fine, but mysteriouysly still keeps listening only to the first change on the CheckBox:下面的代码工作正常,但 mysteriouysly 仍然只监听 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);

Apparently the main reason I was having trouble is because I didn't see how to get an ObservableList from the bound current selectedItem with the EasyBind fluent API.显然,我遇到问题的主要原因是我没有看到如何使用 EasyBind fluent API 从绑定的当前selectedItem获取ObservableList Declaring a local ListProperty and binding it to the selected item's I could take advantage of ListProperty being an ObservableList .声明一个本地ListProperty并将其绑定到所选项目的我可以利用ListProperty作为ObservableList I think EasyBind somewhere doesn't follow.我认为 EasyBind 某处没有遵循。 Feels like type information is getting lost somewhere.感觉类型信息在某处丢失了。 I can't put together all these variables in this last code, and I don't understand why EasyBind.map() will accept ebDetailList in this last code, but won't accept obsList .我无法在最后一个代码中将所有这些变量放在一起,我不明白为什么 EasyBind.map() 在最后一个代码中会接受ebDetailList ,但不会接受obsList

So, the question now is, why is this binding listening to CheckBox events only the first time?那么,现在的问题是,为什么这个绑定只在第一次监听 CheckBox 事件? The extractor in the backing list for the ListProperty doesn't do anything. ListProperty支持列表中的提取器不执行任何操作。 I guess the obsList.bind() is replacing the backing list with the one in the Model, which has no extractors.我猜obsList.bind()正在用没有提取器的模型中的支持列表替换支持列表。

If I understand correctly, you want the label to display text for a selected ModelItem , that text is composed of all the ModelItemDetail it contains.如果我理解正确,您希望标签显示所选ModelItem文本,该文本由它包含的所有ModelItemDetail组成。 This text should update whenever a ModelItemDetail is added or removed, and when the a or b properties of any of the ModelItemDetail s in its list are updated.每当添加或删除ModelItemDetail时,以及更新其列表中任何ModelItemDetailab属性时,此文本都应更新。

You don't need an external library for this 1-level depth binding ( ModelItemDetail -> a , b ).您不需要此 1 级深度绑定的外部库( ModelItemDetail -> a , b )。 Changes to the list of ModelItemDetail s are reported by the ObservableList .ModelItemDetail列表的更改由ObservableList报告。 Changes to the properties of items within the list can be reported by an extractor : 提取器可以报​​告列表中项目属性的更改:

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

In fact, you don't need a ListProperty for this, a simple ObservableList will suffice.实际上,您不需要为此使用ListProperty ,一个简单的ObservableList就足够了。

In the below example,在下面的例子中,

  • A single ModelItem is shown in The ListView .单个ModelItem显示在ListView It is initialized with 3 ModelItemDetail with some a and b properties.它用 3 个ModelItemDetail初始化,其中包含一些ab属性。
  • The text label on the bottom shows the text of the combined ModelItemDetail s.底部的文本标签显示了组合ModelItemDetail的文本。
  • The CheckBox on the top determines whether the b property will be displayed.顶部的CheckBox决定是否显示b属性。 Note that even when it is not selected, changes to b will continue to be reported (but not shown).请注意,即使未选择它,对b更改也会继续报告(但未显示)。
  • The "Add item detail" button to the right will add another randomly numbered ModelItemDetail to the list.右侧的“添加项目详细信息”按钮会将另一个随机编号的ModelItemDetail添加到列表中。 This change will reflect immediately through the ObservableList .此更改将立即通过ObservableList反映出来。
  • The "Change some A" button to the right will set the value of the a property of a randomly selected ModelItemDetail from the list.右侧的“更改一些 A”按钮将设置从列表中随机选择的ModelItemDetaila属性的值。 This change will be reflected immediately through the ObservableList 's extractor.此更改将立即通过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); }
    }
}

You can add more ModelItem s to the ListView and have the label display the text for the selected one.您可以向ListView添加更多ModelItem并让标签显示所选项目的文本。

After some time and practice and getting more familiar with Bindings, Properties and Observables, I came up with what I was looking for.经过一段时间的练习,对绑定、属性和可观察对象越来越熟悉,我想出了我想要的东西。 A simple, powerful, concise and type-safe EasyBind expression that doesn't need listeners, duplicating or explicitly stating binding dependencies, or extractors.一个简单、强大、简洁且类型安全的 EasyBind 表达式,不需要侦听器、复制或显式声明绑定依赖项或提取器。 Definitely looks much better than the Bindings API version.绝对看起来比 Bindings API 版本好得多。

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

With

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

There are apparently some bugs with type inference in Eclipse/javac. Eclipse/javac 中的类型推断显然存在一些错误。 That didn't help getting things clear when I was trying to find the right expression letting the IDE guide me.当我试图找到让 IDE 指导我的正确表达式时,这无助于让事情变得清晰。

The MVCE with the working binding for the sake of completeness:为了完整起见,具有工作绑定的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); }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM