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

目录

项目需求

 项目实现

Part4

需求分析

代码要求:

实现:


项目需求

 项目实现

Part4

需求分析

需求:

4.1 当用户按Control-T键时,系统切换到“目标训练器”视图

4.1.1 视图一次显示一个目标,按照它们存储的顺序

4.1.2 当用户单击目标时,将显示下一个目标

4.1.3 当勾选所有目标器后,系统进入报表视图

4.2 如果用户按下Control-E键,系统立即切换回编辑器视图(不显示报表视图)

4.3 报表视图要收集的数据

用户在第一个目标上的第一次点击就是第一次瞄准试验的开始

在第二个目标上的下一次点击是第一次试验的结束

4.3.1 系统记录每次试验开始和结束之间的经过时间

4.3.2 系统还根据前一个目标之间的距离记录每次试验的难度指数(ID)

4.3.3 以及当前目标和当前目标的宽度

注意:

1.如果集合中有N个目标,将有N-1次试验

2.图表将MT(以毫秒为单位的移动时间)与ID(难度指数)进行对比,并在每次尝试中使用一个点

3.与报表视图没有用户交互

4.当报表视图处于活动状态时,按Control-T键重新启动测试,按Control-E键返回编辑器

代码要求:

1.向InteractionModel中添加代码以跟踪应用程序的模式(例如:EDIT, TEST, REPORT)

2.创建一个额外的发布/订阅接口AppModeListener,以便iModel可以在模式改变时监听

3.MainUI类应该监听应用程序模式的变化,并在适当的时候切换视图

4.为“target trainer”视图创建一个额外的视图类

5.创建一个额外的控制器类来处理与目标训练器视图的交互

6.向InteractionModel中添加数据结构,以跟踪每个目标试验

6.1 例如,创建一个TrialRecord类来存储每次试验的运行时间和ID,并将这些存储在一个列表中

7.创建一个显示摘要图表的报表视图类

7.1 使用JavaFX提供的现有ScatterChart类

7.2 X轴为地块ID, Y轴为MT(即经过时间)

实现:

思路:要做另外两个独立的视图,一个要将编辑时的圆一个一个显示出来,同时记录每两个圆的间隔时间一个做一张静态表单

实现方法:

1.实现第一个视图,包括model层,view层,controller层还有bean类

1.1 model层

第二个视图用到的模型就是从第一个视图中取出来的,所以对BlobModel进行简单的添加即可

BlobModel.java

public class BlobModel {
    private List<BlobModelListener> subscribers;
  	// 测试层监测器 
    private List<BlobModelListener> testSubscribers;
  	// 所有存储的圆列表
    private List<Blob> blobs;
  	// 测试层显示的圆
    private Blob testBlob;
    int count = 0;
  	// 当前测试层显示的圆的下标 + 1
    int testCount = 0;

    

    public void addSubscriber(BlobModelListener sub) {
        subscribers.add(sub);
    }

    private void notifySubscribers() {
        subscribers.forEach(s -> s.modelChanged());
    }

    // 测试层 监测器相关方法
    public void addTestSubscriber(BlobModelListener sub) {
        testSubscribers.add(sub);
    }
  	// 刷新视图显示
    private void testNotifySubscribers() {
        testSubscribers.forEach(s -> s.modelChanged());
    }

    // 测试模式 
  	// 测试层显示下一个圆
    public Blob addTestBlob(){
        if (testCount > count - 1){
            return null;
        }
        Blob blob = blobs.get(testCount++);
        testBlob = blob;
        testNotifySubscribers();
        return blob;
    }
  	// 获取当前测试层模型
    public Blob getTestBlob() {
        return testBlob;
    }
  	// 清空测试层当前模型 并 重置下标
    public void clearTestBlobs(){
        // 清空list
        testBlob = null;
        this.testCount = 0;
        testNotifySubscribers();
    }
    
}

1.2 bean类

记录运行时的时间间隔,两个圆之间的间距,宽度之间的间距


public class TrialRecord {
    Long time;
    Double ID;
    Double width;

    public TrialRecord(Long time, Double ID, Double width) {
        this.time = time;
        this.ID = ID;
        this.width = width;
    }

    ..... get/set/toString方法
}

1.3 view层

特别简单,就是对第一个视图简单修改,仅显示为model层中测试层对应的目标即可。(其他的监测器都和第一个视图一致即可,仅仅修改显示内容和控制层内容即可)

TestBlobView.java

public class TestBlobView extends StackPane implements BlobModelListener, IModelListener {
    GraphicsContext gc;
    Canvas myCanvas;
    BlobModel model;
    InteractionModel iModel;

