这里是反应式编程的新手。

我正在尝试使用ReactFX在JavaFX中实现一个“惰性”实时搜索文本区域。在这里我懒惰的意思是,一旦用户停止键入一秒钟,它将执行搜索。该代码非常简单:

EventStream<Change<String>> textEvents = EventStreams.changesOf(textArea.textProperty())
.successionEnds(Duration.ofSeconds(1));


然后订阅该事件流并发贴。

但是我也希望它在用户按下Enter键后立即执行搜索。我不确定如何以“反应式”方式进行操作。仅对Enter键事件执行搜索会导致搜索两次触发(一个键事件触发,一个键文本更改触发),因此这是我当前的解决方案:

BooleanProperty hasSearched = new SimpleBooleanProperty(false);
EventStream<KeyEvent> enterKeyPressedEvents = EventStreams.eventsOf(textArea, KeyEvent.KEY_PRESSED)
        .filter(k -> k.getCode() == KeyCode.ENTER);
AwaitingEventStream<Change<String>> textEvents = EventStreams.changesOf(textArea.textProperty())
        .successionEnds(Duration.ofSeconds(1));

subs = Subscription.multi(
        //Text changed
        textEvents.subscribe(e -> {
            if (hasSearched.get()) {
                hasSearched.set(false);
                System.out.println("ignored text event");
            } else {
                performSearch(textArea.getText());
            }
        }),

        //Enter key pressed
        enterKeyPressedEvents.subscribe(e -> {
            e.consume();
            if (e.isShiftDown()) {
                textArea.insertText(textArea.getCaretPosition(), "\n");
            } else {
                hasSearched.set(true);
                System.out.println("enter pressed");
                performSearch(textArea.getText());
                if (!textEvents.isPending()) {
                    hasSearched.set(false);
                }
            }
        })
);


我尝试使用SuspendableEventStream.suspend()认为它会“丢弃”所有未决事件,但未按预期工作,仍然发出未决事件:

EventStream<KeyEvent> enterKeyPressedEvents = EventStreams.eventsOf(textArea, KeyEvent.KEY_PRESSED)
        .filter(k -> k.getCode() == KeyCode.ENTER);
SuspendableEventStream<Change<String>> textEvents = EventStreams.changesOf(textArea.textProperty())
        .successionEnds(Duration.ofSeconds(1)).suppressible();

subs = Subscription.multi(
        //Text changed
        textEvents.subscribe(e -> {
                performSearch(textArea.getText());
        }),

        //Enter key pressed
        enterKeyPressedEvents.subscribe(e -> {
            e.consume();
            if (e.isShiftDown()) {
                textArea.insertText(textArea.getCaretPosition(), "\n");
            } else {
                Guard guard = textEvents.suspend();
                System.out.println("enter pressed");
                performSearch(textArea.getText());
                guard.close();
            }
        })
);


我如何想到更好的(更具反应性的)解决方案?

最佳答案

这是一个解决方案。此解决方案的关键部分是观察flatMap内部的文本更改,其作用是“重置”文本更改流。

import java.time.Duration;
import java.util.function.Function;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

import org.reactfx.EventStream;
import org.reactfx.EventStreams;

public class AutoSearch extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        TextArea area = new TextArea();

        EventStream<KeyEvent> enterPresses = EventStreams.eventsOf(area, KeyEvent.KEY_PRESSED)
                .filter(k -> k.getCode() == KeyCode.ENTER)
                .hook(KeyEvent::consume);

        EventStream<?> searchImpulse = enterPresses.withDefaultEvent(null) // emit an event even before Enter is pressed
                .flatMap(x -> {
                    EventStream<?> edits = EventStreams.changesOf(area.textProperty())
                                                       .successionEnds(Duration.ofSeconds(1));
                    return ((x == null) ? edits : edits.withDefaultEvent(null))
                            .map(Function.identity()); // just to get the proper type of the result
                });

        searchImpulse.subscribe(x -> System.out.println("Search now!"));

        stage.setScene(new Scene(area));
        stage.show();
    }

}

09-10 07:04
查看更多