转换 ObervableValue
Transform an ObervableValue
我需要转换一个 ObservableValue。
例如:我有一个 ObervableStringValue,我想在 JavaFX 控件中显示字符串的长度。当我这样做时: value.get().length()
我只是得到一个 int,但我需要一个 ObservableValue。
所以我很快写了一个自己的包装器:
/**
* Wraps an ObervableValue and offers a transformed value of it.
*
* @param <F> From type.
* @param <T> To type.
*/
public class TransformedValue<F, T> extends ObservableValueBase<T>
{
private final ObservableValue<F> original;
private final Function<F, T> function;
/**
* @param original ObservableValue to transform.
* @param function Transform function.
*/
public TransformedValue(ObservableValue<F> original, Function<F, T> function)
{
this.original = original;
this.function = function;
original.addListener((observable, oldValue, newValue) -> fireValueChangedEvent());
}
@Override
public T getValue()
{
return function.apply(original.getValue());
}
}
用法:
new TransformedValue<>(someObservableStringValue, s -> s.length());
这是我的问题:
- 我的做法完全愚蠢吗?
- 是否有 JavaFX 方法可以做到这一点?
- 是否有第三方库可以做到这一点?
- 对我的代码有什么建议吗? (例如注销监听器)
编辑:
使用 String.length() 的例子太简单了,下面是大故事:
我有一个 ObservableList 传感器。每个传感器提供一个或多个测量值,具体取决于传感器的类型。传感器的属性是 ObservabeValues。我在 TreeTableView 中显示传感器及其当前测量值。每个传感器都有其节点及其测量作为子节点。如果现在将重点放在时间戳列上。
TreeTableColumns 的初始化:
...
sensorTreeTimestamp.setCellValueFactory(cell -> cell.getValue().getValue().getTimestamp());
...
由于 TreeTableView 中有完全不同的数据类型,我有自己的 class SensorTreeValue
来保存数据:
private static class SensorTreeValue
{
...
private final ObservableValue<String> timestamp;
...
它有一个构造函数来表示传感器和一个用于测量:
private SensorTreeValue(Sensor sensor)
{
...
timestamp = new TransformedValue<>(sensor.getLastSeen(), (time) -> Utils.formatDateTime(time));
}
private SensorTreeValue(Sensor sensor, ValueType valueType)
{
...
timestamp = new TransformedValue<>(sensor.getLastMeasurement(), measure -> Utils.formatDateTime(measure.getTime()));
}
我知道有一个 asString(format)
函数。但这还不够,因为我仍然需要从测量中获取时间,而且我没有找到将日期转换为区域设置格式字符串的格式字符串。
我也可以将逻辑放入 CellValueFactory 中,但如果它是传感器还是测量,我必须在那里进行类型检查。
这里有一个方法,但是你的结构太复杂了。我不知道您是如何向 table 添加项目的。如果是 TreeTableView<SensorTreeItem>
我觉得太复杂了。如果有多个级别的节点,我只会遇到这样的自定义项目的麻烦。就像 file/directory 视图一样,这是必要的。
我会将 table 中的每一行都设为测量值,但在测量值 class 中包含传感器名称。然后,当添加到 table 时,如果传感器名称尚不存在,则为传感器名称创建一个简单节点,并将其添加到该节点。
import java.util.Date;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FXTest extends Application {
public static void main(String[] args) { launch(args); }
@Override
public void start(Stage stage) {
Label lbl1 = new Label();
Sensor sens1 = new Sensor();
SensorTreeValue stv1 = new SensorTreeValue(sens1);
lbl1.textProperty().bind(stv1.timeStamp.concat(" sens1"));
Label lbl2 = new Label();
Sensor sens2 = new Sensor();
SensorTreeValue stv2 = new SensorTreeValue(sens2, 0);
lbl2.textProperty().bind(stv2.timeStamp.concat(" sens2"));
Scene scene = new Scene(new VBox(5, lbl1, lbl2));
stage.setScene(scene);
stage.show();
Timeline timer = new Timeline(new KeyFrame(
javafx.util.Duration.millis(1000), ae -> {
sens1.lastSeen.set(System.currentTimeMillis());
sens2.lastMeasurement.get().time.set(System.currentTimeMillis());
}));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();
}
private static class SensorTreeValue {
private final SimpleStringProperty timeStamp = new SimpleStringProperty();
private SensorTreeValue(Sensor sensor) {
//you can bind or set a property, not an ObsValue<T>
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastSeen.get()).toString();
},sensor.lastSeen));
}
private SensorTreeValue(Sensor sensor, int valueType) {
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastMeasurement.get().time.get()).toString();
},sensor.lastMeasurement.get().time));
}
}
private static class Sensor{
private SimpleLongProperty lastSeen = new SimpleLongProperty();
private SimpleObjectProperty<Measure> lastMeasurement = new SimpleObjectProperty<>(new Measure());
}
private static class Measure{
private SimpleLongProperty time = new SimpleLongProperty();
private SimpleLongProperty value = new SimpleLongProperty();
}
}
看看 EasyBind 框架,它提供了这种功能以及更多功能。
您建议的示例(创建一个 ObservableValue<Integer>
表示 ObservableValue<String>
的长度)是框架主页上的一个示例:
ObservableValue<String> text = ... ;
ObservableValue<Integer> textLength = EasyBind.map(text, String::length);
上面链接的项目主页上也显示了诸如获得 "property of a property" 等用例。
我需要转换一个 ObservableValue。
例如:我有一个 ObervableStringValue,我想在 JavaFX 控件中显示字符串的长度。当我这样做时: value.get().length()
我只是得到一个 int,但我需要一个 ObservableValue。
所以我很快写了一个自己的包装器:
/**
* Wraps an ObervableValue and offers a transformed value of it.
*
* @param <F> From type.
* @param <T> To type.
*/
public class TransformedValue<F, T> extends ObservableValueBase<T>
{
private final ObservableValue<F> original;
private final Function<F, T> function;
/**
* @param original ObservableValue to transform.
* @param function Transform function.
*/
public TransformedValue(ObservableValue<F> original, Function<F, T> function)
{
this.original = original;
this.function = function;
original.addListener((observable, oldValue, newValue) -> fireValueChangedEvent());
}
@Override
public T getValue()
{
return function.apply(original.getValue());
}
}
用法:
new TransformedValue<>(someObservableStringValue, s -> s.length());
这是我的问题:
- 我的做法完全愚蠢吗?
- 是否有 JavaFX 方法可以做到这一点?
- 是否有第三方库可以做到这一点?
- 对我的代码有什么建议吗? (例如注销监听器)
编辑:
使用 String.length() 的例子太简单了,下面是大故事:
我有一个 ObservableList 传感器。每个传感器提供一个或多个测量值,具体取决于传感器的类型。传感器的属性是 ObservabeValues。我在 TreeTableView 中显示传感器及其当前测量值。每个传感器都有其节点及其测量作为子节点。如果现在将重点放在时间戳列上。
TreeTableColumns 的初始化:
...
sensorTreeTimestamp.setCellValueFactory(cell -> cell.getValue().getValue().getTimestamp());
...
由于 TreeTableView 中有完全不同的数据类型,我有自己的 class SensorTreeValue
来保存数据:
private static class SensorTreeValue
{
...
private final ObservableValue<String> timestamp;
...
它有一个构造函数来表示传感器和一个用于测量:
private SensorTreeValue(Sensor sensor)
{
...
timestamp = new TransformedValue<>(sensor.getLastSeen(), (time) -> Utils.formatDateTime(time));
}
private SensorTreeValue(Sensor sensor, ValueType valueType)
{
...
timestamp = new TransformedValue<>(sensor.getLastMeasurement(), measure -> Utils.formatDateTime(measure.getTime()));
}
我知道有一个 asString(format)
函数。但这还不够,因为我仍然需要从测量中获取时间,而且我没有找到将日期转换为区域设置格式字符串的格式字符串。
我也可以将逻辑放入 CellValueFactory 中,但如果它是传感器还是测量,我必须在那里进行类型检查。
这里有一个方法,但是你的结构太复杂了。我不知道您是如何向 table 添加项目的。如果是 TreeTableView<SensorTreeItem>
我觉得太复杂了。如果有多个级别的节点,我只会遇到这样的自定义项目的麻烦。就像 file/directory 视图一样,这是必要的。
我会将 table 中的每一行都设为测量值,但在测量值 class 中包含传感器名称。然后,当添加到 table 时,如果传感器名称尚不存在,则为传感器名称创建一个简单节点,并将其添加到该节点。
import java.util.Date;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FXTest extends Application {
public static void main(String[] args) { launch(args); }
@Override
public void start(Stage stage) {
Label lbl1 = new Label();
Sensor sens1 = new Sensor();
SensorTreeValue stv1 = new SensorTreeValue(sens1);
lbl1.textProperty().bind(stv1.timeStamp.concat(" sens1"));
Label lbl2 = new Label();
Sensor sens2 = new Sensor();
SensorTreeValue stv2 = new SensorTreeValue(sens2, 0);
lbl2.textProperty().bind(stv2.timeStamp.concat(" sens2"));
Scene scene = new Scene(new VBox(5, lbl1, lbl2));
stage.setScene(scene);
stage.show();
Timeline timer = new Timeline(new KeyFrame(
javafx.util.Duration.millis(1000), ae -> {
sens1.lastSeen.set(System.currentTimeMillis());
sens2.lastMeasurement.get().time.set(System.currentTimeMillis());
}));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();
}
private static class SensorTreeValue {
private final SimpleStringProperty timeStamp = new SimpleStringProperty();
private SensorTreeValue(Sensor sensor) {
//you can bind or set a property, not an ObsValue<T>
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastSeen.get()).toString();
},sensor.lastSeen));
}
private SensorTreeValue(Sensor sensor, int valueType) {
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastMeasurement.get().time.get()).toString();
},sensor.lastMeasurement.get().time));
}
}
private static class Sensor{
private SimpleLongProperty lastSeen = new SimpleLongProperty();
private SimpleObjectProperty<Measure> lastMeasurement = new SimpleObjectProperty<>(new Measure());
}
private static class Measure{
private SimpleLongProperty time = new SimpleLongProperty();
private SimpleLongProperty value = new SimpleLongProperty();
}
}
看看 EasyBind 框架,它提供了这种功能以及更多功能。
您建议的示例(创建一个 ObservableValue<Integer>
表示 ObservableValue<String>
的长度)是框架主页上的一个示例:
ObservableValue<String> text = ... ;
ObservableValue<Integer> textLength = EasyBind.map(text, String::length);
上面链接的项目主页上也显示了诸如获得 "property of a property" 等用例。