我有几个地方想首先检查selectedItemProperty()a 的是否为ChoiceBox空selectionModel,如果不为空,则检查所选项目的某些属性。
我理想中想要的是这样的:
button.disableProperty().bind(
choiceBox.getSelectionModel().selectedItemProperty().isNull().or(
choiceBox.getSelectionModel().selectedItemProperty().get().myProperty()
)
);
Run Code Online (Sandbox Code Playgroud)
但是,这会失败,因为the return value of "javafx.beans.property.ReadOnlyObjectProperty.get()" is null(即,它正在尝试获取所选项目进行评估myProperty())。
相反,我可以做类似的事情
button.disableProperty().bind(
Bindings.createBooleanBinding(
() -> {
if (choiceBox.getSelectionModel().selectedItemProperty().isNull())
return true;
return choiceBox.getSelectionModel().selectedItemProperty().get().myProperty().get();
},
choiceBox.getSelectionModel().selectedItemProperty()
}
)
);
Run Code Online (Sandbox Code Playgroud)
但是,仅当所选项目发生更改时才会更新;它完全不知道底层的变化myProperty()。我希望能够传播底层属性的更改。我真正想要的是or短路,即它看到 为selectedItemProperty空并且不继续,但如果它不为空,则检查基础属性。
在 JavaFX 19 及更高版本中,您可以执行以下操作
button.disableProperty().bind(
choiceBox.getSelectionModel().selectedItemProperty()
.flatMap(selection -> selection.myProperty())
.orElse(Boolean.TRUE)
);
Run Code Online (Sandbox Code Playgroud)
flatMap()有关、orElse()和相关方法的更多信息,请参阅此 Q/A。
在 JavaFX 19 之前,您需要类似以下内容(未经测试):
choiceBox.getSelectionModel().selectedItemProperty().addListener(
(obs, oldSelection, newSelection) -> {
if (newSelection == null) {
button.disableProperty().unbind();
button.setDisable(true);
} else {
button.disableProperty().bind(newSelection.myProperty());
}
}
);
if (choiceBox.getSelectionModel().getSelectedItem() == null) {
button.setDisable(true);
} else {
button.disableProperty().bind(choiceBox.getSelectionModel().getSelectedItem().myProperty());
}
Run Code Online (Sandbox Code Playgroud)
James_D 的答案非常适合 JavaFX 19+。对于旧版本的 JavaFX,我想提供一种替代方案:
void bindDisableProperty(Node node, ChoiceBox<Foo> choiceBox) {
BooleanBinding selectionNotNull = Bindings
.select(choiceBox, "selectionModel", "selectedItem")
.isNotNull();
BooleanBinding flag = Bindings
.selectBoolean(choiceBox, "selectionModel", "selectedItem", "flag");
BooleanBinding disable = Bindings
.when(selectionNotNull)
.then(flag)
.otherwise(true);
node.disableProperty().bind(disable);
}
Run Code Online (Sandbox Code Playgroud)
注意:有关 的定义Foo及其flag属性,请参阅下面的完整示例。
如果您知道选择模型永远不会改变,那么您可以简单地执行以下操作:
ReadOnlyObjectProperty<Foo> selectedItem = choiceBox
.getSelectionModel()
.selectedItemProperty();
BooleanBinding selectionNotNull = selectedItem.isNotNull();
BooleanBinding flag = Bindings.selectBoolean(selectedItem, "flag");
// ...
Run Code Online (Sandbox Code Playgroud)
通常选择模型不会改变。当然,在这种情况下,使用 James_D 的答案中所示的侦听器方法可能是首选。特别是因为当使用“选择绑定”时,您依赖于反射,因此会遇到与为什么我应该避免在 JavaFX 中使用 PropertyValueFactory? 中描述的相同缺点。。
这是一个完整的示例:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
ChoiceBox<Foo> choiceBox = new ChoiceBox<>();
choiceBox.getItems().addAll(new Foo(true), new Foo(true), new Foo(false), new Foo(false), new Foo(true));
choiceBox.setConverter(new StringConverter<Foo>() {
@Override
public String toString(Foo object) {
return object == null ? "<no selection>" : "Foo (" + object.isFlag() + ")";
}
@Override
public Foo fromString(String string) {
throw new UnsupportedOperationException();
}
});
Button clearSelectionBtn = new Button("Clear Selection");
clearSelectionBtn.setOnAction(e -> {
e.consume();
choiceBox.setValue(null);
});
TextField toDisable = new TextField("I am a text field!");
bindDisableProperty(toDisable, choiceBox);
VBox root = new VBox(10, clearSelectionBtn, choiceBox, toDisable);
root.setPadding(new Insets(10));
root.setAlignment(Pos.TOP_CENTER);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
private void bindDisableProperty(Node node, ChoiceBox<Foo> choiceBox) {
BooleanBinding selectionNotNull =
Bindings.select(choiceBox, "selectionModel", "selectedItem").isNotNull();
BooleanBinding flag = Bindings.selectBoolean(choiceBox, "selectionModel", "selectedItem", "flag");
BooleanBinding disable = Bindings.when(selectionNotNull).then(flag).otherwise(true);
node.disableProperty().bind(disable);
}
public static class Foo {
private final BooleanProperty flag = new SimpleBooleanProperty(this, "flag");
public final void setFlag(boolean flag) {
this.flag.set(flag);
}
public final boolean isFlag() {
return flag.get();
}
public final BooleanProperty flagProperty() {
return flag;
}
public Foo() {}
public Foo(boolean flag) {
setFlag(flag);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我一直对如何创建 JavaFX 提供的“选择绑定”的类型安全版本有一个想法。决定将这个想法实际编码。请注意,我尚未测试此代码,因此可能存在错误。另请注意,以下类没有原始特化。
import java.util.Objects;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.util.Callback;
/**
* A type-safe alternative to {@link Bindings#select(Object, String...) select} bindings. A {@code Chain} is made up
* of one or more "links" of {@linkplain ObservableValue observable values}. Except for the "root" link of the chain,
* the observable value of a link is computed by a {@link Callback} based on the observable value of the previous
* link. If any link's {@code Callback} in the chain returns {@code null}, then the chain will be "broken" at that link
* and the end value of the chain will be {@code null}.
*
* <p>A {@code Chain} is created via the {@link Builder} class. Use the {@link #from(ObservableValue)} and {@link
* #fromValue(Object)} methods to create a new builder.
*
* <p>Unless otherwise stated, passing {@code null} to any method of this API will result in a {@code
* NullPointerException} being thrown.
*
* @param <T> The value type.
*/
public class Chain<T> extends ObjectBinding<T> {
/**
* Creates a new builder for a chain with the given observable as its root.
*
* @param observable the root observable value
* @return a new {@code Builder}
* @param <T> The observable's value type.
* @param <U> The observable type.
* @see #fromValue(Object)
*/
public static <T, U extends ObservableValue<T>> Builder<T, U> from(U observable) {
Objects.requireNonNull(observable);
return new Builder<>(observable);
}
/**
* Creates a new builder for a chain with the given <b>value</b> as its root. This method wraps {@code value} in an
* {@code ObservableValue} implementation whose {@code getValue()} method will always return {@code value}.
*
* @param value the root value; may be {@code null}
* @return a new {@code Builder}
* @param <T> The value type.
* @see #from(ObservableValue)
*/
public static <T> Builder<T, ?> fromValue(T value) {
return from(new ImmutableObservableValue<>(value));
}
private final Link<?, ?, T, ?> tail;
private Chain(Link<?, ?, T, ?> tail) {
this.tail = tail;
bind(tail);
}
@Override
protected T computeValue() {
ObservableValue<T> observable = tail.get();
return observable == null ? null : observable.getValue();
}
@Override
public void dispose() {
tail.dispose();
}
/**
* A builder class for {@link Chain} objects. A {@code Builder} instance is not reusable. Once any of the "link"
* methods or the {@code build} method are invoked, invoking any of them again will result in an {@code
* IllegalStateException} being thrown. Note that the "link" methods all return a <b>new</b> instance of {@code
* Builder}.
*
* <p>This class provides four "link" methods for different scenarios.
*
* <table>
* <tbody>
* <tr>
* <th>Method</th>
* <th>Purpose</th>
* </tr>
* <tr>
* <td>{@link #link(Callback)}</td>
* <td>To map the previous {@code ObservableValue} to another {@code ObservableValue}.</td>
* </tr>
* <tr>
* <td>{@link #linkFromValue(Callback)}</td>
* <td>To map the <b>value</b> of the previous {@code ObservableValue} to an {@code ObservableValue}.</td>
* </tr>
* <tr>
* <td>{@link #linkToValue(Callback)}</td>
* <td>To map the previous {@code ObservableValue} to a <b>value</b>.</td>
* </tr>
* <tr>
* <td>{@link #linkByValue(Callback)}</td>
* <td>To map the <b>value</b> of the previous {@code ObservableValue} to a <b>value</b>.</td>
* </tr>
* </tbody>
* </table>
*
* @param <T> The observable's value type.
* @param <U> The observable type.
* @see Chain#from(ObservableValue)
* @see Chain#fromValue(Object)
*/
public static class Builder<T, U extends ObservableValue<T>> {
private final Link<?, ?, T, U> tail;
private boolean valid = true;
// creates a builder for the root link
Builder(U observable) {
this.tail = new Link<>(observable);
}
// creates a builder for the next link in the chain
Builder(Link<?, ?, T, U> tail) {
this.tail = tail;
}
/**
* Creates a link that maps the previous link's observable to a new observable.
*
* <p>The {@code callback} will never be invoked with a {@code null} argument.
*
* @param callback the callback to compute the new link's observable
* @return a <b>new</b> {@code Builder} instance
* @param <X> The next observable's value type.
* @param <Y> The next observable's type.
* @throws IllegalStateException if this builder has already been used to create the next link or build the
* chain
*/
public <X, Y extends ObservableValue<X>> Builder<X, Y> link(Callback<? super U, ? extends Y> callback) {
Objects.requireNonNull(callback);
invalidate();
return new Builder<>(new Link<>(tail, callback));
}
/**
* Creates a link that maps the <b>value</b> of the previous link's observable to a new observable.
*
* <p>The {@code callback} will never be invoked with a {@code null} argument.
*
* @param callback the callback to compute the new link's observable
* @return a <b>new</b> {@code Builder} instance to continue building the chain
* @param <X> The next observable's value type.
* @param <Y> The next observable's type.
* @throws IllegalStateException if this builder has already been used to create the next link or build the
* chain
*/
public <X, Y extends ObservableValue<X>> Builder<X, Y> linkFromValue(
Callback<? super T, ? extends Y> callback) {
Objects.requireNonNull(callback);
invalidate();
return new Builder<>(new Link<>(tail, observable -> {
T value = observable.getValue();
return value == null ? null : callback.call(value);
}));
}
/**
* Creates a new link that maps the previous link's observable to a new <b>value</b>. The new value, when not
* {@code null}, will be wrapped in an {@code ObservableValue} implementation whose {@code getValue()} method
* will always return said new value. If the value of the previous observable is {@code null} then the chain
* is considered broken.
*
* <p>The {@code callback} will never be invoked with a {@code null} argument.
*
* @param callback the callback to compute the new link's value
* @return a <b>new</b> {@code Builder} instance to continue building the chain
* @param <X> The next observable value's value type.
* @throws IllegalStateException if this builder has already been used to create the next link or build the
* chain
*/
public <X> Builder<X, ?> linkToValue(Callback<? super U, ? extends X> callback) {
Objects.requireNonNull(callback);
invalidate();
return new Builder<>(new Link<>(tail, observable -> {
X nextValue = callback.call(observable);
return nextValue == null ? null : new ImmutableObservableValue<>(nextValue);
}));
}
/**
* Creates a new link that maps the <b>value</b> of the previous link's observable to a new <b>value</b>. The
* new value, when not {@code null}, will wrapped in an {@code ObservableValue} implementation whose {@code
* getValue()} method will always return said new value. If the value of the previous observable is {@code null}
* then the chain is considered broken.
*
* <p>The {@code callback} will never be invoked with a {@code null} argument.
*
* @param callback the callback to compute the new link's value
* @return a <b>new</b> {@code Builder} instance to continue building the chain
* @param <X> The next observable value's value type.
* @throws IllegalStateException if this builder has already been used to create the next link or build the
* chain
*/
public <X> Builder<X, ?> linkByValue(Callback<? super T, ? extends X> callback) {
Objects.requireNonNull(callback);
invalidate();
return new Builder<>(new Link<>(tail, observable -> {
T value = observable.getValue();
if (value == null) {
return null;
}
X nextValue = callback.call(value);
return nextValue == null ? null : new ImmutableObservableValue<>(nextValue);
}));
}
/**
* Builds and returns the {@code Chain}.
*
* @return the built {@code Chain}
* @throws IllegalStateException if this builder has already been used to create the next link or build the
* chain
*/
public Chain<T> build() {
invalidate();
return new Chain<>(tail);
}
private void invalidate() {
if (!valid) {
throw new IllegalStateException("builder already used to create next link or build chain");
}
valid = false;
}
}
private static class Link<T, U extends ObservableValue<T>, X, Y extends ObservableValue<X>>
extends ObjectBinding<Y> {
private final boolean root;
private final Link<?, ?, T, U> previous;
private final Callback<? super U, ? extends Y> callback;
private Y observable;
// creates a root link
Link(Y observable) {
this.root = true;
this.previous = null;
this.callback = null;
this.observable = observable;
bind(observable);
}
// creates a non-root link
Link(Link<?, ?, T, U> previous, Callback<? super U, ? extends Y> callback) {
this.root = false;
this.previous = previous;
this.callback = callback;
bind(previous);
}
@Override
protected Y computeValue() {
/*
* A link can become invalid in one of two ways:
*
* - The previous link is invalidated.
* - The observable of this link is invalidated.
*
* Only in the first case do we need to recompute the observable for
* this link. Whether or not the observable needs to be recomputed
* upon invalidation is handled by the onInvalidating() method.
*/
if (!root && observable == null) {
U previousObservable = previous.get();
if (previousObservable != null) {
observable = callback.call(previousObservable);
if (observable != null) {
bind(observable);
}
}
}
return observable;
}
@Override
protected void onInvalidating() {
if (!root && !previous.isValid() && observable != null) {
unbind(observable);
observable = null;
}
}
@Override
public void dispose() {
if (observable != null) {
unbind(observable);
observable = null;
}
if (!root) {
unbind(previous);
previous.dispose();
}
}
}
private static class ImmutableObservableValue<T> implements ObservableValue<T> {
private final T value;
ImmutableObservableValue(T value) {
this.value = value;
}
@Override
public T getValue() {
return value;
}
/*
* Since this observable value is immutable, there's no reason to store
* the listeners. These methods simply check the argument for null in
* order to minimally satisfy their contracts.
*/
@Override
public void addListener(ChangeListener<? super T> listener) {
Objects.requireNonNull(listener);
}
@Override
public void removeListener(ChangeListener<? super T> listener) {
Objects.requireNonNull(listener);
}
@Override
public void addListener(InvalidationListener listener) {
Objects.requireNonNull(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
Objects.requireNonNull(listener);
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用Chain,您可以disable像这样绑定属性:
void bindDisableProperty(Node node, ChoiceBox<Foo> choiceBox) {
Chain<Foo> selectedItem = Chain.from(choiceBox.selectionModelProperty())
.linkFromValue(SelectionModel::selectedItemProperty)
.build();
ObjectBinding<Boolean> disable = Bindings.when(selectedItem.isNotNull())
.then(Chain.from(selectedItem).linkFromValue(Foo::flagProperty).build())
.otherwise(Boolean.TRUE);
node.disableProperty().bind(disable);
}
Run Code Online (Sandbox Code Playgroud)
当然,可能有一个现有的库可以做类似的事情。
| 归档时间: |
|
| 查看次数: |
95 次 |
| 最近记录: |