You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
8.3 KiB
268 lines
8.3 KiB
package com.personalproject.ui.views;
|
|
|
|
import com.personalproject.controller.MathLearningController;
|
|
import com.personalproject.model.ExamSession;
|
|
import com.personalproject.model.QuizQuestion;
|
|
import javafx.geometry.Insets;
|
|
import javafx.geometry.Pos;
|
|
import javafx.scene.control.Alert;
|
|
import javafx.scene.control.Button;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.RadioButton;
|
|
import javafx.scene.control.ToggleGroup;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.HBox;
|
|
import javafx.scene.layout.VBox;
|
|
import javafx.scene.text.Font;
|
|
import javafx.scene.text.FontWeight;
|
|
import javafx.stage.Stage;
|
|
|
|
/**
|
|
* 提供答题界面以及题目与选项的呈现.
|
|
*/
|
|
public class ExamView extends BorderPane {
|
|
|
|
private final Stage primaryStage;
|
|
private final MathLearningController controller;
|
|
private final ExamSession examSession;
|
|
private Label questionNumberLabel;
|
|
private Label questionTextLabel;
|
|
private ToggleGroup answerToggleGroup;
|
|
private VBox optionsBox;
|
|
private Button nextButton;
|
|
private Button previousButton;
|
|
private Button finishButton;
|
|
private HBox buttonBox;
|
|
|
|
/**
|
|
* ExamView 的构造函数.
|
|
*
|
|
* @param primaryStage 应用程序的主舞台
|
|
* @param controller 数学学习控制器
|
|
* @param examSession 当前的考试会话
|
|
*/
|
|
public ExamView(Stage primaryStage, MathLearningController controller, ExamSession examSession) {
|
|
this.primaryStage = primaryStage;
|
|
this.controller = controller;
|
|
this.examSession = examSession;
|
|
initializeUi();
|
|
}
|
|
|
|
/**
|
|
* 初始化界面组件.
|
|
*/
|
|
private void initializeUi() {
|
|
// 创建主布局
|
|
VBox mainLayout = new VBox(20);
|
|
mainLayout.setAlignment(Pos.CENTER);
|
|
mainLayout.setPadding(new Insets(20));
|
|
|
|
// 题号
|
|
questionNumberLabel = new Label();
|
|
questionNumberLabel.setFont(Font.font("System", FontWeight.BOLD, 16));
|
|
|
|
// 题目文本
|
|
questionTextLabel = new Label();
|
|
questionTextLabel.setWrapText(true);
|
|
questionTextLabel.setFont(Font.font("System", FontWeight.NORMAL, 14));
|
|
questionTextLabel.setMaxWidth(500);
|
|
|
|
// 选项容器
|
|
optionsBox = new VBox(10);
|
|
optionsBox.setPadding(new Insets(10));
|
|
answerToggleGroup = new ToggleGroup();
|
|
|
|
// 按钮区域
|
|
buttonBox = new HBox(15);
|
|
buttonBox.setAlignment(Pos.CENTER);
|
|
|
|
previousButton = new Button("上一题");
|
|
nextButton = new Button("下一题");
|
|
finishButton = new Button("完成考试");
|
|
|
|
// 设置按钮尺寸
|
|
previousButton.setPrefSize(100, 35);
|
|
nextButton.setPrefSize(100, 35);
|
|
finishButton.setPrefSize(120, 35);
|
|
|
|
buttonBox.getChildren().addAll(previousButton, nextButton, finishButton);
|
|
|
|
// 将组件添加到主布局
|
|
mainLayout.getChildren().addAll(questionNumberLabel, questionTextLabel, optionsBox, buttonBox);
|
|
|
|
// 将主布局置于边界面板中央
|
|
setCenter(mainLayout);
|
|
|
|
// 加载第一题
|
|
loadCurrentQuestion();
|
|
|
|
// 添加事件处理器
|
|
addEventHandlers();
|
|
}
|
|
|
|
/**
|
|
* 将当前题目加载到界面.
|
|
*/
|
|
private void loadCurrentQuestion() {
|
|
try {
|
|
// 在加载下一题之前检查考试是否已完成
|
|
if (examSession.isComplete()) {
|
|
// 如果考试已完成,则启用“完成考试”按钮
|
|
updateButtonStates();
|
|
return;
|
|
}
|
|
|
|
QuizQuestion currentQuestion = examSession.getCurrentQuestion();
|
|
int currentIndex = examSession.getCurrentQuestionIndex();
|
|
|
|
if (currentQuestion == null) {
|
|
showAlert(Alert.AlertType.ERROR, "错误", "当前题目为空,请重新开始考试");
|
|
return;
|
|
}
|
|
|
|
// 更新题号与题目文本
|
|
questionNumberLabel.setText("第 " + (currentIndex + 1) + " 题");
|
|
questionTextLabel.setText(currentQuestion.getQuestionText());
|
|
|
|
// 清空上一题的选项
|
|
answerToggleGroup.selectToggle(null);
|
|
answerToggleGroup.getToggles().clear();
|
|
optionsBox.getChildren().clear();
|
|
|
|
// 创建新的选项组件
|
|
for (int i = 0; i < currentQuestion.getOptions().size(); i++) {
|
|
String option = currentQuestion.getOptions().get(i);
|
|
RadioButton optionButton = new RadioButton((i + 1) + ". " + option);
|
|
optionButton.setToggleGroup(answerToggleGroup);
|
|
optionButton.setUserData(i); // 存储选项索引
|
|
|
|
// 如果该题已有答案则自动选中
|
|
if (examSession.hasAnswered(currentIndex)
|
|
&& examSession.getUserAnswer(currentIndex) == i) {
|
|
optionButton.setSelected(true);
|
|
}
|
|
|
|
optionsBox.getChildren().add(optionButton);
|
|
}
|
|
|
|
// 更新按钮状态
|
|
updateButtonStates();
|
|
} catch (Exception e) {
|
|
showAlert(Alert.AlertType.ERROR, "错误", "加载题目时发生错误: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 根据当前位置更新导航按钮的状态.
|
|
*/
|
|
private void updateButtonStates() {
|
|
try {
|
|
int currentIndex = examSession.getCurrentQuestionIndex();
|
|
int totalQuestions = examSession.getTotalQuestions();
|
|
|
|
// 处理潜在极端情况
|
|
if (totalQuestions <= 0) {
|
|
// 如果没有题目,则禁用所有导航按钮
|
|
previousButton.setDisable(true);
|
|
nextButton.setDisable(true);
|
|
finishButton.setDisable(false); // 仍允许完成考试
|
|
return;
|
|
}
|
|
|
|
// “上一题”按钮状态
|
|
previousButton.setDisable(currentIndex < 0 || currentIndex == 0);
|
|
|
|
// “下一题”按钮状态
|
|
nextButton.setDisable(currentIndex < 0 || currentIndex >= totalQuestions - 1);
|
|
|
|
// “完成考试”按钮状态——在考试完成或到达最后一题时启用
|
|
boolean isExamComplete = examSession.isComplete();
|
|
boolean isAtLastQuestion = (currentIndex >= totalQuestions - 1);
|
|
finishButton.setDisable(!(isExamComplete || isAtLastQuestion));
|
|
} catch (Exception e) {
|
|
// 若出现异常,禁用导航按钮以避免进一步问题
|
|
previousButton.setDisable(true);
|
|
nextButton.setDisable(true);
|
|
finishButton.setDisable(false); // 仍允许完成考试
|
|
showAlert(Alert.AlertType.ERROR, "错误", "更新按钮状态时发生错误: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 为界面组件添加事件处理器.
|
|
*/
|
|
private void addEventHandlers() {
|
|
nextButton.setOnAction(e -> handleNextQuestion());
|
|
previousButton.setOnAction(e -> handlePreviousQuestion());
|
|
finishButton.setOnAction(e -> handleFinishExam());
|
|
|
|
// 添加变更监听器,在选项被选择时保存答案
|
|
answerToggleGroup.selectedToggleProperty().addListener((obs, oldSelection, newSelection) -> {
|
|
if (newSelection != null) {
|
|
int selectedIndex = (Integer) newSelection.getUserData();
|
|
examSession.setAnswer(selectedIndex);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 处理“下一题”按钮的操作.
|
|
*/
|
|
private void handleNextQuestion() {
|
|
try {
|
|
if (examSession.goToNextQuestion()) {
|
|
loadCurrentQuestion();
|
|
} else {
|
|
// 若无法跳转到下一题,可能已经到达末尾
|
|
// 检查考试是否完成并据此更新按钮状态
|
|
updateButtonStates();
|
|
}
|
|
} catch (Exception e) {
|
|
showAlert(Alert.AlertType.ERROR, "错误", "导航到下一题时发生错误: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理“上一题”按钮的操作.
|
|
*/
|
|
private void handlePreviousQuestion() {
|
|
try {
|
|
if (examSession.goToPreviousQuestion()) {
|
|
loadCurrentQuestion();
|
|
} else {
|
|
// 若无法返回上一题,可能已经位于开头
|
|
updateButtonStates();
|
|
}
|
|
} catch (Exception e) {
|
|
showAlert(Alert.AlertType.ERROR, "错误", "导航到上一题时发生错误: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示提示对话框.
|
|
*
|
|
* @param alertType 提示类型
|
|
* @param title 对话框标题
|
|
* @param message 显示的消息
|
|
*/
|
|
private void showAlert(Alert.AlertType alertType, String title, String message) {
|
|
Alert alert = new Alert(alertType);
|
|
alert.setTitle(title);
|
|
alert.setHeaderText(null);
|
|
alert.setContentText(message);
|
|
alert.showAndWait();
|
|
}
|
|
|
|
/**
|
|
* 处理“完成考试”按钮的操作.
|
|
*/
|
|
private void handleFinishExam() {
|
|
// 保存考试结果
|
|
controller.saveExamResults(examSession);
|
|
|
|
// 展示考试结果
|
|
ExamResultsView resultsView = new ExamResultsView(primaryStage, controller, examSession);
|
|
primaryStage.getScene().setRoot(resultsView);
|
|
}
|
|
}
|