我有一个包含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的可能性。 –