使用 EasyBind 绑定到 ObservableValue<ObservableList> 而不是 ObservableList
Binding to a ObservableValue<ObservableList> instead of an ObservableList with EasyBind
我有一个 master/detail 面板,其中包含 ModelItem
个项目。每个ModelItem
有一个ListProperty<ModelItemDetail>
,每个ModelItemDetail
有几个StringProperty
。
在详细信息面板中,我想显示一个 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(", "))
));}});
这部分起作用,实际上它正在监听复选框的变化,但奇怪的是它只监听第一个变化。我不知道为什么(知道会很好)。
如果我添加 another 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 仍然只监听 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);
显然,我遇到麻烦的主要原因是因为我没有看到如何使用 EasyBind fluent API 从绑定电流 selectedItem
中获取 ObservableList
。声明本地 ListProperty
并将其绑定到所选项目的我可以利用 ListProperty
作为 ObservableList
。我认为 EasyBind 某处不遵循。感觉类型信息在某处丢失了。我无法在最后一段代码中将所有这些变量放在一起,而且我不明白为什么 EasyBind.map() 在最后一段代码中会接受 ebDetailList
,但不会接受 obsList
.
那么,现在的问题是,为什么这个绑定只在第一次监听CheckBox事件呢? ListProperty
支持列表中的提取器不执行任何操作。我猜 obsList.bind()
正在用模型中没有提取器的支持列表替换支持列表。
如果我理解正确,您希望标签显示所选 ModelItem
的文本,该文本由它包含的所有 ModelItemDetail
组成。每当添加或删除 ModelItemDetail
, 和 时,任何 ModelItemDetail
的 a
或 b
属性都应更新此文本其列表中的 s 已更新。
此 1 级深度绑定不需要外部库(ModelItemDetail
-> a
、b
)。 ModelItemDetail
列表的更改由 ObservableList
报告。可以通过 extractor:
报告列表中项目属性的更改
ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(
FXCollections.observableArrayList(i -> new Observable[]{i.aProperty(), i.bProperty()}));
事实上,您不需要 ListProperty
,一个简单的 ObservableList
就足够了。
在下面的例子中,
- 单个
ModelItem
显示在 ListView
中。它用 3 ModelItemDetail
初始化,带有一些 a
和 b
属性。
- 底部的文本标签显示合并后的
ModelItemDetail
s 的文本。
- 最上面的
CheckBox
决定是否显示b
属性。请注意,即使未选择它,对 b
的更改也会继续报告(但不会显示)。
- 右侧的 "Add item detail" 按钮将向列表中添加另一个随机编号的
ModelItemDetail
。此更改将立即通过 ObservableList
. 反映出来
- 右侧的 "Change some A" 按钮将设置从列表中随机选择的
ModelItemDetail
的 a
属性 的值。此更改将立即通过 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); }
}
}
您可以向 ListView
添加更多 ModelItem
并让标签显示所选的文本。
经过一段时间的练习并更加熟悉绑定、属性和 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); }
}
}
我有一个 master/detail 面板,其中包含 ModelItem
个项目。每个ModelItem
有一个ListProperty<ModelItemDetail>
,每个ModelItemDetail
有几个StringProperty
。
在详细信息面板中,我想显示一个 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(", "))
));}});
这部分起作用,实际上它正在监听复选框的变化,但奇怪的是它只监听第一个变化。我不知道为什么(知道会很好)。
如果我添加 another 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 仍然只监听 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);
显然,我遇到麻烦的主要原因是因为我没有看到如何使用 EasyBind fluent API 从绑定电流 selectedItem
中获取 ObservableList
。声明本地 ListProperty
并将其绑定到所选项目的我可以利用 ListProperty
作为 ObservableList
。我认为 EasyBind 某处不遵循。感觉类型信息在某处丢失了。我无法在最后一段代码中将所有这些变量放在一起,而且我不明白为什么 EasyBind.map() 在最后一段代码中会接受 ebDetailList
,但不会接受 obsList
.
那么,现在的问题是,为什么这个绑定只在第一次监听CheckBox事件呢? ListProperty
支持列表中的提取器不执行任何操作。我猜 obsList.bind()
正在用模型中没有提取器的支持列表替换支持列表。
如果我理解正确,您希望标签显示所选 ModelItem
的文本,该文本由它包含的所有 ModelItemDetail
组成。每当添加或删除 ModelItemDetail
, 和 时,任何 ModelItemDetail
的 a
或 b
属性都应更新此文本其列表中的 s 已更新。
此 1 级深度绑定不需要外部库(ModelItemDetail
-> a
、b
)。 ModelItemDetail
列表的更改由 ObservableList
报告。可以通过 extractor:
ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(
FXCollections.observableArrayList(i -> new Observable[]{i.aProperty(), i.bProperty()}));
事实上,您不需要 ListProperty
,一个简单的 ObservableList
就足够了。
在下面的例子中,
- 单个
ModelItem
显示在ListView
中。它用 3ModelItemDetail
初始化,带有一些a
和b
属性。 - 底部的文本标签显示合并后的
ModelItemDetail
s 的文本。 - 最上面的
CheckBox
决定是否显示b
属性。请注意,即使未选择它,对b
的更改也会继续报告(但不会显示)。 - 右侧的 "Add item detail" 按钮将向列表中添加另一个随机编号的
ModelItemDetail
。此更改将立即通过ObservableList
. 反映出来
- 右侧的 "Change some A" 按钮将设置从列表中随机选择的
ModelItemDetail
的a
属性 的值。此更改将立即通过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); }
}
}
您可以向 ListView
添加更多 ModelItem
并让标签显示所选的文本。
经过一段时间的练习并更加熟悉绑定、属性和 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); }
}
}