行业知识
(JavaFx项目)一个鼠标点击训练的小程序(五)
���录
Part1拓展
拓展需求:
需求分步:
具体实现
1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展
2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标
3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配
此部分是基于对第一部分功能的拓展!
Part1拓展
拓展需求:
1.1 用户可以添加/删除单个目标/从选择通过控制点击
1.2 用户可以使用讲座中讨论的“橡皮筋-套索”组合方法选择多个目标
1.2.1 简化使用橡皮筋或套索的决定,以选择更多的项目
1.3 用户可以通过按下控制键和使用橡皮带套索控制来添加/删除多个目标
1.4 当多个目标被选中时,操作会影响所有选中的项目(移动,调整大小,删除)
需求分步:
1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展
2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标
3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配
具体实现
1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展
思路:实现多选功能就肯定需要一个列表对选中的目标进行存储,但之前的单选也应该保留,所以我就想到普通单选和多选可以分开存储,那么就必须保证单选和多选不会起冲突(不可以同时有单选和多选两种情况)
实现方法:
1.在iModel类中添加多选列表,并添加相关方法,包括增删改查以及Blob是否在列表中等方法。
public class InteractionModel { Blob selected; // 多选列表 List selectedBlobs = new ArrayList(); public void setSelected(Blob b) { clearSelectedBlobs(); selected = b; notifySubscribers(); } public void unselect() { selected = null; notifySubscribers(); } public Blob getSelected() { return selected; } // 多选列表操作的相关方法 public void setSelectedBlobs(List blobs){ selectedBlobs = blobs; notifySubscribers(); } public void addSelectedBlobs(Blob blob){ selectedBlobs.add(blob); notifySubscribers(); } public void deleteSelectedBlob(Blob blob){ Iterator it = selectedBlobs.iterator(); while (it.hasNext()) { Blob s = it.next(); if (blob.equals(s)) { it.remove(); } } notifySubscribers(); } public void clearSelectedBlobs(){ selectedBlobs.clear(); notifySubscribers(); } public List getSelectedBlobs(){ return selectedBlobs; } public boolean containedInSelected(Blob blob){ Iterator it = selectedBlobs.iterator(); while (it.hasNext()) { Blob s = it.next(); if (blob.equals(s)) { return true; } } return false; } } 2.在View类中的画操作中添加对多选目标的显示
仅仅只需要对imodel的多选列表中的对象显示即可
public class BlobView extends StackPane implements BlobModelListener, IModelListener {private void draw() { gc.clearRect(0,0,myCanvas.getWidth(),myCanvas.getHeight()); model.getBlobs().forEach(b -> { // 仅仅只需要对imodel的多选列表中的对象显示即可 if (b == iModel.getSelected() || iModel.containedInSelected(b)) { gc.setFill(Color.TOMATO); } else { gc.setFill(Color.BEIGE); } gc.fillOval(b.x-b.r,b.y-b.r,b.r*2,b.r*2); gc.setFill(Color.BLACK); gc.fillText(String.valueOf(b.getCount()), b.x-3,b.y+3); }); }} 3.修改controller类中的多选操作以及对单选与多选逻辑上修改即可
鼠标按压时,判断按压背景还是某个圆,是某个圆,则就是选择操作
选择 从是否按压Ctrl上可以区分为多选还是单选。
若是多选 则通过判断该圆是否被选中后 对圆进行选中/取消(为了分开单选和多选,在多选时就要将单选取消)
若是单选 若此时多选不为空(即正在进行多选操作,就单选不成功)
为空 就可以正常使用
之后的每一个增加,删除,修改操作都是对两种情况进行判断
若单选 正常操作即可
多选 对多个目标边里进行操作即可
public class BlobController { BlobModel model; InteractionModel iModel; double prevX,prevY; double dX,dY; enum State {READY,PREPARE_CREATE, DRAGGING, DRAGGING_TOOL} State currentState = State.READY; public void handlePressed(MouseEvent event) { switch (currentState) { case READY -> { if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪ Blob b = model.whichHit(event.getX(),event.getY()); // 对多选和单选就行区分 if (event.isControlDown()){ if (iModel.containedInSelected(b)){ iModel.deleteSelectedBlob(b); }else { iModel.addSelectedBlobs(b); iModel.unselect(); } }else if (iModel.getSelectedBlobs().isEmpty()){ iModel.setSelected(b); } prevX = event.getX(); prevY = event.getY(); currentState = State.DRAGGING; // 选中之后就改变状态为dragging } else { if (event.isControlDown()){ iModel.clearPoints(); iModel.setPathComplete(false); System.out.println("设置起点"); iModel.setInitialPointX(event.getX()); iModel.setInitialPointY(event.getY()); iModel.addPoint(new Point2D(event.getX(), event.getY())); System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY()); currentState = State.DRAGGING_TOOL; } 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; prevX = event.getX(); prevY = event.getY(); if (iModel.getSelected() != null){ System.out.println("移动之前"+iModel.getSelected()); BlobAction blobAction = BlobAction.DraggedBlobRecord(iModel.getSelected()); model.moveBlob(iModel.getSelected(), dX,dY); System.out.println("移动之后"+iModel.getSelected()); // 创建一个移动操作 List blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); }else { // 同时移动多个选中目标 List blobActions = iModel.createBlobActions(); List selectedBlobs = iModel.getSelectedBlobs(); selectedBlobs.forEach(b -> { BlobAction blobAction = BlobAction.DraggedBlobRecord(b); blobActions.add(blobAction); }); model.moveBlobs(selectedBlobs, dX, dY); // 创建多个移动操作 iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); System.out.println("多个移动栈"+blobActions); } }else{ // 更改⚪的大小 dX = event.getX() - prevX; if (iModel.getSelected() != null){ Blob blob = iModel.getSelected(); BlobAction blobAction = BlobAction.MotifiedBlobRecord(blob); model.motifyR(blob,dX); // 创建更改大小操作 List blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); }else { List blobActions = iModel.createBlobActions(); // 修改多选的多个圆大小 List selectedBlobs = iModel.getSelectedBlobs(); selectedBlobs.forEach(b -> { BlobAction blobAction = BlobAction.MotifiedBlobRecord(b); blobActions.add(blobAction); }); model.motifyRs(selectedBlobs, dX); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); System.out.println("多个更改大小栈"+blobActions); } } } } } public void handleReleased(MouseEvent event) { // 鼠标释放 switch (currentState) { case PREPARE_CREATE -> { // 创建一个⚪ if (event.isShiftDown()){ Blob blob = model.addBlob(event.getX(), event.getY()); currentState = State.READY; // 注册一个创建操作 List blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(blob,"CREATE")); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); }else if (event.isControlDown()){ // 删除所有⚪ model.clear(); iModel.clearUndoStack(); iModel.clearRedoStack(); }else { iModel.unselect(); iModel.clearSelectedBlobs(); } } case DRAGGING -> {// iModel.unselect(); currentState = State.READY; } case DRAGGING_TOOL -> { currentState = State.READY; iModel.setPathComplete(true); } } } public void keyHandlePressed(KeyEvent event) { // 键盘输入 System.out.println(event.getCode()); List blobActions; if (event.getCode() == KeyCode.DELETE){ System.out.println("正在执行删除操作"); System.out.println(iModel.getSelected()); System.out.println(currentState); if (iModel.getSelected() != null){ System.out.println("删除单个选中目标"); model.deleteBlob(iModel.getSelected()); // 注册删除操作 blob type blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(iModel.getSelected(), "DELETE")); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); }else { System.out.println("删除多个选中目标"); blobActions = iModel.createBlobActions(); List selectedBlobs = iModel.getSelectedBlobs(); selectedBlobs.forEach(b -> { BlobAction blobAction = new BlobAction(b,"DELETE"); blobActions.add(blobAction); }); model.deleteBlobs(selectedBlobs); iModel.registerUndoStack(blobActions); iModel.clearRedoStack(); System.out.println("多个删除栈"+blobActions); } } } } 2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标
1.实现Ctrl + 单击 (上面其实已经实现了)
2.“橡皮筋-套索”组合方法选择多个目标
1.矩形
思路:在鼠标按压时,设置起始位置点,在拖动时,鼠标的位置就是终止点。这两个点就是矩形的对角点。然后通过这两个点在视图层中画出矩形
实现步骤:
1.在iModel类中添加起始点和游标位置的存储
设置游标点时,view层就要刷新,所以要添加检测方法dragNotifySubscribers();
public class InteractionModel { // 起始位置double initialPointX,initialPointY; // 游标位置 double cursorX,cursorY; public void setCursorXY(double cursorX,double cursorY) { this.cursorX = cursorX; this.cursorY = cursorY; dragNotifySubscribers(); } get/set方法。。。。} 2.在View类中画出矩形,并设置动态检测方法
(1).在检测接口类添加检测方法
public interface IModelListener { void iModelChanged(); void iModelDragChanged();} (2).在视图类中实现该方法并画出矩形
public class BlobView extends StackPane implements BlobModelListener, IModelListener {private void drawDrag() { // 矩形 if (!iModel.isPathComplete()){ gc.setStroke(Color.GREEN); gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()), Math.min(iModel.getInitialPointY(),iModel.getCursorY()), Math.abs(iModel.getCursorX() - iModel.getInitialPointX()), Math.abs(iModel.getCursorY() - iModel.getInitialPointY())); } }@Override public void iModelChanged() { draw(); drawDrag(); } @Override public void iModelDragChanged() { draw(); drawDrag(); }} (3)iModel类中设置检测方法(也就是第一步中用于监测的方法)
public class InteractionModel { // 对矩形之外的图形进行监测 private void notifySubscribers() { subscribers.forEach(s -> s.iModelChanged()); } // 对矩形进行监测 private void dragNotifySubscribers() { subscribers.forEach(s -> s.iModelDragChanged()); }} 3.控制层中实现矩形多选圆的算法
该算法很简单就是判断圆心是否在矩形中,并且圆心到四条边的距离是否大于圆的半径即可判断
public boolean isBlobInStrokeRect(Blob blob){ if (!(blob.getX() > iModel.getInitialPointX() == blob.getX() iModel.getInitialPointY() == blob.getY() blob.getR() && Math.abs(blob.getX() - iModel.getCursorX()) > blob.getR() && Math.abs(blob.getY() - iModel.getInitialPointY()) > blob.getR() && Math.abs(blob.getY() - iModel.getCursorY()) > blob.getR()){ return true; } return false; } 4.就是在controller增重设置起始点与游标点即可
拖动时设置新的状态机 DRAGGING_TOOL 用于画橡皮筋和矩形时使用(和更改大小,拖动等操作区分开)
public class BlobController {public void handlePressed(MouseEvent event) { switch (currentState) { case READY -> { if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪ .......... // 选中之后就改变状态为dragging } else { if (event.isControlDown()){ iModel.clearPoints(); iModel.setPathComplete(false); System.out.println("设置起点");// 按压时添加起始点 iModel.setInitialPointX(event.getX()); iModel.setInitialPointY(event.getY()); iModel.addPoint(new Point2D(event.getX(), event.getY())); System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY()); currentState = State.DRAGGING_TOOL; } else { currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪ } } } } }public void handleDragged(MouseEvent event) { // 鼠标拖动 switch (currentState) { case DRAGGING_TOOL -> { if (event.isControlDown()){ iModel.clearSelectedBlobs(); System.out.println("设置游标点"); iModel.setCursorXY(event.getX(), event.getY()); iModel.addPoint(new Point2D(event.getX(), event.getY())); // 设置之后对所有圆遍历判断 是否在矩形中model.getBlobs().forEach(b -> { System.out.println("开始运行。。。"); if (isBlobInStrokeRect(b)){ // 是 则被选中 iModel.addSelectedBlobs(b); } if (isBlobInPoints(b)){ iModel.addSelectedBlobs(b); } }); } } } }} 2.橡皮筋-套索”
思路:就是将鼠标移动时的点全纪录下来,通过这些点判断圆是否被圈中
实现步骤:
1.将鼠标移动时的点记录在iModel类中,同时使用pathComplete来判断在鼠标移动过程以及鼠标按压/释放状态
刚按压时 pathComplete -> false
过程中 false
释放时 true
public class InteractionModel {// 记录鼠标移动时的点 List points = new ArrayList(); boolean pathComplete; public boolean isPathComplete() { return pathComplete; } public void setPathComplete(boolean pathComplete) { this.pathComplete = pathComplete; dragNotifySubscribers(); if (pathComplete == true){ notifySubscribers(); } } public List getPoints() { return points; } public void addPoint(Point2D point){ points.add(point); dragNotifySubscribers(); } public void clearPoints(){ points.clear(); dragNotifySubscribers(); }} 2.通过算法判断圆是否被圈中
算法思路:
首先排除经过的点有经过圆的。
然后将圆心放在坐标轴中心,四个象限必须全部有经过的点才算作被包裹。(可以使用的大多数情况)
但是也有例外。就是经过圆四周但是不包裹圆的情况,这个属于算法上的缺陷
如上图,就无法判定圆不在索套中
public boolean isBlobInPoints(Blob blob){ List list = new ArrayList(); List points = iModel.getPoints(); for (Point2D point : points) { if (getLength(blob,point) blob.getX() && point.getY() blob.getX() && point.getY() > blob.getY()){ if (list.size() == 0){ list.add(3); } if (list.size() != 0 && list.get(list.size() - 1) != 3){ list.add(3); } } if (point.getX() blob.getY()){ if (list.size() == 0){ list.add(4); } if (list.size() != 0 && list.get(list.size() - 1) != 4){ list.add(4); } } } if (list.size() >= 4 && list.contains(1) && list.contains(2) && list.contains(3) && list.contains(4)){ return true; } return false; } 3.在controller类中拖动时将点记录在iModel中
public class BlobController {public void handlePressed(MouseEvent event) { switch (currentState) { case READY -> { if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪ Blob b = model.whichHit(event.getX(),event.getY()); if (event.isControlDown()){ if (iModel.containedInSelected(b)){ iModel.deleteSelectedBlob(b); }else { iModel.addSelectedBlobs(b); iModel.unselect(); } }else if (iModel.getSelectedBlobs().isEmpty()){ iModel.setSelected(b); } prevX = event.getX(); prevY = event.getY(); currentState = State.DRAGGING; // 选中之后就改变状态为dragging } else { if (event.isControlDown()){ iModel.clearPoints(); // 按压时将pathComplete -> false iModel.setPathComplete(false); System.out.println("设置起点"); iModel.setInitialPointX(event.getX()); iModel.setInitialPointY(event.getY()); // 记录起始点 iModel.addPoint(new Point2D(event.getX(), event.getY())); currentState = State.DRAGGING_TOOL; } else { currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪ } } } } }public void handleDragged(MouseEvent event) { // 鼠标拖动 switch (currentState) { case DRAGGING_TOOL -> { if (event.isControlDown()){ iModel.clearSelectedBlobs(); System.out.println("设置游标点"); iModel.setCursorXY(event.getX(), event.getY()); // 添加拖动时的点 iModel.addPoint(new Point2D(event.getX(), event.getY())); System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY()); System.out.println("游标点:"+iModel.getCursorX()+","+iModel.getCursorY()); model.getBlobs().forEach(b -> { if (isBlobInStrokeRect(b)){ iModel.addSelectedBlobs(b); } // 判断是否被套中 if (isBlobInPoints(b)){ iModel.addSelectedBlobs(b); } }); } } } }} 4.将imodel记录的点在view层显示出来
public class BlobView extends StackPane implements BlobModelListener, IModelListener {private void drawDrag() { // 矩形 if (!iModel.isPathComplete()){ gc.setStroke(Color.GREEN); gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()), Math.min(iModel.getInitialPointY(),iModel.getCursorY()), Math.abs(iModel.getCursorX() - iModel.getInitialPointX()), Math.abs(iModel.getCursorY() - iModel.getInitialPointY())); } // 橡皮筋 if (!iModel.isPathComplete()) { gc.setFill(Color.DARKGRAY); iModel.getPoints().forEach(p -> gc.fillOval(p.getX()-3,p.getY()-3,6,6)); } }} 3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配
思路:将撤销重做栈 和 剪切板 修改为列表数据类型即可,之前的所有操作都修改为基于列表的操作即可。就是个体力活,不多介绍啦。
上一篇:国外服务器哪里买?