转换ObervableValue

时间:2015-01-25 19:41:06

标签: java javafx

我需要转换一个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()的例子太简单了,所以这是一个重要的故事:

我有一个Observable传感器列表。根据传感器的类型,每个传感器都提供一个或多个测量值。传感器的属性是ObservabeValues。我在TreeTableView中显示传感器及其当前测量值。每个传感器都有其节点及其测量值作为子节点。如果现在将重点关注时间戳列。

TreeTableColumns的初始化:

...
sensorTreeTimestamp.setCellValueFactory(cell -> cell.getValue().getValue().getTimestamp());
...

由于TreeTableView中有完全不同的数据类型,我有一个自己的类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,但是如果是传感器或测量,我必须进行类型检查。

2 个答案:

答案 0 :(得分:1)

查看EasyBind框架,它提供了这种功能以及更多功能。

您建议的示例(创建表示ObservableValue<Integer>长度的ObservableValue<String>)是框架主页上的示例:

ObservableValue<String> text = ... ;
ObservableValue<Integer> textLength = EasyBind.map(text, String::length);

获取“属性的属性”等用例也会显示在上面链接的项目主页上。

答案 1 :(得分:0)

这是一种方法,但你的结构太复杂了。我不知道你是如何在桌子上添加物品的。如果它是TreeTableView<SensorTreeItem>我觉得它太复杂了。如果有多个级别的节点,我只会遇到类似自定义项目的麻烦。就像文件/目录视图一样,这是必要的。

我会将表中的每一行都作为测量值,但在测量类中有传感器名称。然后,当添加到表中时,为传感器名称创建一个简单节点(如果它尚不存在)并将其添加到该节点。

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();
    }
}