JavaFX绘图程序Undo/Redo按钮实现技术咨询
JavaFX绘图程序实现撤销/重做功能
针对撤销按钮的实现需求,这里用**双栈(撤销栈+重做栈)**的方式落地,配合ObservableList的自动监听,就能完成基础的撤销和重做功能。
1. 修改shapeModel类
在模型中添加撤销栈、重做栈,重写添加形状的逻辑并实现undo/redo方法:
import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.paint.Color; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import java.util.ArrayDeque; import java.util.Deque; public class shapeModel { // 撤销栈:存储已添加的形状,用于撤销操作 private final Deque<Shape> undoStack = new ArrayDeque<>(); // 重做栈:存储被撤销的形状,用于重做操作 private final Deque<Shape> redoStack = new ArrayDeque<>(); private final ObservableList<Shape> shapeList; private final ObjectProperty<Color> colorPickerSelect; private ShapeType currentShape; public shapeModel() { this.colorPickerSelect = new SimpleObjectProperty<>(Color.GREEN); this.shapeList = FXCollections.observableArrayList(shape -> new Observable[]{ shape.colorProperty() }); } public ShapeType getCurrentShape() { return currentShape; } public void setCurrentShape(ShapeType currentShape) { this.currentShape = currentShape; } public void addShapes(Shape shape) { if (shape != null) { this.shapeList.add(shape); undoStack.push(shape); // 添加新形状后清空重做栈,之前的重做操作失效 redoStack.clear(); } } public Color getColorPickerSelect() { return colorPickerSelect.get(); } public ObjectProperty<Color> colorPickerSelectProperty() { return colorPickerSelect; } public ObservableList<Shape> getShapeObservableList() { return shapeList; } // 撤销操作:移除最后添加的形状,放入重做栈 public void undo() { if (!undoStack.isEmpty()) { Shape lastShape = undoStack.pop(); shapeList.remove(lastShape); redoStack.push(lastShape); } } // 重做操作:恢复最后被撤销的形状,放入撤销栈 public void redo() { if (!redoStack.isEmpty()) { Shape lastUndoneShape = redoStack.pop(); shapeList.add(lastUndoneShape); undoStack.push(lastUndoneShape); } } }
2. 修改HelloController类
完善撤销按钮的点击事件,同时修复画布重绘逻辑(避免图形叠加残留):
import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Button; import javafx.scene.control.ColorPicker; import javafx.scene.control.ChoiceBox; import javafx.scene.layout.BorderPane; import javafx.scene.input.MouseEvent; import javafx.application.Platform; import javafx.stage.FileChooser; import java.io.File; public class HelloController { @FXML public Button eraserButton; @FXML public Button undoButton; // 若需要重做功能,可添加对应的按钮 // @FXML public Button redoButton; @FXML public BorderPane scenePane; @FXML public ColorPicker myColorPicker; @FXML public ChoiceBox<String> myChoiceBox; public GraphicsContext context; @FXML public Canvas canvas; public shapeModel model; public void initialize() { context = canvas.getGraphicsContext2D(); model.getShapeObservableList().addListener((ListChangeListener<Shape>) e -> listChanged()); myColorPicker.valueProperty().bindBidirectional(model.colorPickerSelectProperty()); } public HelloController() { this.model = new shapeModel(); } public void canvasClicked(MouseEvent mouseEvent) { Shape shape = Shape.creatingShapes(model.getCurrentShape(),mouseEvent.getX(),mouseEvent.getY(),50,50,myColorPicker.getValue()); model.addShapes(shape); } // 修复重绘逻辑:先清空画布再重新绘制所有形状 public void listChanged() { var context = canvas.getGraphicsContext2D(); context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); model.getShapeObservableList().forEach(s -> s.draw(context)); } // 撤销按钮点击事件 public void undoClicked() { model.undo(); } // 若有重做按钮,添加对应点击事件 // public void redoClicked() { // model.redo(); // } public void handleClick() { FileChooser chooser = new FileChooser(); File file = chooser.showSaveDialog(scenePane.getScene().getWindow()); } public void onCircleClicked(ActionEvent e) { model.setCurrentShape(ShapeType.CIRCLE); } public void onRectangleClicked(ActionEvent e) { model.setCurrentShape(ShapeType.RECTANGLE); } public void exitAction(ActionEvent actionEvent) { Platform.exit(); } public void eraser() { context.setFill(Color.WHITE); context.fillRect(0,0,canvas.getWidth(), canvas.getHeight()); } }
3. 关键逻辑说明
- 撤销栈:每次添加新形状时,将形状推入撤销栈,同时清空重做栈(新操作会打断之前的重做历史)
- 撤销操作:从撤销栈弹出最后一个形状,从显示列表移除后推入重做栈
- 重做操作:从重做栈弹出最后一个被撤销的形状,重新添加到显示列表后推入撤销栈
- 重绘逻辑:每次列表变化时先清空画布,再重新绘制所有形状,避免图形叠加残留
内容的提问来源于stack exchange,提问作者Haggen
相关产品推荐
相关产品推荐

