目录
项目需求
项目实现
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;
}
}
四部分功能基本完成!