小工具知识网

当前位置: 首页 > 行业知识 > (JavaFx项目)一个鼠标点击训练的小程序(五)

行业知识

(JavaFx项目)一个鼠标点击训练的小程序(五)

2026-11-12 20:57:43 来源:互联网转载

���录

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中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

思路:将撤销重做栈 和 剪切板 修改为列表数据类型即可,之前的所有操作都修改为基于列表的操作即可。就是个体力活,不多介绍啦。

上一篇:国外服务器哪里买?

下一篇:保姆级教程,轻松下载午餐,一键搞定!