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

Что я пытаюсь сделать

Я ищу способ изменить свойство без вызова измененного метода слушателей.

Более конкретно, я пытаюсь реализовать функции отмены / повтора. Я реализовал это следующим образом: в примере с BooleanProperty и JavaFX CheckBox.

  1. selectedProperty из CheckBox изменяется щелчком мыши .
  2. A BooleanProperty (на самом деле JavaFX SimpleBooleanProperty) изменен, поскольку он связан в двух направлениях с selectedProperty
  3. . ChangeListener из BooleanProperty регистрирует это и добавляет Command в приложении undoStack. Command сохраняет свойство, старое и новое значение.
  4. Пользователь нажимает кнопку отмены
  5. С помощью кнопки приложение извлекает последний из Command из стека и вызывает его undo() метод.
  6. Метод undo() изменяет обратно BooleanProperty.
  7. ChangeListener регистрирует это изменение снова и создает новый Command
  8. Создан бесконечный цикл

Мое решение Hacky

Я сделал это, передав ChangeListener до Command объекта. Затем метод undo() сначала удаляет ChangeListener, изменяет BooleanProperty, а затем снова добавляет ChangeListener.
Считается неправильным и хакерским передавать ChangeListener в Command (в моем случае Реализация в шаге 3. На самом деле между ChangeListener и Command есть еще несколько классов, которые теперь все должны знать о ChangeListener)

Мой вопрос

это действительно способ сделать это? Разве нет способа изменить свойство на шаге 6 и просто сообщить ему , а не , информировать своих слушателей? Или хотя бы завести своих слушателей?

1 Ответ

0 голосов
/ 28 апреля 2020

Нет поддерживаемого способа обхода слушателей, как вы описываете. Вам просто нужно встроить эту логику c в ваш механизм отмены / возврата. Основная идея заключается в том, чтобы установить флаг, если вы выполняете отмену / повтор, и не добавлять изменения в свой стек, если так.

Вот очень простой пример: обратите внимание, что это не качество производства - например, ввод в текстовом элементе управления добавляются в стек для каждого изменения символа (сохраняя копии текущего текста при каждом изменении). В реальном коде вы должны объединить эти изменения вместе.

import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;

public class UndoManager {

    private boolean performingUndoRedo = false ;
    private Deque<Command<?>> undoStack = new LinkedList<>();
    private Deque<Command<?>> redoStack = new LinkedList<>();


    private Map<Property<?>, ChangeListener<?>> listeners = new HashMap<>();

    public <T> void register(Property<T> property) {
        // don't register properties multiple times:
        if (listeners.containsKey(property)) {
            return ;
        }
        // FIXME: should coalesce (some) changes on the same property, so, e.g. typing in a text
        // control does not result in a separate command for each character
        ChangeListener<? super T> listener = (obs, oldValue, newValue) -> {
            if (! performingUndoRedo) {
                Command<T> cmd = new Command<>(property, oldValue, newValue) ;
                undoStack.addFirst(cmd);
            }
        };
        property.addListener(listener);
        listeners.put(property, listener);
    }

    public <T> void unregister(Property<T> property) {
        listeners.remove(property);
    }

    public void undo() {
        if (undoStack.isEmpty()) {
            return ;
        }
        Command<?> command = undoStack.pop();
        performingUndoRedo = true ;
        command.undo();
        redoStack.addFirst(command);
        performingUndoRedo = false ;
    }

    public void redo() {
        if (redoStack.isEmpty()) {
            return ;
        }
        Command<?> command = redoStack.pop();
        performingUndoRedo = true ;
        command.redo();
        undoStack.addFirst(command);
        performingUndoRedo = false ;
    }



    private static class Command<T> {
        private final Property<T> property ;
        private final T oldValue ;
        private final T newValue ;

        public Command(Property<T> property, T oldValue, T newValue) {
            super();
            this.property = property;
            this.oldValue = oldValue;
            this.newValue = newValue;
        }

        private void undo() {
            property.setValue(oldValue);
        }

        private void redo() {
            property.setValue(newValue);
        }

        @Override 
        public String toString() {
            return "property: "+property+", from: "+oldValue+", to: "+newValue ;
        }
    }
}

А вот быстрый тестовый комплект:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class UndoExample extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        ComboBox<Color> textColor = new ComboBox<Color>();
        textColor.getItems().addAll(Color.BLACK, Color.RED, Color.DARKGREEN, Color.BLUE);
        textColor.setValue(Color.BLACK);
        textColor.setCellFactory(lv -> new ColorCell());
        textColor.setButtonCell(new ColorCell());
        CheckBox italic = new CheckBox("Italic");
        TextArea text = new TextArea();
        updateStyle(text, textColor.getValue(), italic.isSelected());

        ChangeListener<Object> listener = (obs, oldValue, newValue) -> 
            updateStyle(text, textColor.getValue(), italic.isSelected());
        textColor.valueProperty().addListener(listener);
        italic.selectedProperty().addListener(listener);

        UndoManager undoMgr = new UndoManager();
        undoMgr.register(textColor.valueProperty());
        undoMgr.register(italic.selectedProperty());
        undoMgr.register(text.textProperty());

        Button undo = new Button("Undo");
        Button redo = new Button("Redo");
        undo.setOnAction(e -> undoMgr.undo());
        redo.setOnAction(e -> undoMgr.redo());

        HBox controls = new HBox(textColor, italic, undo, redo);
        controls.setSpacing(5);

        BorderPane root = new BorderPane(text);
        root.setTop(controls);

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    private void updateStyle(TextArea text, Color textColor, boolean italic) {
        StringBuilder style = new StringBuilder()
                .append("-fx-text-fill: ")
                .append(hexString(textColor))
                .append(";")
                .append("-fx-font: ");
        if (italic) {
            style.append("italic ");
        }
        style.append("13pt sans-serif ;");
        text.setStyle(style.toString());
    }

    private String hexString(Color color) {
        int r = (int) (color.getRed() * 255) ;
        int g = (int) (color.getGreen() * 255) ;
        int b = (int) (color.getBlue() * 255) ;
        return String.format("#%02x%02x%02x", r, g, b);
    }

    private static class ColorCell extends ListCell<Color> {
        private Rectangle rect = new Rectangle(25, 25);
        @Override
        protected void updateItem(Color color, boolean empty) {
            super.updateItem(color, empty);
            if (empty || color==null) {
                setGraphic(null);
            } else {
                rect.setFill(color);
                setGraphic(rect);
            }
        }       
    }

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

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...