java - 有没有办法在不向其侦听器触发值更改事件的情况下更改 Java 属性?
问题描述
我正在尝试做的事情
我正在寻找一种更改属性的方法,而无需调用侦听器的更改方法。
更具体地说,我正在尝试实现撤消/重做功能。我实现它的方式如下,在一个带有 aBooleanProperty
和一个 JavaFX的示例中CheckBox
。
- 的
selectedProperty
是CheckBox
通过鼠标点击来改变的。 - A
BooleanProperty
(实际上是 JavaFXSimpleBooleanProperty
)已更改,因为它双向绑定到selectedProperty
- 的注册这个并
ChangeListener
在应用程序的. 存储属性、旧值和新值。BooleanProperty
Command
undoStack
Command
- 用户单击撤消按钮
- 通过按钮,应用程序
Command
从堆栈中取出最后一个并调用它的undo()
方法。 - 该
undo()
方法改变了BooleanProperty
背部。 ChangeListener
再次注册此更改并创建一个新的Command
- 创造了一个无限循环
我的黑客解决方案
我这样做的方式是将 传递ChangeListener
给Command
对象。然后该undo()
方法首先删除ChangeListener
,更改BooleanProperty
,然后ChangeListener
再次添加 。将 the 传递给 the
感觉是错误和骇人听闻的(在我的 3. 步骤中的实际实现中,实际上在 the和 the之间还有几个类,现在都需要了解 the )ChangeListener
Command
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);
}
}
推荐阅读
- svg - I complie test code of svgpp ,there is error forbids declaration of ‘BOOST_DELETED_FUNCTION’ with no type。But I have install boost
- python - 在为类和 chrome gui 错误运行我的 selenium 代码时出现两个错误
- elm - Elm 语言中的“Program 是由三个类型变量参数化的类型:flags、model 和 msg”是什么意思?
- php - php preg_replace 正则表达式替换子匹配中的匹配字符
- python - 创建列表时,“Amounts_list”看起来好像没有将值添加到列表中。我得到一个 None 值
- rest - 测试远程 REST 服务 API,特别是 HubSpot
- google-compute-engine - 在 Windows 服务器上配置 Stackdriver 代理以收集 Qlik 日志
- vue.js - Quasar Framework v1 中的 Setp Apollo graphql
- javascript - 想要从数组中拼接一个元素
- reactjs - 使用 scss 为 react-bootstrap 自定义样式