2017-08-03 65 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的可能性。 –

相關問題