ReactFX - "lazy"实时搜索文本区域



响应式编程新知识。

我正在尝试在JavaFX中使用ReactFX实现一个"懒惰"的实时搜索文本区域。这里所说的懒惰是指一旦用户停止输入一秒钟,它就会执行搜索。代码非常简单:

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

这是另一个解决方案。这一个计算Enter键,并且只有在Enter计数没有改变的情况下才让编辑事件触发搜索。

import java.time.Duration;
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 AutoSearch2 extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(Stage stage) throws Exception {
        TextArea area = new TextArea();
        EventStream<KeyEvent> enterPressed = EventStreams.eventsOf(area, KeyEvent.KEY_PRESSED)
                .filter(k -> k.getCode() == KeyCode.ENTER)
                .hook(KeyEvent::consume);
        EventStream<Long> enterCount = enterPressed.accumulate(0L, (n, k) -> n + 1)
                                                   .withDefaultEvent(0L);
        EventStream<Long> delayedEdits = enterCount.emitOnEach(EventStreams.changesOf(area.textProperty()))
                                                   .successionEnds(Duration.ofSeconds(1));
        // check that the delayed edit event still has the current value of the Enter counter
        EventStream<?> validEdits = enterCount.emitBothOnEach(delayedEdits)
                                              .filter(cd -> cd.test((current, delayed) -> delayed == current));
        EventStream<?> searchImpulse = EventStreams.merge(enterPressed, validEdits);
        searchImpulse.subscribe(x -> System.out.println("Search now!"));
        stage.setScene(new Scene(area));
        stage.show();
    }
}

未测试,但如何:

EventStream<KeyEvent> enterKeyPressedEvents = EventStreams.eventsOf(textArea, KeyEvent.KEY_PRESSED)
        .filter(k -> k.getCode() == KeyCode.ENTER);
EventStream<Change<String>> textEvents = EventStreams.changesOf(textArea.textProperty())
        .successionEnds(Duration.ofSeconds(1))
        .filter(c -> ! isAdditionOfNewline(c, textArea.getCaratPosition()));
EventStreams.merge(enterKeyPressedEvents, textEvents)
        .subscribe(o -> performSearch(textArea.getText()));
private boolean isAdditionOfNewline(Change<String> change, int caratPos) {
    // TODO make sure this works as required
    String oldText = change.getOldValue();
    String newText = change.getNewValue();
    if (oldText.length()+1 != newText.length() || caratPos == 0) {
        return false ;
    }
    return oldText.equals(newText.substring(0, caratPos-1) + newText.substring(caratPos));
}

相关内容

  • 没有找到相关文章

最新更新