    public TestBlobView() {
        myCanvas = new Canvas(800,800);
        // 将焦点指定给任何项目
        myCanvas.setFocusTraversable(true);
        gc = myCanvas.getGraphicsContext2D();
        gc.setFill(Color.ORANGE);
        gc.fillRect(100,100,200,200);

        this.getChildren().add(myCanvas);
    }
  	// 仅仅画出模型层中测试曾那个圆即可
    private void draw() {
        gc.clearRect(0,0,myCanvas.getWidth(),myCanvas.getHeight());
        Blob b = model.getTestBlob();
        if (b != null) {
            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);
        }
    }

    public void setModel(BlobModel newModel) {
        model = newModel;
    }

    public void setIModel(InteractionModel newIModel) {
        iModel = newIModel;
    }

    @Override
    public void modelChanged() {
        draw();
    }

    @Override
    public void iModelChanged() {
        draw();
    }

    public void setController(TestBlobController controller) {
        // 调用控制层函数
        myCanvas.setOnMousePressed(controller::handlePressed);
    }
}

1.4 controller层

控制层需要的组件:

1.model层 :控制层要修改model层中测试层的圆(单击一下显示下一个)

2.imodel:imodel层要添加视图转换的状态机(当所有圆都显示出来时,要进入报表视图)(后续步骤具体说明)

控制层要收集的数据:

1.两次点击的时间间隔和两个圆的间距与宽度(生成的列表被封装在iModel的一个属性中)

public class TestBlobController {

    BlobModel model;
    InteractionModel iModel;

    Long preTime = null;
    Blob preBlob = null;

    public TestBlobController() {
    }

    public BlobModel getModel() {
        return model;
    }

    public InteractionModel getiModel() {
        return iModel;
    }

    public void setiModel(InteractionModel iModel) {
        this.iModel = iModel;
    }

    public void setModel(BlobModel model) {
        this.model = model;
    }
  	
    public void handlePressed(MouseEvent mouseEvent) {
        Blob blob = model.addTestBlob();
        long currentTimeMillis = System.currentTimeMillis();
        if (this.preTime != null && this.preBlob != null && blob != null){
            Long time = currentTimeMillis - preTime;
            Double ID = Math.sqrt(Math.pow(blob.getX()-preBlob.getX(),2) + Math.pow(blob.getY() - preBlob.getY(),2));
            Double width = blob.getY() - preBlob.getY();
          	// 将实验数据添加到iModel中
            iModel.addTrialRecord(new TrialRecord(time, ID, width));
        }
        this.preTime = currentTimeMillis;
        this.preBlob = blob;
        if (blob == null){
          	// 更改到报表视图状态
            iModel.setReportState();
        }
    }
}

1.5 iModel类

暂时只用来存储测试层的数据,后续要加状态机管理视图


public class InteractionModel {
   
  	// 用来存储测试层的数据
    List<TrialRecord> records = new ArrayList<>();

    // 记录间隔
    public void addTrialRecord(TrialRecord trialRecord){
        records.add(trialRecord);
    }
    public List<TrialRecord> getRecords(){
        return records;
    }

    public void clearRecords(){
        records.clear();
    }
}

2.报表视图

报表视图是静态视图,所以仅需要数据和视图即可

数据被保存在iModel类中

视图则使用ScatterChart类创建散点图

因为是静态视图,所以仅继承StackPane类即可,不需要实现前两个视图的监测器接口

public class ReportView extends StackPane {

    InteractionModel iModel;

    ScatterChart<Number, Number> sc;

    public ReportView(){
      	// 初始化 X,Y轴 等信息
        NumberAxis xAxia = new NumberAxis(0,4,0.25);
        NumberAxis yAxia = new NumberAxis(0,1000,50);
        sc = new ScatterChart<>(xAxia, yAxia);
        xAxia.setLabel("ID(bits)");
        yAxia.setLabel("MT(ms)");
        xAxia.setAutoRanging(true);
        yAxia.setAutoRanging(true);
        sc.setTitle("Targeting Performancce");
    }

  	// 将记录的数据添加到图表中
    public void setData(List<TrialRecord> records){
        System.out.println("所有数据:"+records);
        XYChart.Series series = new XYChart.Series<>();
        records.forEach(r ->{
            series.getData().add(new XYChart.Data(r.getID()/100, r.getTime()));
        });
        sc.getData().addAll(series);
        this.getChildren().add(sc);
    }

    public void setiModel(InteractionModel iModel) {
        this.iModel = iModel;
    }
}

3.视图解决之后,就是使用状态机进行状态管理

代码要求:

1.向InteractionModel中添加代码以跟踪应用程序的模式(例如:EDIT, TEST, REPORT)

先创建一个状态机管理视图,当然还起不了作用。(还需要监测类对视图真正操作视图状态)

