目录
项目需求
第二部分需求:

项目实现
Part2
需求分析
实现流程:
总的来说就是实现撤销与重做功能
2.1 所有创建、删除、移动和调整大小的操作都可以撤销/重做
2.2 当用户按下Control-Z键时,之前的操作(如果有的话)将被撤消
2.3 当用户按下Control-R键时,会重做下一个动作(如果有的话)
2.4 如果用户已经撤消了之前的操作,然后再执行任何新的操作,则清除Redo堆栈
优化:
2.5 撤消/重做操作应该容纳作为原始操作一部分的任何多个选择(例如,移动多个)
代码分析与实现:
实现方法:
1.创建动作记录BlobAction类(记录动作的Blob对象,之前的位置信息,之前的大小信息数据,以及动作的类型)
2.在InteractionModel.java中创建撤销栈和重做栈用于存放撤销和重做的动作并添加注册栈以及取出栈动作函数
3. 设置全局快捷键
4.实现具体重做与撤销的方法体以及在合适位置添加注册栈的方法
5.最后的优化:
2.5 撤消/重做操作应该容纳作为原始操作一部分的任何多个选择(例如,移动多个)
代码实现:
1.创建动作记录BlobAction类(记录动作的Blob对象,之前的位置信息,之前的大小信息数据,以及动作的类型)
BlobAction.java
public class BlobAction {
private Blob blob;
private double prevX,prevY;
private double prevR;
private String type; // 1. 创建 CREATE 2. 移动 MOVE 3. 删除 DELETE 4.调整大小 MOTIFY
public BlobAction(Blob blob, double prevX, double prevY){
this.blob = blob;
this.prevX = prevX;
this.prevY = prevY;
this.type = "MOVE";
}
public BlobAction(Blob blob, String type) {
this.blob = blob;
this.type = type;
}
public BlobAction(Blob blob, double prevR){
this.blob = blob;
this.prevR = prevR;
this.type = "MOTIFY";
}
}
2.在InteractionModel.java中创建撤销栈和重做栈用于存放撤销和重做的动作并添加注册栈以及取出栈动作函数
InteractionModel.java
public class InteractionModel {
List<IModelListener> subscribers;
Blob selected;
// 撤销栈
Stack<BlobAction> undoStack = new Stack<BlobAction>();
// 重做栈
Stack<BlobAction> redoStack = new Stack<BlobAction>();
// 注册撤销栈
public void registerUndoStack(BlobAction blobAction){
undoStack.push(blobAction);
}
// 取出要撤销的操作
public BlobAction undoStack(){
if (!undoStack.isEmpty()){
BlobAction blobAction = undoStack.pop();
return blobAction;
}else {
System.out.println("无可撤销的操作");
}
return null;
}
// 注册要重做的操作
public void registerRedoStack(BlobAction blobAction){
redoStack.push(blobAction);
}
// 取出要重做的操作
public BlobAction redoStack(){
if (!redoStack.isEmpty()){
BlobAction blobAction = redoStack.pop();
return blobAction;
}else {
System.out.println("无可重做的操作");
}
return null;
}
// 清空撤销栈
public void clearUndoStack(){
System.out.println("清空撤销栈");
undoStack.clear();
}
// 清空重做栈
public void clearRedoStack(){
System.out.println("清空重做栈");
redoStack.clear();
}
}
3. 设置全局快捷键
实现方法:
1. 使用KeyCombination类创建目标快捷键按键
2.将快捷键设置在scene层
3.里面书写方法体
代码示例:
KeyCombination ctrl_z = new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN);
scene.getAccelerators().put(ctrl_z, () -> {
uiRoot.getController().handleUndo();
});
实现方法:
HelloApplication.java
public class HelloApplication extends Application {
@Override
public void start(Stage stage) {
MainUI uiRoot = new MainUI();
Scene scene = new Scene(uiRoot);
// 设置快捷键
setShortCuts(scene, uiRoot);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
// 实现快捷键的方法
public void setShortCuts(Scene scene, MainUI uiRoot){
// ctrl_Z 撤销
KeyCombination ctrl_z = new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN);
scene.getAccelerators().put(ctrl_z, () -> {
uiRoot.getController().handleUndo();
});
// ctrl_R 重做
KeyCombination ctrl_R = new KeyCodeCombination(KeyCode.R, KeyCombination.CONTROL_DOWN);
scene.getAccelerators().put(ctrl_R, () -> {
uiRoot.getController().handleRedo();
});
}
4.实现具体重做与撤销的方法体以及在合适位置添加注册栈的方法
1.在合适位置添加注册撤销栈的方法
(1)创建时 (2)删除时 (3)拖动时 (4)更改大小时 (5)清空时清空两个栈
2.在上述位置同样需要将重做栈清空
对应 2.4 如果用户已经撤消了之前的操作,然后再执行任何新的操作,则清除Redo堆栈
BlobController.java
public void handlePressed(MouseEvent event) {
switch (currentState) {
case READY -> {
if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪
Blob b = model.whichHit(event.getX(),event.getY());
iModel.setSelected(b);
prevX = event.getX();
prevY = event.getY();
currentState = State.DRAGGING; // 选中之后就改变状态为dragging
} else {
currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪
}
}
}
}
public void handleDragged(MouseEvent event) { // 鼠标拖动
switch (currentState) {
case PREPARE_CREATE -> {
currentState = State.READY;
}
case DRAGGING -> {
if (!event.isShiftDown()){
// 拖动被选中的⚪
dX = event.getX() - prevX;
dY = event.getY() - prevY;
System.out.println("移动之前"+iModel.getSelected());
prevX = event.getX();
prevY = event.getY();
double preX = iModel.getSelected().getX();
double preY = iModel.getSelected().getY();
model.moveBlob(iModel.getSelected(), dX,dY);
System.out.println("移动之后"+iModel.getSelected());
// 拖动时注册拖动操作
iModel.registerUndoStack(new BlobAction(iModel.getSelected(), preX, preY));
// 清空重做栈
iModel.clearRedoStack();
}else{
// 更改⚪的大小
dX = event.getX() - prevX;
Blob blob = iModel.getSelected();
double preR = blob.getR();
model.motifyR(blob,dX);
// 更改时注册更改大小操作
iModel.registerUndoStack(new BlobAction(blob, preR));
// 清空重做栈
iModel.clearRedoStack();
}
}
}
}
public void handleReleased(MouseEvent event) { // 鼠标释放
switch (currentState) {
case PREPARE_CREATE -> { // 创建一个⚪
if (event.isShiftDown()){
Blob blob = model.addBlob(event.getX(), event.getY());
currentState = State.READY;
// 创建时注册创建操作
iModel.registerUndoStack(new BlobAction(blob,"CREATE"));
// 清空重做栈
iModel.clearRedoStack();
}else if (event.isControlDown()){
// 删除所有⚪
// 清空时清空撤销与重做栈
model.clear();
iModel.clearUndoStack();
iModel.clearRedoStack();
}
}
case DRAGGING -> {
// iModel.unselect();
currentState = State.READY;
}
}
}
public void keyHandlePressed(KeyEvent event) { // 键盘输入
System.out.println(event.getCode());
if (event.getCode() == KeyCode.DELETE){
System.out.println("正在执行删除操作");
System.out.println(iModel.getSelected());
System.out.println(currentState);
Blob blob = iModel.getSelected();
model.deleteBlobs(iModel.getSelected());
// 删除时注册删除操作
iModel.registerUndoStack(new BlobAction(blob, "DELETE"));
// 清空重做栈
iModel.clearRedoStack();
}
}
2.创建处理撤销操作并添加注册重做栈
1.处理撤销方法
根据类别:
CREATE : 创建方法撤销就是将创建的再次删除
DELETE:删除就是重新创建
MOVE:移动就是返回原先的位置
MOTIFY:修改就是重新变为原来的大小
2.每执行一个撤销操作就将其注册到重做栈
BlobController.java
public void handleUndo() {
BlobAction blobAction = iModel.undoStack();
if (blobAction == null){
System.out.println("无可撤销的操作!");
return;
}
switch (blobAction.getType()) {
case "CREATE" -> {
// 删除
model.deleteBlobs(blobAction.getBlob());
currentState = State.READY;
// 注册重做栈
iModel.registerRedoStack(blobAction);
System.out.println("注册redo动作"+blobAction);
}
case "MOVE" -> {
// 撤销移动就是移动回去
dX = blobAction.getPrevX() - blobAction.getBlob().getX();
dY = blobAction.getPrevY() - blobAction.getBlob().getY();
blobAction.setPrevX(blobAction.getBlob().getX());
blobAction.setPrevY(blobAction.getBlob().getY());
model.moveBlob(blobAction.getBlob(), dX,dY);
// 注册重做栈
iModel.registerRedoStack(blobAction);
System.out.println("注册redo动作"+blobAction);
}
case "DELETE" -> {
// 撤销删除就是重新创建回来
model.addBlob(blobAction.getBlob().getX(),blobAction.getBlob().getY(), blobAction.getBlob().getR());
currentState = State.READY;
// 注册重做栈
iModel.registerRedoStack(blobAction);
System.out.println("注册redo动作"+blobAction);
}
case "MOTIFY" -> {
// 撤销放大操作
double dX = blobAction.getPrevR() - blobAction.getBlob().getR();
blobAction.setPrevR(blobAction.getBlob().getR());
model.motifyR(blobAction.getBlob(),dX);
// 注册重做栈
iModel.registerRedoStack(blobAction);
System.out.println("注册redo动作"+blobAction);
}
}
}
3.创建处理重做操作并添加注册撤销栈
1.处理撤销方法
根据类别:
CREATE : 创建方法重做就是重新创建
DELETE:删除就是重新删除
MOVE:移动就是返回原先的位置
MOTIFY:修改就是重新变为原来的大小
2.每执行一个重做操作就将其注册到撤销栈
BlobController.java
// 执行重做操作
public void handleRedo() {
BlobAction blobAction = iModel.redoStack();
if (blobAction == null){
System.out.println("无可重做的操作!");
return;
}
switch (blobAction.getType()) {
case "CREATE" -> {
// 创建
model.addBlob(blobAction.getBlob().getX(),blobAction.getBlob().getY(), blobAction.getBlob().getR());
currentState = State.READY;
// 注册撤销栈
iModel.registerUndoStack(blobAction);
}
case "MOVE" -> {
// 重复移动过程
dX = blobAction.getPrevX() - blobAction.getBlob().getX();
dY = blobAction.getPrevY() - blobAction.getBlob().getY();
blobAction.setPrevX(blobAction.getBlob().getX());
blobAction.setPrevY(blobAction.getBlob().getY());
model.moveBlob(blobAction.getBlob(), dX,dY);
// 注册撤销栈
iModel.registerUndoStack(blobAction);
}
case "DELETE" -> {
// 重新删除
model.deleteBlobs(blobAction.getBlob());
currentState = State.READY;
// 注册撤销栈
iModel.registerUndoStack(blobAction);
}
case "MOTIFY" -> {
// 撤销放大操作
double dX = blobAction.getPrevR() - blobAction.getBlob().getR();
blobAction.setPrevR(blobAction.getBlob().getR());
model.motifyR(blobAction.getBlob(),dX);
// 注册撤销栈
iModel.registerUndoStack(blobAction);
System.out.println("注册undo动作"+blobAction);
}
}
}
5.最后的优化:
2.5 撤消/重做操作应该容纳作为原始操作一部分的任何多个选择(例如,移动多个)
方法:
1.注册撤销操作时将类型相同,操作对象相同的操作合并
代码实现:
BlobController.java
public void registerUndoStack(BlobAction blobAction){
System.out.println("正在注册可撤销动作");
if (!undoStack.isEmpty()){
BlobAction lastElement = undoStack.lastElement();
// 将类型相同,处理对象相同的动作合并
if(lastElement.getType() == blobAction.getType() && lastElement.getBlob() == blobAction.getBlob()){
lastElement.setBlob(blobAction.getBlob());
}else {
undoStack.push(blobAction);
}
}else {
undoStack.push(blobAction);
}
System.out.println(blobAction);
}
本文详细描述了一个项目如何实现撤销和重做功能,包括创建BlobAction类来记录操作,使用InteractionModel管理撤销和重做栈,设置全局快捷键以及在控制事件中处理撤销、重做操作。
970





