我正在尝试做什么
我正在寻找一种更改属性的方法,而无需调用监听器的更改方法。
更具体地说,我正在尝试实现撤消/重做功能。我实现它的方式如下,在一个带有 BooleanProperty
的示例中和 JavaFX CheckBox
.
selectedProperty
CheckBox
的单击鼠标即可更改。- 一个
BooleanProperty
(实际上是 JavaFXSimpleBooleanProperty
)已更改,因为它双向绑定(bind)到selectedProperty
ChangeListener
BooleanProperty
的注册它并添加Command
在应用程序的undoStack
。Command
存储属性、旧值和新值。- 用户点击撤消按钮
- 通过按钮,应用程序获取最后的
Command
从堆栈中调用它是undo()
方法。 undo()
方法改变BooleanProperty
回来。ChangeListener
再次注册此更改并创建一个新的Command
- 创建无限循环
我的黑客解决方案
我这样做的方法是传递 ChangeListener
到Command
目的。然后是undo()
方法首先删除 ChangeListener
,更改 BooleanProperty
然后添加ChangeListener
再次。
通过ChangeListener
感觉是错误的和hacky的到Command
(在我的第 3 步的实际实现中, ChangeListener
和 Command
之间实际上还有一些类,现在都需要了解 ChangeListener
)
我的问题
这真的是这样做的方法吗?有没有办法在第 6 步中更改属性并告诉它不通知它的监听器?或者至少是为了吸引听众?
最佳答案
正如您所描述的,没有支持绕过监听器的方法。您只需将此逻辑构建到您的撤消/重做机制中即可。这个想法基本上是在执行撤消/重做时设置一个标志,如果是这样,则不将更改添加到堆栈中。
这是一个非常简单的示例:请注意,这不是生产质量 - 例如,在文本控件中键入内容将添加到每个字符更改的堆栈中(在每次更改时保留当前文本的副本)。在实际代码中,您应该将这些更改合并在一起。
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);
}
}
关于java - 有没有一种方法可以更改 Java 属性而不向其监听器触发值更改事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61478539/