我有一個包含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()
正在使用模型中沒有提取器的替換列表。
謝謝您的回答。 MVCE可能過於簡單。實際上,答案中的代碼與我最初的API綁定非常相似。當然除了提取器。該標籤實際上不需要聽ModelItemDetails的屬性更改,但無論如何,我認爲這是因爲標籤綁定應該使用它們作爲observablevalues /屬性。如果我需要提取器,它不能在模型類中(它們是數據模型)。我會創建一個本地可觀察列表,將它綁定到原始列表並將提取器放在那裏, –
哪種類型可以走向我試圖避免的路徑。與在'createStringBinding'調用中指定依賴關係相同。我在尋找的是一種用EasyBind表達這種方式,並避免明確列出綁定依賴關係,並引入ChangeListeners。我不確定這是可能的,因爲EasyBind有辦法實現這一點。這是一個簡化的案例,但我有更復雜的綁定在未來工作,並希望掌握EasyBind的可能性。 –