InteractionModel.java

public class InteractionModel {
    
    // 创建state控制器    主要用于控制视图
    enum State {EDIT, TEST, REPORT}
  	// 默认的状态是EDIT
    State currentState = State.EDIT;

    // 修改为三个状态对应的函数
    public void setEditState(){
        this.currentState = State.EDIT;
        appModeListener.modelChanged();

    }
    public void setTestState(){
        this.currentState = State.TEST;
        appModeListener.modelChanged();
    }
    public void setReportState(){
        this.currentState = State.REPORT;
        appModeListener.modelChanged();
    }
  	// 获取当前的状态
    public State getCurrentState() {
        return currentState;
    }
}

2.创建一个额外的发布/订阅接口AppModeListener,以便iModel可以在模式改变时监听

3.MainUI类应该监听应用程序模式的变化,并在适当的时候切换视图

实现:

1.创建一个监测接口类,用于监测模式变化

AppModeListener.java

public interface AppModeListener {

    void modelChanged();
}

2.MainUI类实现该接口,并真正实现切换视图的功能

1.要获取iModel类中视图状态机的状态

2.切换视图时的操作

EDIT:仅切换视图

TEST: 1. 将model类中测试层的数据清空 2.清空iModel中的试验记录

3.切换视图

REPORT: 1.初始化报表视图 2.添加实验数据 3.切换视图

public class MainUI extends StackPane implements AppModeListener{

    InteractionModel iModel;

    BlobModel model;
    BlobView view;
    TestBlobView testBlobView;
    ReportView reportView;
    BlobController controller;
    public MainUI() {

        model = new BlobModel();
        this.controller = new BlobController();
        this.view = new BlobView();
        this.iModel = new InteractionModel();
        BlobClipboard clipboard = new BlobClipboard();

        // 测试控制器
        TestBlobController testBlobController = new TestBlobController();
        this.testBlobView = new TestBlobView();


        controller.setModel(model);
        view.setModel(model);
        controller.setIModel(iModel);
        view.setIModel(iModel);
        model.addSubscriber(view);
        iModel.addSubscriber(view);
        iModel.setBlobClipboard(clipboard);

        view.setController(controller);

        // 测试模式视图
        model.addTestSubscriber(testBlobView);
        testBlobView.setModel(model);
        testBlobView.setIModel(iModel);
//        iModel.addTestSubscriber(testBlobView);
        testBlobController.setModel(model);
        testBlobController.setiModel(iModel);
        iModel.setAppModeListener(this);
        testBlobView.setController(testBlobController);

        this.getChildren().add(view);
    }

  
  	// 实现接口方法
    @Override
    public void modelChanged() {
        InteractionModel.State currentState = iModel.getCurrentState();
        switch (currentState){
            case EDIT -> {
              	// 删除另外两个视图 添加编辑视图
                this.getChildren().removeAll(testBlobView,reportView);
                this.getChildren().add(view);
            }
            case TEST -> {
              	// 测试开始时,清除模型层
                model.clearTestBlobs();
                iModel.clearRecords();
                this.getChildren().removeAll(view,reportView);
                this.getChildren().add(testBlobView);
            }
            case REPORT -> {
                this.getChildren().removeAll(view,testBlobView);

                this.reportView = new ReportView();
                reportView.setiModel(iModel);
                reportView.setData(iModel.getRecords());
                this.getChildren().add(reportView);
            }
        }
    }
}

4.当前的iModel的状态机仅仅实现了状态的切换,咱们还需要将MainUI中实现的转换状态的方法用在状态机中,状态机才可以真正管理状态的转变。

即:为改变状态机状态的每一个方法添加appModeListener.modelChanged();方法

实现:

将mainUI中放进iModel中,使用mainUI实现的方法放入改变状态的方法中

public class InteractionModel {
    // 创建state控制器    主要用于控制视图
    enum State {EDIT, TEST, REPORT}

    State currentState = State.EDIT;
  	// 添加监测器  就是实现了该接口的MainUI类
    AppModeListener appModeListener;


    public AppModeListener getAppModeListener() {
        return appModeListener;
    }

    public void setAppModeListener(AppModeListener appModeListener) {
        this.appModeListener = appModeListener;
    }

    // 修改状态
    public void setEditState(){
        this.currentState = State.EDIT;
      	// 使用该方法转变状态
        appModeListener.modelChanged();

    }
    public void setTestState(){
        this.currentState = State.TEST;
        appModeListener.modelChanged();
    }
    public void setReportState(){
        this.currentState = State.REPORT;
        appModeListener.modelChanged();
    }

    public State getCurrentState() {
        return currentState;
    }

}

四部分功能基本完成!

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值