TornadoFX/JavaFX - 根据另一个 observable 属性 过滤一个 observable 列表

TornadoFX/JavaFX - filter an observable list based on another observable property

我在使用 TorandoFX 找出相对简单的过滤配置时遇到了一些困难。我想基于 SimpleStringProperty 创建一个 FilteredList(由 ObservableList 支持)。过滤操作应该是 "bound" 到字符串 属性,这样任何对 属性 的更新都会自动重新执行过滤操作。

例如,假设我想根据字符串 属性 的 长度 过滤列表,以便 FilteredList 中的所有元素都具有 长度 >= 字符串 属性。以下无效。

val prop = SimpleStringProperty()
val baseList = listOf("a", "aa", "aaa", "b", "bb", "bbb")
val filteredList = FilteredList(baseList){ t -> prop.length().lessThanOrEqualTo(t.length).get()}

我将此界面连接到 GUI 中,但是当我在文本字段中键入内容(绑定到 SimpleStringProperty 时,组合框(绑定到 filteredList)没有改变。

如何使此代码生效?

我不知道 Kotlin/TornadoFX,但这里有一个您(或其他人)可以翻译的 JavaFX 解决方案。

基本思想是创建 FilteredList 并将其 predicateProperty 绑定到 Predicate,后者取决于适当的 StringProperty。有多种库方法可用于创建此类绑定。例如。你可以这样做:

filteredList = new FilteredList<>(baseList);
filteredList.predicateProperty().bind(
    new ObjectBinding<>() {
        {
            super.bind(prop);
        }
        @Override
        public Predicate<String> computeValue() {
            return t -> t.length() > prop.get().length() ;
        }
    }
);

您还可以使用 Bindings.createBinding() 方法,该方法采用 Callable<Predicate<String>> 和要观察的可观察对象列表(如果有任何无效,则重新计算):

filteredList.predicateProperty().bind(Bindings.createObjectBinding(
    // Callable<Predicate<String>> expressed as a lambda: () -> Predicate<String>
    () ->
        // Predicate<String> expressed as a lambda: String -> boolean
        t -> t.length() > prop.get().length(),
    prop
));

如果没有评论,那将简化为简洁(但令人难以置信)

filteredList.predicateProperty().bind(Bindings.createObjectBinding(
    () -> t -> t.length() > prop.get().length(),
    prop
));

这是一个完整的例子:

import static javafx.beans.binding.Bindings.createObjectBinding;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class FilteredListExample extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        ObservableList<String> baseList = FXCollections.observableArrayList("a", "aa", "aaa", "b", "bb", "bbb");
        FilteredList<String> filteredList = new FilteredList<>(baseList);

        ListView<String> listView = new ListView<>(filteredList);

        TextField input = new TextField();

        filteredList.predicateProperty().bind(createObjectBinding(
                () -> t -> t.length() >= input.getText().length(),
                input.textProperty()));


        BorderPane root = new BorderPane(listView, input, null, null, null) ;
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) { Application.launch(args); }

}

我明白了。感谢 James_DPredicates 给我指明了正确的方向。并感谢其他提供 JavaFX 示例的人(这引导我找到 TornadoFX/Kotlin 答案)。

这是 Kotlin 中的答案:

val prop = SimpleStringProperty()
val baseList = listOf("a", "aa", "aaa", "b", "bb", "bbb")
val filteredList = SortedFilteredList(baseList).apply {
        filterWhen(prop) {prop, item -> (prop?.length ?: 0) <= item.length}
}

这里的魔法是 filterWhen (see docs) 方法。由于我不明白的原因,它只能在 SortedFilteredList 上使用,而不是普通的 FilteredListfilterWhen 允许您显式声明要观察哪些属性以进行更改,并且过滤器每次都重新运行。