首页 > 解决方案 > 有没有办法在不向其侦听器触发值更改事件的情况下更改 Java 属性?

问题描述

我正在尝试做的事情

我正在寻找一种更改属性的方法,而无需调用侦听器的更改方法。

更具体地说,我正在尝试实现撤消/重做功能。我实现它的方式如下,在一个带有 aBooleanProperty和一个 JavaFX的示例中CheckBox

  1. selectedPropertyCheckBox通过鼠标点击来改变的。
  2. A BooleanProperty(实际上是 JavaFX SimpleBooleanProperty)已更改,因为它双向绑定到selectedProperty
  3. 的注册这个并ChangeListener在应用程序的. 存储属性、旧值和新值。BooleanPropertyCommandundoStackCommand
  4. 用户单击撤消按钮
  5. 通过按钮,应用程序Command从堆栈中取出最后一个并调用它的undo()方法。
  6. undo()方法改变了BooleanProperty背部。
  7. ChangeListener再次注册此更改并创建一个新的Command
  8. 创造了一个无限循环

我的黑客解决方案

我这样做的方式是将 传递ChangeListenerCommand对象。然后该undo()方法首先删除ChangeListener,更改BooleanProperty,然后ChangeListener再次添加 。将 the 传递给 the
感觉是错误和骇人听闻的(在我的 3. 步骤中的实际实现中,实际上在 the和 the之间还有几个类,现在都需要了解 the )ChangeListenerCommandChangeListenerCommandChangeListener

我的问题

这真的是这样做的方法吗?有没有办法在第 6 步中更改属性并告诉它不要通知它的听众?或者至少得到它的听众?

标签: javajavafxpropertiesundo-redo

解决方案


正如您所描述的,没有支持绕过侦听器的方法。您只需要将这个逻辑构建到您的撤消/重做机制中。这个想法基本上是在您执行撤消/重做时设置一个标志,如果是这样,则不要将更改添加到您的堆栈中。

这是一个非常简单的示例:请注意,这不是生产质量 - 例如,在每次字符更改时键入文本控件都会添加到堆栈中(在每次更改时保留当前文本的副本)。在实际代码中,您应该将这些更改合并在一起。

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

}

推荐阅读