[英]Binding to a ObservableValue<ObservableList> instead of an ObservableList with EasyBind
I've got a master/detail panel with ModelItem
items.我有一个包含
ModelItem
项目的主/细节面板。 Each ModelItem
has a ListProperty<ModelItemDetail>
, and each ModelItemDetail
has a few StringProperty
s.每个
ModelItem
都有一个ListProperty<ModelItemDetail>
,每个ModelItemDetail
都有几个StringProperty
。
In the detail panel, I want to show a Label
that will have its text bounded to and derived from the properties of each ModelItemDetail
of the currently selected ModelItem
.在细节面板中,我想显示一个
Label
,它的文本将绑定到当前选定的ModelItem
的每个ModelItemDetail
的属性并从这些属性派生。 The final value may depend on other external properties, such as having a CheckBox
on the Detail panel selected (ie if the checkbox is selected, the values of bProperty
aren't included in the result).最终值可能取决于其他外部属性,例如选择了 Detail 面板上的
CheckBox
(即,如果选中了该复选框,则结果中不包含bProperty
的值)。
This binding accomplishes what I want, using Bindings.createStringBinding()
:这个绑定完成了我想要的,使用
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()));
With for instance:例如:
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);
}
I recently found out about EasyBind, and I'm trying to replace some API Bindings with it.我最近发现了 EasyBind,我正在尝试用它替换一些 API 绑定。 I can't find a way to express this binding with EasyBind.
我找不到用 EasyBind 表达这种绑定的方法。 Apparently, the main problem with my code is that because the selectedItem is a property, I can't use its details as an
ObservableList
, and I must stick to an ObservableValue<ObservableList>>
.显然,我的代码的主要问题是,因为 selectedItem 是一个属性,我不能将其详细信息用作
ObservableList
,我必须坚持使用ObservableValue<ObservableList>>
。 This is inconvenient to chain the transformations through EasyBind.map(ObservableList)
and EasyBind.combine(ObservableList)
, which seem ideal candidates to implement this binding.通过
EasyBind.map(ObservableList)
和EasyBind.combine(ObservableList)
链接转换是不方便的,这似乎是实现此绑定的理想选择。 At some point I've thought of creating a local ListProperty and binding it to the selectedItem's details through a listener on selectedItem, but it just looks too verbose and unclean.在某些时候,我想过创建一个本地 ListProperty 并通过 selectedItem 上的侦听器将其绑定到 selectedItem 的详细信息,但它看起来太冗长和不干净。
I've tried forcing the EasyBind API like this:我试过像这样强制 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")));
But I've got the feeling the last getOrElse
is only getting invoked at initialization time and doesn't update when selectedItem
changes.但是我感觉最后一个
getOrElse
只在初始化时被调用,并且在selectedItem
更改时不会更新。
I've also tried getting the ObservableList
right away, but couldn't expect any other thing that an empty list:我也试过立即获取
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);
I've even tried using EasyBind.subscribe
to listen to selectedItem changes and re-bind (not too sure about this but I don't think rebinding would be needed, everything is there to perform the calculations):我什至尝试使用
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(", "))
));}});
This works partially, actually it's listening to checkbox changes but curiously only to the first change.这部分起作用,实际上它正在侦听复选框更改,但奇怪的是只听第一个更改。 I don't have a clue why (would be great knowing).
我不知道为什么(会很好知道)。 If I add another
EasyBind.Subscribe
to subscribe to checkbox.selectedProperty, it works as intended, but this also too verbose and unclean.如果我添加另一个
EasyBind.Subscribe
来订阅 checkbox.selectedProperty,它会按预期工作,但这也太冗长和不干净。 Same happens if I add an API listener myself to the selectedItemProperty and perform the binding there.如果我自己将 API 侦听器添加到 selectedItemProperty 并在那里执行绑定,也会发生同样的情况。
My motivation to use EasyBind to express this binding is precisely get rid of the need to explicitly express dependencies for the binding, and try to simplify it further.我使用 EasyBind 来表达这种绑定的动机正是摆脱了显式表达绑定的依赖关系的需要,并尝试进一步简化它。 All the approaches i've come up with are notably worse than the API one, as much as I'm not fully satisfied with it.
我提出的所有方法都明显比 API 差,尽管我对它并不完全满意。
I'm still quite new to JavaFX and I'm trying to wrap my head around this.我对 JavaFX 还是很陌生,我正在努力解决这个问题。 I want to understand what's happening, and find out if there is a short concise and elegant way to express this Binding with EasyBind.
我想了解发生了什么,并找出是否有一种简洁而优雅的方式来表达这种使用 EasyBind 的 Binding。 I'm starting to wonder if EasyBind just isn't prepared for this use case (which by the way I don't think is that rare).
我开始怀疑 EasyBind 是否没有为这个用例做好准备(顺便说一下,我认为这并不罕见)。 Probably I am missing something trivial, though.
不过,可能我遗漏了一些微不足道的东西。
Here is a MVCE showing a few of the approaches I've tried, and the API Binding working as intended:这是一个 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); }
}
}
UPDATE : I've made some progress.更新:我取得了一些进展。
The following code works fine, but mysteriouysly still keeps listening only to the first change on the CheckBox:下面的代码工作正常,但 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);
Apparently the main reason I was having trouble is because I didn't see how to get an ObservableList
from the bound current selectedItem
with the EasyBind fluent API.显然,我遇到问题的主要原因是我没有看到如何使用 EasyBind fluent API 从绑定的当前
selectedItem
获取ObservableList
。 Declaring a local ListProperty
and binding it to the selected item's I could take advantage of ListProperty
being an ObservableList
.声明一个本地
ListProperty
并将其绑定到所选项目的我可以利用ListProperty
作为ObservableList
。 I think EasyBind somewhere doesn't follow.我认为 EasyBind 某处没有遵循。 Feels like type information is getting lost somewhere.
感觉类型信息在某处丢失了。 I can't put together all these variables in this last code, and I don't understand why EasyBind.map() will accept
ebDetailList
in this last code, but won't accept obsList
.我无法在最后一个代码中将所有这些变量放在一起,我不明白为什么 EasyBind.map() 在最后一个代码中会接受
ebDetailList
,但不会接受obsList
。
So, the question now is, why is this binding listening to CheckBox events only the first time?那么,现在的问题是,为什么这个绑定只在第一次监听 CheckBox 事件? The extractor in the backing list for the
ListProperty
doesn't do anything. ListProperty
支持列表中的提取器不执行任何操作。 I guess the obsList.bind()
is replacing the backing list with the one in the Model, which has no extractors.我猜
obsList.bind()
正在用没有提取器的模型中的支持列表替换支持列表。
If I understand correctly, you want the label to display text for a selected ModelItem
, that text is composed of all the ModelItemDetail
it contains.如果我理解正确,您希望标签显示所选
ModelItem
文本,该文本由它包含的所有ModelItemDetail
组成。 This text should update whenever a ModelItemDetail
is added or removed, and when the a
or b
properties of any of the ModelItemDetail
s in its list are updated.每当添加或删除
ModelItemDetail
时,以及更新其列表中任何ModelItemDetail
的a
或b
属性时,此文本都应更新。
You don't need an external library for this 1-level depth binding ( ModelItemDetail
-> a
, b
).您不需要此 1 级深度绑定的外部库(
ModelItemDetail
-> a
, b
)。 Changes to the list of ModelItemDetail
s are reported by the ObservableList
.对
ModelItemDetail
列表的更改由ObservableList
报告。 Changes to the properties of items within the list can be reported by an extractor : 提取器可以报告列表中项目属性的更改:
ListProperty<ModelItemDetail> detailsProperty = new SimpleListProperty<>(
FXCollections.observableArrayList(i -> new Observable[]{i.aProperty(), i.bProperty()}));
In fact, you don't need a ListProperty
for this, a simple ObservableList
will suffice.实际上,您不需要为此使用
ListProperty
,一个简单的ObservableList
就足够了。
In the below example,在下面的例子中,
ModelItem
is shown in The ListView
.ModelItem
显示在ListView
。 It is initialized with 3 ModelItemDetail
with some a
and b
properties.ModelItemDetail
初始化,其中包含一些a
和b
属性。ModelItemDetail
s.ModelItemDetail
的文本。CheckBox
on the top determines whether the b
property will be displayed.CheckBox
决定是否显示b
属性。 Note that even when it is not selected, changes to b
will continue to be reported (but not shown).b
更改也会继续报告(但未显示)。ModelItemDetail
to the list.ModelItemDetail
添加到列表中。 This change will reflect immediately through the ObservableList
.ObservableList
反映出来。a
property of a randomly selected ModelItemDetail
from the list.ModelItemDetail
的a
属性的值。 This change will be reflected immediately through the ObservableList
's extractor.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); }
}
}
You can add more ModelItem
s to the ListView
and have the label display the text for the selected one.您可以向
ListView
添加更多ModelItem
并让标签显示所选项目的文本。
After some time and practice and getting more familiar with Bindings, Properties and Observables, I came up with what I was looking for.经过一段时间的练习,对绑定、属性和可观察对象越来越熟悉,我想出了我想要的东西。 A simple, powerful, concise and type-safe EasyBind expression that doesn't need listeners, duplicating or explicitly stating binding dependencies, or extractors.
一个简单、强大、简洁且类型安全的 EasyBind 表达式,不需要侦听器、复制或显式声明绑定依赖项或提取器。 Definitely looks much better than the Bindings API version.
绝对看起来比 Bindings API 版本好得多。
labelWorking.textProperty().bind(
selectedItemBinding
.flatMap(ModelItem::detailsProperty)
.map(l -> derivedBinding(l))
.flatMap(l -> EasyBind.combine(
l, stream -> stream.collect(Collectors.joining(", "))))
);
With与
private ObservableList<ObservableValue<String>> derivedBinding(ObservableList<ModelItemDetail> l) {
return l.stream()
.map(c -> derivedBinding(c.aProperty(), c.bProperty()))
.collect(Collectors.toCollection(FXCollections::observableArrayList));
}
There are apparently some bugs with type inference in Eclipse/javac. Eclipse/javac 中的类型推断显然存在一些错误。 That didn't help getting things clear when I was trying to find the right expression letting the IDE guide me.
当我试图找到让 IDE 指导我的正确表达式时,这无助于让事情变得清晰。
The MVCE with the working binding for the sake of completeness:为了完整起见,具有工作绑定的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); }
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.