Сделайте многократные выстрелы изменения, чтобы привести к намного меньшему количеству действий - PullRequest
0 голосов
/ 02 мая 2020

У меня есть TextArea, в котором пользователь моего приложения может писать вещи. A ChangeListener также прослушивает StringProperty «текст» этого TextArea. Всякий раз, когда изменяется текстовое содержимое, ChangeListener.changed(), среди прочего, устанавливает "грязный" BooleanProperty в true для центрального объекта приложения. Где «грязный» имеет смысл «документ нуждается в сохранении».

Но я только что реализовал в своем приложении кое-что, когда каждый раз, когда «грязный» Property устанавливается на true, запускается автоматическое сохранение файла-на-диске, так что пользователю не нужно беспокоиться о ручном сохранении файлов. Примечание: акт сохранения также устанавливает dirty обратно на * 1013. * Конечно.

Одна из проблем заключается в том, что это замедляет ввод TextArea (в частности, поскольку это сохранение происходит на Форекс нить). Поэтому каждый новый добавленный или удаленный символ запускает действие сохранения.

Я хочу найти решение, в котором за каждым действием по изменению текста всегда следует сохранение не более, например, в течение 1 секунды, но где сохранение никогда не происходит более одного раза в течение этой 1 секунды ... очевидно, в потоке без FX.

Это не первый раз, когда я сталкивался с такой ситуацией, и в прошлом я возился с различными очереди таймеров и тд. Но трудно найти решение, когда оба критерия удовлетворены, и мне просто интересно, есть ли известный метод для решения этой проблемы ... даже, может быть, что-то из какой-то библиотеки?

Вот MRE / MCVE :

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    BooleanProperty dirty;
    void setDirty(Boolean value) {
        dirtyProperty().set(value);
        if( value ){
            System.out.println( "serialise to file again...");
            // successful saving also means we become clean again:
            dirtyProperty().set( false );
        }
    }
    Boolean getDirty() { return dirtyProperty().get(); }
    BooleanProperty dirtyProperty() {
        if ( dirty == null) dirty = new SimpleBooleanProperty(this, "dirty");
        return dirty;
    }

    @Override
    public void start(Stage stage) throws Exception {
        Scene scene = new Scene(new Group());
        Group sceneRoot = (Group)scene.getRoot();
        TextArea textArea = new TextArea();

        textArea.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
                setDirty( true );
            }
        });

        sceneRoot.getChildren().add( textArea );
        stage.setMinWidth( 600 );
        stage.setMinHeight( 400 );
        stage.setScene(scene);
        stage.show();
    }
}

Каждое нажатие клавиши вызывает сохранение ...

1 Ответ

2 голосов
/ 02 мая 2020

Вместо использования фоновых потоков, используйте переход паузы:

PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(e -> {
    // save action here
});

dirtyProperty().addListener((obs, wasDirty, isNowDirty) -> {
    if (isNowDirty && pause.getStatus() != Animation.Status.RUNNING) {
        pause.playFromStart();
    }
});

Это запустит паузу в одну секунду, когда dirtyProperty() изменится на true, и в конце паузы сохранит данные. Однако, проверяя состояние паузы, он не будет планировать более одного сохранения в секунду.

Если вы хотите использовать фоновые потоки, вы можете сделать что-то вроде следующего:

BlockingQueue<String> textToSave = new ArrayBlockingQueue<>(1);
ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
exec.scheduleWithFixedDelay(() -> {
    try {
        String text = textToSave.take();
        // save text
    } catch (InterruptedException interrupt) {
        Thread.currentThread().interrupt();
    }
}, 0, 1, TimeUnit.SECONDS);

и затем

dirtyProperty().addListener((obs, wasDirty, isNowDirty) -> {
    textToSave.clear();
    textToSave.offer(myTextArea.getText());
});

Это предполагает, что никакие потоки, кроме потока приложений FX, не отправляют данные в textToSave «очередь» (не уверен, что что-то с размером <= 1 правильно называется "queue"), что кажется достаточно простым для обеспечения. </p>

Преимущество этого подхода состоит в том, что IO происходит в фоновом потоке, что означает, что нет никакой возможности блокировать поток приложения FX путем записи файла.

...