2017-08-03 70 views
2

我有一个包含ModelItem项目的主/细节面板。每个ModelItem具有ListProperty<ModelItemDetail>,并且每个ModelItemDetail具有几个StringProperty使用EasyBind绑定到ObservableValue <ObservableList>而不是ObservableList

在详细面板,我想显示Label,将有它的文本限定到和从当前选择的ModelItem的每个ModelItemDetail的属性的。最终值可能取决于其他外部属性,例如在选定的详细信息面板上有CheckBox(即如果选中该复选框,结果中不包括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); } 
    } 
} 

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

下面的代码工作正常,但mysteriouysly仍保持只听上的复选框的第一个变化:

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 API从绑定的当前selectedItem中删除ObservableList。声明本地ListProperty并将其绑定到所选项目我可以利用ListProperty作为ObservableList。我认为EasyBind并不遵循。感觉像类型信息在某处丢失。我无法将所有这些变量放在最后一个代码中,我不明白为什么EasyBind.map()将在最后一个代码中接受ebDetailList,但不会接受obsList

所以,现在的问题是,为什么这个绑定仅在第一次收听CheckBox事件? ListProperty的备份列表中的提取器不会执行任何操作。我猜obsList.bind()正在使用模型中没有提取器的替换列表。

回答

0

经过一段时间和练习,并越来越熟悉绑定,属性和Observable,我想出了我正在寻找的东西。简单,功能强大,简洁且类型安全的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); } 
    } 
} 
3

如果我理解正确,您希望标签显示所选ModelItem的文本,该文本由它包含的所有ModelItemDetail组成。每当ModelItemDetail被添加或删除时,该文本应该被更新,当其列表中ModelItemDetail的任何一个的ab属性被更新时。

对于此1级深度绑定,您不需要外部库(ModelItemDetaila,b)。对ModelItemDetail列表的更改由ObservableList报告。在列表中的项目特性的变化可以通过extractor报告:

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

其实,你并不需要为这个ListProperty,一个简单的ObservableList就足够了。

在下面的例子中,

  • 单个ModelItem显示在ListView。它被初始化为3 ModelItemDetail,其中一些属性为ab
  • 底部的文字标签显示合并的ModelItemDetail的文字。
  • 顶部的CheckBox决定是否显示b属性。请注意,即使未选中,对b的更改也会继续进行报告(但未显示)。
  • 右侧的“添加项目详细信息”按钮会将另一个随机编号的ModelItemDetail添加到列表中。此更改将立即通过ObservableList反映出来。
  • 右侧的“更改某个A”按钮将从列表中设置随机选择的ModelItemDetaila属性的值。此更改将立即通过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); } 
    } 
} 

您可以添加更多ModelItem S到ListView,并在标签显示所选一个文本。

+1

谢谢您的回答。 MVCE可能过于简单。实际上,答案中的代码与我最初的API绑定非常相似。当然除了提取器。该标签实际上不需要听ModelItemDetails的属性更改,但无论如何,我认为这是因为标签绑定应该使用它们作为observablevalues /属性。如果我需要提取器,它不能在模型类中(它们是数据模型)。我会创建一个本地可观察列表,将它绑定到原始列表并将提取器放在那里, –

+1

哪种类型可以走向我试图避免的路径。与在'createStringBinding'调用中指定依赖关系相同。我在寻找的是一种用EasyBind表达这种方式,并避免明确列出绑定依赖关系,并引入ChangeListeners。我不确定这是可能的,因为EasyBind有办法实现这一点。这是一个简化的案例,但我有更复杂的绑定在未来工作,并希望掌握EasyBind的可能性。 –

相关问题